diff options
203 files changed, 13069 insertions, 1245 deletions
diff --git a/Android.bp b/Android.bp index 21054ddd89c6..6a85f62a5ae6 100644 --- a/Android.bp +++ b/Android.bp @@ -161,6 +161,7 @@ java_defaults { ":libcamera_client_framework_aidl", "core/java/android/hardware/IConsumerIrService.aidl", "core/java/android/hardware/ISerialManager.aidl", + "core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl", "core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl", "core/java/android/hardware/biometrics/IBiometricService.aidl", "core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl", diff --git a/api/current.txt b/api/current.txt index 535fbbcf1d73..03648ea01c78 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5335,6 +5335,7 @@ package android.app { field public static final String EXTRA_TITLE = "android.title"; field public static final String EXTRA_TITLE_BIG = "android.title.big"; field public static final int FLAG_AUTO_CANCEL = 16; // 0x10 + field public static final int FLAG_BUBBLE = 4096; // 0x1000 field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40 field public static final int FLAG_GROUP_SUMMARY = 512; // 0x200 field @Deprecated public static final int FLAG_HIGH_PRIORITY = 128; // 0x80 @@ -9515,7 +9516,7 @@ package android.content { method public static android.content.SyncAdapterType[] getSyncAdapterTypes(); method public static boolean getSyncAutomatically(android.accounts.Account, String); method @Nullable public final String getType(@NonNull android.net.Uri); - method @NonNull public final android.content.ContentResolver.TypeInfo getTypeInfo(@NonNull String); + method @NonNull public final android.content.ContentResolver.MimeTypeInfo getTypeInfo(@NonNull String); method @Nullable public final android.net.Uri insert(@RequiresPermission.Write @NonNull android.net.Uri, @Nullable android.content.ContentValues); method public static boolean isSyncActive(android.accounts.Account, String); method public static boolean isSyncPending(android.accounts.Account, String); @@ -9595,7 +9596,7 @@ package android.content { field public static final int SYNC_OBSERVER_TYPE_SETTINGS = 1; // 0x1 } - public static final class ContentResolver.TypeInfo { + public static final class ContentResolver.MimeTypeInfo { method @NonNull public CharSequence getContentDescription(); method @NonNull public android.graphics.drawable.Icon getIcon(); method @NonNull public CharSequence getLabel(); @@ -23078,8 +23079,8 @@ package android.media { method @NonNull public android.media.AudioAttributes.Builder setAllowedCapturePolicy(int); method public android.media.AudioAttributes.Builder setContentType(int); method public android.media.AudioAttributes.Builder setFlags(int); + method @NonNull public android.media.AudioAttributes.Builder setHapticChannelsMuted(boolean); method public android.media.AudioAttributes.Builder setLegacyStreamType(int); - method public android.media.AudioAttributes.Builder setMuteHapticChannels(boolean); method public android.media.AudioAttributes.Builder setUsage(int); } @@ -38478,8 +38479,8 @@ package android.provider { public final class MediaStore { ctor public MediaStore(); - method @NonNull public static java.util.Set<java.lang.String> getAllVolumeNames(@NonNull android.content.Context); method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri); + method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context); method public static android.net.Uri getMediaScannerUri(); method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri); method @NonNull public static String getVersion(@NonNull android.content.Context); @@ -38523,6 +38524,7 @@ package android.provider { field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service"; field public static final String UNKNOWN_STRING = "<unknown>"; field public static final String VOLUME_EXTERNAL = "external"; + field public static final String VOLUME_EXTERNAL_PRIMARY = "external_primary"; field public static final String VOLUME_INTERNAL = "internal"; } @@ -38537,6 +38539,7 @@ package android.provider { field public static final String ALBUM_ID = "album_id"; field public static final String ALBUM_KEY = "album_key"; field public static final String ARTIST = "artist"; + field public static final String ARTIST_ID = "artist_id"; field public static final String FIRST_YEAR = "minyear"; field public static final String LAST_YEAR = "maxyear"; field public static final String NUMBER_OF_SONGS = "numsongs"; @@ -38770,6 +38773,7 @@ package android.provider { field public static final String RELATIVE_PATH = "relative_path"; field public static final String SIZE = "_size"; field public static final String TITLE = "title"; + field public static final String VOLUME_NAME = "volume_name"; field public static final String WIDTH = "width"; } @@ -51672,7 +51676,7 @@ 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); @@ -51687,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/removed.txt b/api/removed.txt index fe3e866de682..70ff50ed40a6 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -514,6 +514,7 @@ package android.provider { public final class MediaStore { method @Deprecated @NonNull public static android.net.Uri createPending(@NonNull android.content.Context, @NonNull android.provider.MediaStore.PendingParams); + method @Deprecated @NonNull public static java.util.Set<java.lang.String> getAllVolumeNames(@NonNull android.content.Context); method @Deprecated @NonNull public static android.provider.MediaStore.PendingSession openPending(@NonNull android.content.Context, @NonNull android.net.Uri); method @Deprecated @NonNull public static android.net.Uri setIncludeTrashed(@NonNull android.net.Uri); method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri); diff --git a/api/system-current.txt b/api/system-current.txt index 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 f76881d19e93..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,8 +329,14 @@ package android.app { } public class NotificationManager { + 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); method public boolean matchesCallFilter(android.os.Bundle); + method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean); } public final class PictureInPictureParams implements android.os.Parcelable { @@ -2454,10 +2461,49 @@ package android.service.contentcapture { package android.service.notification { + public final class Adjustment implements android.os.Parcelable { + ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int); + ctor public Adjustment(@NonNull String, @NonNull String, @NonNull android.os.Bundle, @NonNull CharSequence, @NonNull android.os.UserHandle); + method public int describeContents(); + method @NonNull public CharSequence getExplanation(); + method @NonNull public String getKey(); + method @NonNull public String getPackage(); + method @NonNull public android.os.Bundle getSignals(); + method public int getUser(); + method @NonNull public android.os.UserHandle getUserHandle(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR; + field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions"; + field public static final String KEY_IMPORTANCE = "key_importance"; + field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; + field public static final String KEY_TEXT_REPLIES = "key_text_replies"; + field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; + } + @Deprecated public abstract class ConditionProviderService extends android.app.Service { method @Deprecated public boolean isBound(); } + public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { + ctor public NotificationAssistantService(); + 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 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); + method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); + method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String); + method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>); + method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); + method public final void unsnoozeNotification(@NonNull String); + field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; + field public static final int SOURCE_FROM_APP = 0; // 0x0 + field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1 + } + public abstract class NotificationListenerService extends android.app.Service { method public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int); } @@ -2653,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); @@ -2835,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/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index f7608f5320e8..82d177aeede2 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -3251,13 +3251,12 @@ message BinaryPushStateChanged { INSTALLER_ROLLBACK_BOOT_TRIGGERED_FAILURE = 14; INSTALLER_ROLLBACK_SUCCESS = 15; INSTALLER_ROLLBACK_FAILURE = 16; - INSTALLER_ROLLBACK_CANCEL_STAGED_REMOVE_FROM_QUEUE = 17; - INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_INITIATED = 18; - INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_SUCCESS = 19; - INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_FAILURE = 20; - INSTALL_STAGED_CANCEL_REQUESTED = 21; - INSTALL_STAGED_CANCEL_SUCCESS = 22; - INSTALL_STAGED_CANCEL_FAILURE = 23; + INSTALLER_ROLLBACK_STAGED_CANCEL_REQUESTED = 17; + INSTALLER_ROLLBACK_STAGED_CANCEL_SUCCESS = 18; + INSTALLER_ROLLBACK_STAGED_CANCEL_FAILURE = 19; + INSTALL_STAGED_CANCEL_REQUESTED = 20; + INSTALL_STAGED_CANCEL_SUCCESS = 21; + INSTALL_STAGED_CANCEL_FAILURE = 22; } optional State state = 6; // Possible experiment ids for monitoring this push. @@ -5748,13 +5747,12 @@ message TrainInfo { INSTALLER_ROLLBACK_BOOT_TRIGGERED_FAILURE = 14; INSTALLER_ROLLBACK_SUCCESS = 15; INSTALLER_ROLLBACK_FAILURE = 16; - INSTALLER_ROLLBACK_CANCEL_STAGED_REMOVE_FROM_QUEUE = 17; - INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_INITIATED = 18; - INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_SUCCESS = 19; - INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_FAILURE = 20; - INSTALL_STAGED_CANCEL_REQUESTED = 21; - INSTALL_STAGED_CANCEL_SUCCESS = 22; - INSTALL_STAGED_CANCEL_FAILURE = 23; + INSTALLER_ROLLBACK_STAGED_CANCEL_REQUESTED = 17; + INSTALLER_ROLLBACK_STAGED_CANCEL_SUCCESS = 18; + INSTALLER_ROLLBACK_STAGED_CANCEL_FAILURE = 19; + INSTALL_STAGED_CANCEL_REQUESTED = 20; + INSTALL_STAGED_CANCEL_SUCCESS = 21; + INSTALL_STAGED_CANCEL_FAILURE = 22; } optional Status status = 4; } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 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/Notification.java b/core/java/android/app/Notification.java index 0ab1a85372f5..a90185cf1468 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -618,9 +618,11 @@ public class Notification implements Parcelable public static final int FLAG_CAN_COLORIZE = 0x00000800; /** - * Bit to be bitswised-ored into the {@link #flags} field that should be - * set if this notification can be shown as a bubble. - * @hide + * Bit to be bitswised-ored into the {@link #flags} field that should be set if this + * notification is showing as a bubble. This will be set by the system if it is determined + * that your notification is allowed to be a bubble. + * + * @see {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} */ public static final int FLAG_BUBBLE = 0x00001000; @@ -3578,9 +3580,9 @@ public class Notification implements Parcelable * <p>This data will be ignored unless the notification is posted to a channel that * allows {@link NotificationChannel#canBubble() bubbles}.</p> * - * <b>Notifications with a valid and allowed bubble metadata will display in collapsed state - * outside of the notification shade on unlocked devices. When a user interacts with the - * collapsed state, the bubble intent will be invoked and displayed.</b> + * <p>Notifications allowed to bubble that have valid bubble metadata will display in + * collapsed state outside of the notification shade on unlocked devices. When a user + * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p> */ @NonNull public Builder setBubbleMetadata(@Nullable BubbleMetadata data) { diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 0bec21fcda90..dd39376f80ca 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1169,6 +1169,7 @@ public class NotificationManager { * @hide */ @SystemApi + @TestApi public boolean isNotificationAssistantAccessGranted(@NonNull ComponentName assistant) { INotificationManager service = getService(); try { @@ -1204,10 +1205,37 @@ public class NotificationManager { * @hide */ @SystemApi - public @NonNull @Adjustment.Keys List<String> getAllowedAssistantCapabilities() { + @TestApi + public @NonNull @Adjustment.Keys List<String> getAllowedAssistantAdjustments() { + INotificationManager service = getService(); + try { + return service.getAllowedAssistantAdjustments(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @TestApi + public void allowAssistantAdjustment(String capability) { INotificationManager service = getService(); try { - return service.getAllowedAssistantCapabilities(mContext.getOpPackageName()); + service.allowAssistantAdjustment(capability); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @TestApi + public void disallowAssistantAdjustment(String capability) { + INotificationManager service = getService(); + try { + service.disallowAssistantAdjustment(capability); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1310,6 +1338,7 @@ public class NotificationManager { * @hide */ @SystemApi + @TestApi public void setNotificationAssistantAccessGranted(@Nullable ComponentName assistant, boolean granted) { INotificationManager service = getService(); @@ -1332,6 +1361,7 @@ public class NotificationManager { /** @hide */ @SystemApi + @TestApi public @Nullable ComponentName getAllowedNotificationAssistant() { INotificationManager service = getService(); try { diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 791c55196ecf..00f1e43c8493 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -3398,7 +3398,7 @@ public abstract class ContentResolver implements ContentInterface { * * @param mimeType Valid, concrete MIME type. */ - public final @NonNull TypeInfo getTypeInfo(@NonNull String mimeType) { + public final @NonNull MimeTypeInfo getTypeInfo(@NonNull String mimeType) { Objects.requireNonNull(mimeType); return MimeIconUtils.getTypeInfo(mimeType); } @@ -3407,13 +3407,13 @@ public abstract class ContentResolver implements ContentInterface { * Detailed description of a specific MIME type, including an icon and label * that describe the type. */ - public static final class TypeInfo { + public static final class MimeTypeInfo { private final Icon mIcon; private final CharSequence mLabel; private final CharSequence mContentDescription; /** {@hide} */ - public TypeInfo(@NonNull Icon icon, @NonNull CharSequence label, + public MimeTypeInfo(@NonNull Icon icon, @NonNull CharSequence label, @NonNull CharSequence contentDescription) { mIcon = Objects.requireNonNull(icon); mLabel = Objects.requireNonNull(label); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 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 2b23e7a2a633..b2b4e0e2edfb 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -591,8 +591,6 @@ public class PackageParser { */ public interface Callback { boolean hasFeature(String feature); - String[] getOverlayPaths(String targetPackageName, String targetPath); - String[] getOverlayApks(String targetPackageName); } /** @@ -609,14 +607,6 @@ public class PackageParser { @Override public boolean hasFeature(String feature) { return mPm.hasSystemFeature(feature); } - - @Override public String[] getOverlayPaths(String targetPackageName, String targetPath) { - return null; - } - - @Override public String[] getOverlayApks(String targetPackageName) { - return null; - } } /** @@ -1168,19 +1158,7 @@ public class PackageParser { } final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath()); - Package p = fromCacheEntry(bytes); - if (mCallback != null) { - String[] overlayApks = mCallback.getOverlayApks(p.packageName); - if (overlayApks != null && overlayApks.length > 0) { - for (String overlayApk : overlayApks) { - // If a static RRO is updated, return null. - if (!isCacheUpToDate(new File(overlayApk), cacheFile)) { - return null; - } - } - } - } - return p; + return fromCacheEntry(bytes); } catch (Throwable e) { Slog.w(TAG, "Error reading package cache: ", e); @@ -1354,7 +1332,7 @@ public class PackageParser { final Resources res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; - final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError); + final Package pkg = parseBaseApk(res, parser, flags, outError); if (pkg == null) { throw new PackageParserException(mParseError, apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]); @@ -1602,8 +1580,8 @@ 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) @@ -1640,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. } } @@ -1911,7 +1895,6 @@ public class PackageParser { * need to consider whether they should be supported by split APKs and child * packages. * - * @param apkPath The package apk file path * @param res The resources from which to resolve values * @param parser The manifest parser * @param flags Flags how to parse @@ -1921,8 +1904,7 @@ public class PackageParser { * @throws XmlPullParserException * @throws IOException */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags, + private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { final String splitName; final String pkgName; @@ -1942,15 +1924,6 @@ public class PackageParser { return null; } - if (mCallback != null) { - String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath); - if (overlayPaths != null && overlayPaths.length > 0) { - for (String overlayPath : overlayPaths) { - res.getAssets().addOverlayPath(overlayPath); - } - } - } - final Package pkg = new Package(pkgName); TypedArray sa = res.obtainAttributes(parser, diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 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/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index 014bc242e17a..3523e956656a 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -52,7 +52,7 @@ public class SQLiteQueryBuilder { private static final Pattern sLimitPattern = Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); private static final Pattern sAggregationPattern = Pattern.compile( - "(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL)\\((.+)\\)"); + "(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL|GROUP_CONCAT)\\((.+)\\)"); private Map<String, String> mProjectionMap = null; private List<Pattern> mProjectionGreylist = null; diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index a696eeb6bcc7..6c497d47c645 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -207,5 +207,22 @@ public class BiometricManager { Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected"); } } + + /** + * TODO(b/123378871): Remove when moved. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback) { + if (mService != null) { + try { + mService.registerCancellationCallback(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "registerCancellationCallback(): Service not connected"); + } + } } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 08035972a0db..1142a07bc66c 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -82,6 +82,11 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ public static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential"; + /** + * @hide + */ + public static final String KEY_FROM_CONFIRM_DEVICE_CREDENTIAL + = "from_confirm_device_credential"; /** * Error/help message will show for this amount of time. @@ -271,6 +276,17 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * TODO(123378871): Remove when moved. + * @return + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + @NonNull public Builder setFromConfirmDeviceCredential() { + mBundle.putBoolean(KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, true); + return this; + } + + /** * Creates a {@link BiometricPrompt}. * @return a {@link BiometricPrompt} * @throws IllegalArgumentException if any of the required fields are not set. @@ -494,7 +510,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public void authenticateUser(@NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, - int userId) { + int userId, + IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) { if (cancel == null) { throw new IllegalArgumentException("Must supply a cancellation signal"); } @@ -504,7 +521,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } - authenticateInternal(null /* crypto */, cancel, executor, callback, userId); + authenticateInternal(null /* crypto */, cancel, executor, callback, userId, + confirmDeviceCredentialCallback); } /** @@ -555,7 +573,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) { throw new IllegalArgumentException("Device credential not supported with crypto"); } - authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId()); + authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId(), + null /* confirmDeviceCredentialCallback */); } /** @@ -597,7 +616,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan if (callback == null) { throw new IllegalArgumentException("Must supply a callback"); } - authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId()); + authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId(), + null /* confirmDeviceCredentialCallback */); } private void cancelAuthentication() { @@ -614,7 +634,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan @NonNull CancellationSignal cancel, @NonNull @CallbackExecutor Executor executor, @NonNull AuthenticationCallback callback, - int userId) { + int userId, + IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) { try { if (cancel.isCanceled()) { Log.w(TAG, "Authentication already canceled"); @@ -629,7 +650,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan final long sessionId = crypto != null ? crypto.getOpId() : 0; if (BiometricManager.hasBiometrics(mContext)) { mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver, - mContext.getOpPackageName(), mBundle); + mContext.getOpPackageName(), mBundle, confirmDeviceCredentialCallback); } else { mExecutor.execute(() -> { callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT, diff --git a/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl b/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl new file mode 100644 index 000000000000..8b35852efd31 --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.biometrics; + +/** + * Communication channel between ConfirmDeviceCredential / ConfirmLock* and BiometricService. + * @hide + */ +interface IBiometricConfirmDeviceCredentialCallback { + // Invoked when authentication should be canceled. + oneway void cancel(); +}
\ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 4971911eb87c..90d4921c3c18 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.os.Bundle; +import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -30,8 +31,10 @@ import android.hardware.biometrics.IBiometricServiceReceiver; interface IBiometricService { // Requests authentication. The service choose the appropriate biometric to use, and show // the corresponding BiometricDialog. + // TODO(b/123378871): Remove callback when moved. void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle); + IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle, + IBiometricConfirmDeviceCredentialCallback callback); // Cancel authentication for the given sessionId void cancelAuthentication(IBinder token, String opPackageName); @@ -59,4 +62,8 @@ interface IBiometricService { void onConfirmDeviceCredentialSuccess(); // TODO(b/123378871): Remove when moved. void onConfirmDeviceCredentialError(int error, String message); + // TODO(b/123378871): Remove when moved. + // When ConfirmLock* is invoked from BiometricPrompt, it needs to register a callback so that + // it can receive the cancellation signal. + void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback); } diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index dbb894f92f55..a46c410bd55e 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -15,9 +15,16 @@ */ package android.net; +import static android.Manifest.permission.NETWORK_STACK; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.content.Context; +import java.util.ArrayList; +import java.util.Arrays; /** * * Constants for client code communicating with the network stack service. @@ -37,4 +44,52 @@ public class NetworkStack { "android.permission.MAINLINE_NETWORK_STACK"; private NetworkStack() {} + + /** + * If the NetworkStack, MAINLINE_NETWORK_STACK are not allowed for a particular process, throw a + * {@link SecurityException}. + * + * @param context {@link android.content.Context} for the process. + * + * @hide + */ + public static void checkNetworkStackPermission(final @NonNull Context context) { + checkNetworkStackPermissionOr(context); + } + + /** + * If the NetworkStack, MAINLINE_NETWORK_STACK or other specified permissions are not allowed + * for a particular process, throw a {@link SecurityException}. + * + * @param context {@link android.content.Context} for the process. + * @param otherPermissions The set of permissions that could be the candidate permissions , or + * empty string if none of other permissions needed. + * @hide + */ + public static void checkNetworkStackPermissionOr(final @NonNull Context context, + final @NonNull String... otherPermissions) { + ArrayList<String> permissions = new ArrayList<String>(Arrays.asList(otherPermissions)); + permissions.add(NETWORK_STACK); + permissions.add(PERMISSION_MAINLINE_NETWORK_STACK); + enforceAnyPermissionOf(context, permissions.toArray(new String[0])); + } + + private static void enforceAnyPermissionOf(final @NonNull Context context, + final @NonNull String... permissions) { + if (!checkAnyPermissionOf(context, permissions)) { + throw new SecurityException("Requires one of the following permissions: " + + String.join(", ", permissions) + "."); + } + } + + private static boolean checkAnyPermissionOf(final @NonNull Context context, + final @NonNull String... permissions) { + for (String permission : permissions) { + if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + return true; + } + } + return false; + } + } diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 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/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 075b650ed8f4..080ff7301ed2 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1130,7 +1130,7 @@ public class StorageManager { public @NonNull StorageVolume getStorageVolume(@NonNull Uri uri) { final String volumeName = MediaStore.getVolumeName(uri); switch (volumeName) { - case MediaStore.VOLUME_EXTERNAL: + case MediaStore.VOLUME_EXTERNAL_PRIMARY: return getPrimaryStorageVolume(); default: for (StorageVolume vol : getStorageVolumes()) { diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 225ecfa315aa..6280600823d7 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -265,8 +265,13 @@ public final class StorageVolume implements Parcelable { } /** {@hide} */ + public static @Nullable String normalizeUuid(@Nullable String fsUuid) { + return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null; + } + + /** {@hide} */ public @Nullable String getNormalizedUuid() { - return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null; + return normalizeUuid(mFsUuid); } /** diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index bda6ed19d3c1..da19d59367a0 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -102,20 +102,40 @@ public final class MediaStore { public static final @NonNull Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); /** - * Volume name used for content on "internal" storage of device. This - * volume contains media distributed with the device, such as built-in - * ringtones and wallpapers. + * Synthetic volume name that provides a view of all content across the + * "internal" storage of the device. + * <p> + * This synthetic volume provides a merged view of all media distributed + * with the device, such as built-in ringtones and wallpapers. + * <p> + * Because this is a synthetic volume, you can't insert new content into + * this volume. */ public static final String VOLUME_INTERNAL = "internal"; /** - * Volume name used for content on "external" storage of device. This only - * includes media on the primary shared storage device; the contents of any - * secondary storage devices can be obtained using - * {@link #getAllVolumeNames(Context)}. + * Synthetic volume name that provides a view of all content across the + * "external" storage of the device. + * <p> + * This synthetic volume provides a merged view of all media across all + * currently attached external storage devices. + * <p> + * Because this is a synthetic volume, you can't insert new content into + * this volume. Instead, you can insert content into a specific storage + * volume obtained from {@link #getExternalVolumeNames(Context)}. */ public static final String VOLUME_EXTERNAL = "external"; + /** + * Specific volume name that represents the primary external storage device + * at {@link Environment#getExternalStorageDirectory()}. + * <p> + * This volume may not always be available, such as when the user has + * ejected the device. You can find a list of all specific volume names + * using {@link #getExternalVolumeNames(Context)}. + */ + public static final String VOLUME_EXTERNAL_PRIMARY = "external_primary"; + /** {@hide} */ public static final String SCAN_FILE_CALL = "scan_file"; /** {@hide} */ @@ -1037,6 +1057,16 @@ public final class MediaStore { public static final String OWNER_PACKAGE_NAME = "owner_package_name"; /** + * Volume name of the specific storage device where this media item is + * persisted. The value is typically one of the volume names returned + * from {@link MediaStore#getExternalVolumeNames(Context)}. + * <p> + * This is a read-only column that is automatically computed. + */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) + public static final String VOLUME_NAME = "volume_name"; + + /** * Relative path of this media item within the storage device where it * is persisted. For example, an item stored at * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a @@ -1408,7 +1438,7 @@ public final class MediaStore { final StorageVolume sv = sm.getStorageVolume(path); if (sv != null) { if (sv.isPrimary()) { - return VOLUME_EXTERNAL; + return VOLUME_EXTERNAL_PRIMARY; } else { return checkArgumentVolumeName(sv.getNormalizedUuid()); } @@ -1710,7 +1740,7 @@ public final class MediaStore { String stringUrl = null; /* value to be returned */ try { - url = cr.insert(EXTERNAL_CONTENT_URI, values); + url = cr.insert(getContentUri(VOLUME_EXTERNAL_PRIMARY), values); if (source != null) { try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream( @@ -2676,7 +2706,13 @@ public final class MediaStore { public static final String ALBUM = "album"; /** - * The artist whose songs appear on this album + * The ID of the artist whose songs appear on this album. + */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) + public static final String ARTIST_ID = "artist_id"; + + /** + * The name of the artist whose songs appear on this album. */ @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ARTIST = "artist"; @@ -3218,22 +3254,29 @@ public final class MediaStore { } } + /** @removed */ + @Deprecated + public static @NonNull Set<String> getAllVolumeNames(@NonNull Context context) { + return getExternalVolumeNames(context); + } + /** - * Return list of all volume names currently available. This includes a - * unique name for each shared storage device that is currently mounted. + * Return list of all specific volume names that make up + * {@link #VOLUME_EXTERNAL}. This includes a unique volume name for each + * shared storage device that is currently attached, which typically + * includes {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}. * <p> - * Each name can be passed to APIs like - * {@link MediaStore.Images.Media#getContentUri(String)} to query media at - * that location. + * Each specific volume name can be passed to APIs like + * {@link MediaStore.Images.Media#getContentUri(String)} to interact with + * media on that storage device. */ - public static @NonNull Set<String> getAllVolumeNames(@NonNull Context context) { + public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) { final StorageManager sm = context.getSystemService(StorageManager.class); final Set<String> volumeNames = new ArraySet<>(); - volumeNames.add(VOLUME_INTERNAL); for (VolumeInfo vi : sm.getVolumes()) { if (vi.isVisibleForUser(UserHandle.myUserId()) && vi.isMountedReadable()) { if (vi.isPrimary()) { - volumeNames.add(VOLUME_EXTERNAL); + volumeNames.add(VOLUME_EXTERNAL_PRIMARY); } else { volumeNames.add(vi.getNormalizedFsUuid()); } @@ -3264,6 +3307,8 @@ public final class MediaStore { return volumeName; } else if (VOLUME_EXTERNAL.equals(volumeName)) { return volumeName; + } else if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) { + return volumeName; } // When not one of the well-known values above, it must be a hex UUID @@ -3279,8 +3324,9 @@ public final class MediaStore { } /** - * Return path where the given volume is mounted. Not valid for - * {@link #VOLUME_INTERNAL}. + * Return path where the given specific volume is mounted. Not valid for + * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are + * broad collections that cover many paths. * * @hide */ @@ -3291,8 +3337,12 @@ public final class MediaStore { throw new IllegalArgumentException(); } - if (VOLUME_EXTERNAL.equals(volumeName)) { - return Environment.getExternalStorageDirectory(); + switch (volumeName) { + case VOLUME_INTERNAL: + case VOLUME_EXTERNAL: + throw new FileNotFoundException(volumeName + " has no associated path"); + case VOLUME_EXTERNAL_PRIMARY: + return Environment.getExternalStorageDirectory(); } final StorageManager sm = AppGlobals.getInitialApplication() @@ -3322,23 +3372,31 @@ public final class MediaStore { throw new IllegalArgumentException(); } + final Context context = AppGlobals.getInitialApplication(); + final UserManager um = context.getSystemService(UserManager.class); + final ArrayList<File> res = new ArrayList<>(); if (VOLUME_INTERNAL.equals(volumeName)) { - addCanoncialFile(res, new File(Environment.getRootDirectory(), "media")); - addCanoncialFile(res, new File(Environment.getOemDirectory(), "media")); - addCanoncialFile(res, new File(Environment.getProductDirectory(), "media")); + addCanonicalFile(res, new File(Environment.getRootDirectory(), "media")); + addCanonicalFile(res, new File(Environment.getOemDirectory(), "media")); + addCanonicalFile(res, new File(Environment.getProductDirectory(), "media")); + } else if (VOLUME_EXTERNAL.equals(volumeName)) { + for (String exactVolume : getExternalVolumeNames(context)) { + addCanonicalFile(res, getVolumePath(exactVolume)); + } + if (um.isDemoUser()) { + addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory()); + } } else { - addCanoncialFile(res, getVolumePath(volumeName)); - final UserManager um = AppGlobals.getInitialApplication() - .getSystemService(UserManager.class); - if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) { - addCanoncialFile(res, Environment.getDataPreloadsMediaDirectory()); + addCanonicalFile(res, getVolumePath(volumeName)); + if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName) && um.isDemoUser()) { + addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory()); } } return res; } - private static void addCanoncialFile(List<File> list, File file) { + private static void addCanonicalFile(List<File> list, File file) { try { list.add(file.getCanonicalFile()); } catch (IOException e) { @@ -3376,12 +3434,12 @@ public final class MediaStore { * <p> * No other assumptions should be made about the meaning of the version. * <p> - * This method returns the version for {@link MediaStore#VOLUME_EXTERNAL}; - * to obtain a version for a different volume, use - * {@link #getVersion(Context, String)}. + * This method returns the version for + * {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}; to obtain a version for a + * different volume, use {@link #getVersion(Context, String)}. */ public static @NonNull String getVersion(@NonNull Context context) { - return getVersion(context, VOLUME_EXTERNAL); + return getVersion(context, VOLUME_EXTERNAL_PRIMARY); } /** @@ -3395,7 +3453,7 @@ public final class MediaStore { * * @param volumeName specific volume to obtain an opaque version string for. * Must be one of the values returned from - * {@link #getAllVolumeNames(Context)}. + * {@link #getExternalVolumeNames(Context)}. */ public static @NonNull String getVersion(@NonNull Context context, @NonNull String volumeName) { final ContentResolver resolver = context.getContentResolver(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 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/Adjustment.java b/core/java/android/service/notification/Adjustment.java index 8ba9a8357c65..e81ce7f85ac1 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -18,6 +18,7 @@ package android.service.notification; import android.annotation.NonNull; import android.annotation.StringDef; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Notification; import android.os.Bundle; import android.os.Parcel; @@ -40,6 +41,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi +@TestApi public final class Adjustment implements Parcelable { private final String mPackage; private final String mKey; @@ -130,6 +132,7 @@ public final class Adjustment implements Parcelable { * @hide */ @SystemApi + @TestApi public Adjustment(String pkg, String key, Bundle signals, CharSequence explanation, int user) { mPackage = pkg; mKey = key; @@ -212,6 +215,7 @@ public final class Adjustment implements Parcelable { /** @hide */ @SystemApi + @TestApi public int getUser() { return mUser; } diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index 22104b5089cf..5977bafd7cf1 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -53,4 +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 onAllowedAdjustmentsChanged(); } diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index b81725d99d2b..cafeb87691bd 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -65,6 +66,7 @@ import java.util.List; * @hide */ @SystemApi +@TestApi public abstract class NotificationAssistantService extends NotificationListenerService { private static final String TAG = "NotificationAssistants"; @@ -218,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() { } /** @@ -357,6 +359,11 @@ public abstract class NotificationAssistantService extends NotificationListenerS args.argi2 = source; mHandler.obtainMessage(MyHandler.MSG_ON_ACTION_INVOKED, args).sendToTarget(); } + + @Override + public void onAllowedAdjustmentsChanged() { + mHandler.obtainMessage(MyHandler.MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED).sendToTarget(); + } } private final class MyHandler extends Handler { @@ -367,6 +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_ALLOWED_ADJUSTMENTS_CHANGED = 8; public MyHandler(Looper looper) { super(looper, null, false); @@ -448,6 +456,10 @@ public abstract class NotificationAssistantService extends NotificationListenerS onActionInvoked(key, action, source); break; } + 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 333868a2f08a..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,6 +1400,11 @@ public abstract class NotificationListenerService extends Service { } @Override + public void onAllowedAdjustmentsChanged() { + // no-op in the listener + } + + @Override public void onNotificationChannelModification(String pkgName, UserHandle user, NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) { @@ -1675,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/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index f9b629c85fb0..1fc7f0e36095 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -416,23 +416,8 @@ public abstract class LayoutInflater { } private void initPrecompiledViews() { - // Use the device config if enabled, otherwise default to the system property. - String usePrecompiledLayout = null; - try { - usePrecompiledLayout = DeviceConfig.getProperty( - DeviceConfig.NAMESPACE_RUNTIME, - USE_PRECOMPILED_LAYOUT); - } catch (Exception e) { - // May be caused by permission errors reading the property (i.e. instant apps). - } + // Precompiled layouts are not supported in this release. boolean enabled = false; - if (TextUtils.isEmpty(usePrecompiledLayout)) { - enabled = SystemProperties.getBoolean( - USE_PRECOMPILED_LAYOUT, - false); - } else { - enabled = Boolean.parseBoolean(usePrecompiledLayout); - } initPrecompiledViews(enabled); } diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 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/android/view/contentcapture/ContentCaptureCondition.java b/core/java/android/view/contentcapture/ContentCaptureCondition.java index cf171d738524..6f9d4d30909f 100644 --- a/core/java/android/view/contentcapture/ContentCaptureCondition.java +++ b/core/java/android/view/contentcapture/ContentCaptureCondition.java @@ -54,7 +54,9 @@ public final class ContentCaptureCondition implements Parcelable { * * @param locusId id of the condition, as defined by * {@link ContentCaptureContext#getLocusId()}. - * @param flags either {@link ContentCaptureCondition#FLAG_IS_REGEX} or {@code 0}. + * @param flags either {@link ContentCaptureCondition#FLAG_IS_REGEX} (to use a regular + * expression match) or {@code 0} (in which case the {@code LocusId} must be an exact match of + * the {@code LocusId} used in the {@link ContentCaptureContext}). */ public ContentCaptureCondition(@NonNull LocusId locusId, @Flags int flags) { this.mLocusId = Preconditions.checkNotNull(locusId); diff --git a/core/java/android/view/textclassifier/ConversationAction.java b/core/java/android/view/textclassifier/ConversationAction.java index f2d878a8bf54..b8cb7bed8712 100644 --- a/core/java/android/view/textclassifier/ConversationAction.java +++ b/core/java/android/view/textclassifier/ConversationAction.java @@ -200,13 +200,11 @@ public final class ConversationAction implements Parcelable { /** * Returns the extended data related to this conversation action. * - * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should - * prefer to hold a reference to the returned bundle rather than frequently calling this - * method. + * <p><b>NOTE: </b>Do not modify this bundle. */ @NonNull public Bundle getExtras() { - return mExtras.deepCopy(); + return mExtras; } /** Builder class to construct {@link ConversationAction}. */ diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java index dc7521296e9f..eddc672dc87f 100644 --- a/core/java/android/view/textclassifier/ConversationActions.java +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -214,13 +214,11 @@ public final class ConversationActions implements Parcelable { /** * Returns the extended data related to this conversation action. * - * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should - * prefer to hold a reference to the returned bundle rather than frequently calling this - * method. + * <p><b>NOTE: </b>Do not modify this bundle. */ @NonNull public Bundle getExtras() { - return mExtras.deepCopy(); + return mExtras; } /** Builder class to construct a {@link Message} */ diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 9ede8fbd176e..4c4cb55cd3a0 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -265,13 +265,11 @@ public final class TextClassification implements Parcelable { /** * Returns the extended data. * - * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should - * prefer to hold a reference to the returned bundle rather than frequently calling this - * method. + * <p><b>NOTE: </b>Do not modify this bundle. */ @NonNull public Bundle getExtras() { - return mExtras.deepCopy(); + return mExtras; } @Override @@ -635,13 +633,11 @@ public final class TextClassification implements Parcelable { /** * Returns the extended data. * - * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should - * prefer to hold a reference to the returned bundle rather than frequently calling this - * method. + * <p><b>NOTE: </b>Do not modify this bundle. */ @NonNull public Bundle getExtras() { - return mExtras.deepCopy(); + return mExtras; } /** diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index cde27a08fc79..c815f63b1708 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -125,13 +125,11 @@ public final class TextLinks implements Parcelable { /** * Returns the extended data. * - * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should - * prefer to hold a reference to the returned bundle rather than frequently calling this - * method. + * <p><b>NOTE: </b>Do not modify this bundle. */ @NonNull public Bundle getExtras() { - return mExtras.deepCopy(); + return mExtras; } /** @@ -413,13 +411,11 @@ public final class TextLinks implements Parcelable { /** * Returns the extended data. * - * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should - * prefer to hold a reference to the returned bundle rather than frequently calling this - * method. + * <p><b>NOTE: </b>Do not modify this bundle. */ @NonNull public Bundle getExtras() { - return mExtras.deepCopy(); + return mExtras; } /** diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index 52989397e473..e378e65aa29e 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -112,13 +112,11 @@ public final class TextSelection implements Parcelable { /** * Returns the extended data. * - * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should - * prefer to hold a reference to the returned bundle rather than frequently calling this - * method. + * <p><b>NOTE: </b>Do not modify this bundle. */ @NonNull public Bundle getExtras() { - return mExtras.deepCopy(); + return mExtras; } @Override @@ -296,13 +294,11 @@ public final class TextSelection implements Parcelable { /** * Returns the extended data. * - * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should - * prefer to hold a reference to the returned bundle rather than frequently calling this - * method. + * <p><b>NOTE: </b>Do not modify this bundle. */ @NonNull public Bundle getExtras() { - return mExtras.deepCopy(); + return mExtras; } /** diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 51303f70f878..eeca73234c30 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2592,10 +2592,8 @@ public class ChooserActivity extends ResolverActivity { if (startType != lastStartType || rowPosition == getContentPreviewRowCount()) { row.setBackground(mChooserRowLayer); - setVertPadding(row, 0, 0); } else { row.setBackground(null); - setVertPadding(row, 0, 0); } int columnCount = holder.getColumnCount(); @@ -2642,10 +2640,6 @@ public class ChooserActivity extends ResolverActivity { } } - private void setVertPadding(ViewGroup row, int top, int bottom) { - row.setPadding(row.getPaddingLeft(), top, row.getPaddingRight(), bottom); - } - int getFirstRowPosition(int row) { row -= getContentPreviewRowCount(); diff --git a/core/java/com/android/internal/colorextraction/drawable/GradientDrawable.java b/core/java/com/android/internal/colorextraction/drawable/ScrimDrawable.java index bf151c39271a..7bd7acfab9f9 100644 --- a/core/java/com/android/internal/colorextraction/drawable/GradientDrawable.java +++ b/core/java/com/android/internal/colorextraction/drawable/ScrimDrawable.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.internal.colorextraction.drawable; @@ -21,64 +21,42 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.RadialGradient; -import android.graphics.Rect; -import android.graphics.Shader; import android.graphics.Xfermode; import android.graphics.drawable.Drawable; import android.view.animation.DecelerateInterpolator; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.graphics.ColorUtils; /** - * Draws a gradient based on a Palette + * Drawable used on SysUI scrims. */ -public class GradientDrawable extends Drawable { - private static final String TAG = "GradientDrawable"; - - private static final float CENTRALIZED_CIRCLE_1 = -2; - private static final int GRADIENT_RADIUS = 480; // in dp +public class ScrimDrawable extends Drawable { + private static final String TAG = "ScrimDrawable"; private static final long COLOR_ANIMATION_DURATION = 2000; - private int mAlpha = 255; - - private float mDensity; private final Paint mPaint; - private final Rect mWindowBounds; - private final Splat mSplat; - + private int mAlpha = 255; private int mMainColor; - private int mSecondaryColor; private ValueAnimator mColorAnimation; private int mMainColorTo; - private int mSecondaryColorTo; - - public GradientDrawable(@NonNull Context context) { - mDensity = context.getResources().getDisplayMetrics().density; - mSplat = new Splat(0.50f, 1.00f, GRADIENT_RADIUS, CENTRALIZED_CIRCLE_1); - mWindowBounds = new Rect(); + public ScrimDrawable() { mPaint = new Paint(); mPaint.setStyle(Paint.Style.FILL); } - public void setColors(@NonNull ColorExtractor.GradientColors colors) { - setColors(colors.getMainColor(), colors.getSecondaryColor(), true); - } - - public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) { - setColors(colors.getMainColor(), colors.getSecondaryColor(), animated); - } - - public void setColors(int mainColor, int secondaryColor, boolean animated) { - if (mainColor == mMainColorTo && secondaryColor == mSecondaryColorTo) { + /** + * Sets the background color. + * @param mainColor the color. + * @param animated if transition should be interpolated. + */ + public void setColor(int mainColor, boolean animated) { + if (mainColor == mMainColorTo) { return; } @@ -87,19 +65,15 @@ public class GradientDrawable extends Drawable { } mMainColorTo = mainColor; - mSecondaryColorTo = mainColor; if (animated) { final int mainFrom = mMainColor; - final int secFrom = mSecondaryColor; ValueAnimator anim = ValueAnimator.ofFloat(0, 1); anim.setDuration(COLOR_ANIMATION_DURATION); anim.addUpdateListener(animation -> { float ratio = (float) animation.getAnimatedValue(); mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio); - mSecondaryColor = ColorUtils.blendARGB(secFrom, secondaryColor, ratio); - buildPaints(); invalidateSelf(); }); anim.addListener(new AnimatorListenerAdapter() { @@ -115,8 +89,6 @@ public class GradientDrawable extends Drawable { mColorAnimation = anim; } else { mMainColor = mainColor; - mSecondaryColor = secondaryColor; - buildPaints(); invalidateSelf(); } } @@ -125,7 +97,6 @@ public class GradientDrawable extends Drawable { public void setAlpha(int alpha) { if (alpha != mAlpha) { mAlpha = alpha; - mPaint.setAlpha(mAlpha); invalidateSelf(); } } @@ -156,73 +127,15 @@ public class GradientDrawable extends Drawable { return PixelFormat.TRANSLUCENT; } - public void setScreenSize(int width, int height) { - mWindowBounds.set(0, 0, width, height); - setBounds(0, 0, width, height); - buildPaints(); - } - - private void buildPaints() { - Rect bounds = mWindowBounds; - if (bounds.width() == 0) { - return; - } - - float w = bounds.width(); - float h = bounds.height(); - - float x = mSplat.x * w; - float y = mSplat.y * h; - - float radius = mSplat.radius * mDensity; - - // When we have only a single alpha gradient, we increase quality - // (avoiding banding) by merging the background solid color into - // the gradient directly - RadialGradient radialGradient = new RadialGradient(x, y, radius, - mSecondaryColor, mMainColor, Shader.TileMode.CLAMP); - mPaint.setShader(radialGradient); - } - @Override public void draw(@NonNull Canvas canvas) { - Rect bounds = mWindowBounds; - if (bounds.width() == 0) { - throw new IllegalStateException("You need to call setScreenSize before drawing."); - } - - // Splat each gradient - float w = bounds.width(); - float h = bounds.height(); - - float x = mSplat.x * w; - float y = mSplat.y * h; - - float radius = Math.max(w, h); - canvas.drawRect(x - radius, y - radius, x + radius, y + radius, mPaint); + mPaint.setColor(mMainColor); + mPaint.setAlpha(mAlpha); + canvas.drawRect(getBounds(), mPaint); } @VisibleForTesting public int getMainColor() { return mMainColor; } - - @VisibleForTesting - public int getSecondaryColor() { - return mSecondaryColor; - } - - static final class Splat { - final float x; - final float y; - final float radius; - final float colorIndex; - - Splat(float x, float y, float radius, float colorIndex) { - this.x = x; - this.y = y; - this.radius = radius; - this.colorIndex = colorIndex; - } - } -}
\ No newline at end of file +} diff --git a/core/java/com/android/internal/colorextraction/types/Tonal.java b/core/java/com/android/internal/colorextraction/types/Tonal.java index b9aab21b1e4c..d2e71c85e7f9 100644 --- a/core/java/com/android/internal/colorextraction/types/Tonal.java +++ b/core/java/com/android/internal/colorextraction/types/Tonal.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WallpaperColors; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Color; import android.util.Log; import android.util.MathUtils; @@ -51,11 +52,13 @@ public class Tonal implements ExtractionType { private static final boolean DEBUG = true; - public static final int MAIN_COLOR_LIGHT = 0xffe0e0e0; - public static final int MAIN_COLOR_DARK = 0xff212121; + public static final int MAIN_COLOR_LIGHT = 0xffdadce0; + public static final int MAIN_COLOR_DARK = 0xff202124; + public static final int MAIN_COLOR_REGULAR = 0xff000000; private final TonalPalette mGreyPalette; private final ArrayList<TonalPalette> mTonalPalettes; + private final Context mContext; // Temporary variable to avoid allocations private float[] mTmpHSL = new float[3]; @@ -64,6 +67,7 @@ public class Tonal implements ExtractionType { ConfigParser parser = new ConfigParser(context); mTonalPalettes = parser.getTonalPalettes(); + mContext = context; mGreyPalette = mTonalPalettes.get(0); mTonalPalettes.remove(0); @@ -247,7 +251,20 @@ public class Tonal implements ExtractionType { boolean light = inWallpaperColors != null && (inWallpaperColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0; - final int color = light ? MAIN_COLOR_LIGHT : MAIN_COLOR_DARK; + boolean dark = inWallpaperColors != null + && (inWallpaperColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) + != 0; + final int color; + final boolean inNightMode = (mContext.getResources().getConfiguration().uiMode + & android.content.res.Configuration.UI_MODE_NIGHT_MASK) + == Configuration.UI_MODE_NIGHT_YES; + if (light) { + color = MAIN_COLOR_LIGHT; + } else if (dark || inNightMode) { + color = MAIN_COLOR_DARK; + } else { + color = MAIN_COLOR_REGULAR; + } final float[] hsl = new float[3]; ColorUtils.colorToHSL(color, hsl); 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/java/com/android/internal/util/MimeIconUtils.java b/core/java/com/android/internal/util/MimeIconUtils.java index 0b5fa6d4538a..8523b4ea9b43 100644 --- a/core/java/com/android/internal/util/MimeIconUtils.java +++ b/core/java/com/android/internal/util/MimeIconUtils.java @@ -18,7 +18,7 @@ package com.android.internal.util; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.ContentResolver.TypeInfo; +import android.content.ContentResolver.MimeTypeInfo; import android.content.res.Resources; import android.graphics.drawable.Icon; import android.text.TextUtils; @@ -34,9 +34,9 @@ import java.util.Objects; public class MimeIconUtils { @GuardedBy("sCache") - private static final ArrayMap<String, TypeInfo> sCache = new ArrayMap<>(); + private static final ArrayMap<String, MimeTypeInfo> sCache = new ArrayMap<>(); - private static TypeInfo buildTypeInfo(String mimeType, int iconId, + private static MimeTypeInfo buildTypeInfo(String mimeType, int iconId, int labelId, int extLabelId) { final Resources res = Resources.getSystem(); @@ -49,10 +49,10 @@ public class MimeIconUtils { label = res.getString(labelId); } - return new TypeInfo(Icon.createWithResource(res, iconId), label, label); + return new MimeTypeInfo(Icon.createWithResource(res, iconId), label, label); } - private static @Nullable TypeInfo buildTypeInfo(@NonNull String mimeType) { + private static @Nullable MimeTypeInfo buildTypeInfo(@NonNull String mimeType) { switch (mimeType) { case "inode/directory": case "vnd.android.document/directory": @@ -222,7 +222,7 @@ public class MimeIconUtils { } } - private static @Nullable TypeInfo buildGenericTypeInfo(@NonNull String mimeType) { + private static @Nullable MimeTypeInfo buildGenericTypeInfo(@NonNull String mimeType) { // Look for partial matches if (mimeType.startsWith("audio/")) { return buildTypeInfo(mimeType, R.drawable.ic_doc_audio, @@ -252,12 +252,12 @@ public class MimeIconUtils { R.string.mime_type_generic, R.string.mime_type_generic_ext); } - public static @NonNull TypeInfo getTypeInfo(@NonNull String mimeType) { + public static @NonNull MimeTypeInfo getTypeInfo(@NonNull String mimeType) { // Normalize MIME type mimeType = mimeType.toLowerCase(Locale.US); synchronized (sCache) { - TypeInfo res = sCache.get(mimeType); + MimeTypeInfo res = sCache.get(mimeType); if (res == null) { res = buildTypeInfo(mimeType); sCache.put(mimeType, res); diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp index 29051f14c72f..1c247cbb7743 100644 --- a/core/jni/android_hardware_camera2_DngCreator.cpp +++ b/core/jni/android_hardware_camera2_DngCreator.cpp @@ -19,6 +19,7 @@ #include <inttypes.h> #include <string.h> #include <algorithm> +#include <array> #include <memory> #include <vector> #include <cmath> @@ -976,6 +977,153 @@ static status_t generateNoiseProfile(const double* perChannelNoiseProfile, uint8 return OK; } +static void undistort(/*inout*/double& x, /*inout*/double& y, + const std::array<float, 6>& distortion, + const float cx, const float cy, const float f) { + double xp = (x - cx) / f; + double yp = (y - cy) / f; + + double x2 = xp * xp; + double y2 = yp * yp; + double r2 = x2 + y2; + double xy2 = 2.0 * xp * yp; + + const float k0 = distortion[0]; + const float k1 = distortion[1]; + const float k2 = distortion[2]; + const float k3 = distortion[3]; + const float p1 = distortion[4]; + const float p2 = distortion[5]; + + double kr = k0 + ((k3 * r2 + k2) * r2 + k1) * r2; + double xpp = xp * kr + p1 * xy2 + p2 * (r2 + 2.0 * x2); + double ypp = yp * kr + p1 * (r2 + 2.0 * y2) + p2 * xy2; + + x = xpp * f + cx; + y = ypp * f + cy; + return; +} + +static inline bool unDistortWithinPreCorrArray( + double x, double y, + const std::array<float, 6>& distortion, + const float cx, const float cy, const float f, + int preCorrW, int preCorrH) { + undistort(x, y, distortion, cx, cy, f); + if (x < 0.0 || y < 0.0 || x > preCorrW - 1 || y > preCorrH - 1) { + return false; + } + return true; +} + +static inline bool boxWithinPrecorrectionArray( + int left, int top, int right, int bottom, + const std::array<float, 6>& distortion, + const float& cx, const float& cy, const float& f, + const int& preCorrW, const int& preCorrH){ + // Top row + if (!unDistortWithinPreCorrArray(left, top, distortion, cx, cy, f, preCorrW, preCorrH)) { + return false; + } + + if (!unDistortWithinPreCorrArray(cx, top, distortion, cx, cy, f, preCorrW, preCorrH)) { + return false; + } + + if (!unDistortWithinPreCorrArray(right, top, distortion, cx, cy, f, preCorrW, preCorrH)) { + return false; + } + + // Middle row + if (!unDistortWithinPreCorrArray(left, cy, distortion, cx, cy, f, preCorrW, preCorrH)) { + return false; + } + + if (!unDistortWithinPreCorrArray(right, cy, distortion, cx, cy, f, preCorrW, preCorrH)) { + return false; + } + + // Bottom row + if (!unDistortWithinPreCorrArray(left, bottom, distortion, cx, cy, f, preCorrW, preCorrH)) { + return false; + } + + if (!unDistortWithinPreCorrArray(cx, bottom, distortion, cx, cy, f, preCorrW, preCorrH)) { + return false; + } + + if (!unDistortWithinPreCorrArray(right, bottom, distortion, cx, cy, f, preCorrW, preCorrH)) { + return false; + } + return true; +} + +static inline bool scaledBoxWithinPrecorrectionArray( + double scale/*must be <= 1.0*/, + const std::array<float, 6>& distortion, + const float cx, const float cy, const float f, + const int preCorrW, const int preCorrH){ + + double left = cx * (1.0 - scale); + double right = (preCorrW - 1) * scale + cx * (1.0 - scale); + double top = cy * (1.0 - scale); + double bottom = (preCorrH - 1) * scale + cy * (1.0 - scale); + + return boxWithinPrecorrectionArray(left, top, right, bottom, + distortion, cx, cy, f, preCorrW, preCorrH); +} + +static status_t findPostCorrectionScale( + double stepSize, double minScale, + const std::array<float, 6>& distortion, + const float cx, const float cy, const float f, + const int preCorrW, const int preCorrH, + /*out*/ double* outScale) { + if (outScale == nullptr) { + ALOGE("%s: outScale must not be null", __FUNCTION__); + return BAD_VALUE; + } + + for (double scale = 1.0; scale > minScale; scale -= stepSize) { + if (scaledBoxWithinPrecorrectionArray( + scale, distortion, cx, cy, f, preCorrW, preCorrH)) { + *outScale = scale; + return OK; + } + } + ALOGE("%s: cannot find cropping scale for lens distortion: stepSize %f, minScale %f", + __FUNCTION__, stepSize, minScale); + return BAD_VALUE; +} + +// Apply a scale factor to distortion coefficients so that the image is zoomed out and all pixels +// are sampled within the precorrection array +static void normalizeLensDistortion( + /*inout*/std::array<float, 6>& distortion, + float cx, float cy, float f, int preCorrW, int preCorrH) { + ALOGV("%s: distortion [%f, %f, %f, %f, %f, %f], (cx,cy) (%f, %f), f %f, (W,H) (%d, %d)", + __FUNCTION__, distortion[0], distortion[1], distortion[2], + distortion[3], distortion[4], distortion[5], + cx, cy, f, preCorrW, preCorrH); + + // Only update distortion coeffients if we can find a good bounding box + double scale = 1.0; + if (OK == findPostCorrectionScale(0.002, 0.5, + distortion, cx, cy, f, preCorrW, preCorrH, + /*out*/&scale)) { + ALOGV("%s: scaling distortion coefficients by %f", __FUNCTION__, scale); + // The formula: + // xc = xi * (k0 + k1*r^2 + k2*r^4 + k3*r^6) + k4 * (2*xi*yi) + k5 * (r^2 + 2*xi^2) + // To create effective zoom we want to replace xi by xi *m, yi by yi*m and r^2 by r^2*m^2 + // Factor the extra m power terms into k0~k6 + std::array<float, 6> scalePowers = {1, 3, 5, 7, 2, 2}; + for (size_t i = 0; i < 6; i++) { + distortion[i] *= pow(scale, scalePowers[i]); + } + } + return; +} + // ---------------------------------------------------------------------------- extern "C" { @@ -1086,9 +1234,9 @@ static sp<TiffWriter> DngCreator_setup(JNIEnv* env, jobject thiz, uint32_t image uint32_t pixHeight = static_cast<uint32_t>(pixelArrayEntry.data.i32[1]); if (!((imageWidth == preWidth && imageHeight == preHeight) || - (imageWidth == pixWidth && imageHeight == pixHeight))) { + (imageWidth == pixWidth && imageHeight == pixHeight))) { jniThrowException(env, "java/lang/AssertionError", - "Height and width of imate buffer did not match height and width of" + "Height and width of image buffer did not match height and width of" "either the preCorrectionActiveArraySize or the pixelArraySize."); return nullptr; } @@ -1793,7 +1941,7 @@ static sp<TiffWriter> DngCreator_setup(JNIEnv* env, jobject thiz, uint32_t image status_t err = OK; // Set up rectilinear distortion correction - float distortion[6] {1.f, 0.f, 0.f, 0.f, 0.f, 0.f}; + std::array<float, 6> distortion = {1.f, 0.f, 0.f, 0.f, 0.f, 0.f}; bool gotDistortion = false; camera_metadata_entry entry4 = @@ -1810,6 +1958,19 @@ static sp<TiffWriter> DngCreator_setup(JNIEnv* env, jobject thiz, uint32_t image results.find(ANDROID_LENS_DISTORTION); if (entry3.count == 5) { gotDistortion = true; + + + // Scale the distortion coefficients to create a zoom in warpped image so that all + // pixels are drawn within input image. + for (size_t i = 0; i < entry3.count; i++) { + distortion[i+1] = entry3.data.f[i]; + } + + // TODO b/118690688: deal with the case where RAW size != preCorrSize + if (preWidth == imageWidth && preHeight == imageHeight) { + normalizeLensDistortion(distortion, cx, cy, f, preWidth, preHeight); + } + float m_x = std::fmaxf(preWidth-1 - cx, cx); float m_y = std::fmaxf(preHeight-1 - cy, cy); float m_sq = m_x*m_x + m_y*m_y; @@ -1831,7 +1992,7 @@ static sp<TiffWriter> DngCreator_setup(JNIEnv* env, jobject thiz, uint32_t image m / f }; for (size_t i = 0; i < entry3.count; i++) { - distortion[i+1] = convCoeff[i] * entry3.data.f[i]; + distortion[i+1] *= convCoeff[i]; } } else { entry3 = results.find(ANDROID_LENS_RADIAL_DISTORTION); @@ -1859,8 +2020,8 @@ static sp<TiffWriter> DngCreator_setup(JNIEnv* env, jobject thiz, uint32_t image } } if (gotDistortion) { - err = builder.addWarpRectilinearForMetadata(distortion, preWidth, preHeight, cx, - cy); + err = builder.addWarpRectilinearForMetadata( + distortion.data(), preWidth, preHeight, cx, cy); if (err != OK) { ALOGE("%s: Could not add distortion correction.", __FUNCTION__); jniThrowRuntimeException(env, "failed to add distortion correction."); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 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-night/colors.xml b/core/res/res/values-night/colors.xml index 6bbd2587d78d..37e452d72c9a 100644 --- a/core/res/res/values-night/colors.xml +++ b/core/res/res/values-night/colors.xml @@ -28,4 +28,6 @@ <!-- The background color of a notification card. --> <color name="notification_material_background_color">@color/black</color> -</resources>
\ No newline at end of file + + <color name="chooser_row_divider">@color/list_divider_color_dark</color> +</resources> diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml index e35b750272da..98f209d3f401 100644 --- a/core/res/res/values-night/themes_device_defaults.xml +++ b/core/res/res/values-night/themes_device_defaults.xml @@ -71,4 +71,8 @@ easier. <style name="ThemeOverlay.DeviceDefault.Accent.DayNight" parent="@style/ThemeOverlay.DeviceDefault.Accent" /> + <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.ResolverCommon"> + <item name="windowLightNavigationBar">false</item> + </style> + </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 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/colors.xml b/core/res/res/values/colors.xml index e9b1bd3af0dc..ef26cd74cde0 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -215,6 +215,5 @@ <!-- Magnifier --> <color name="default_magnifier_color_overlay">#00FFFFFF</color> - <color name="chooser_row_divider">#1f000000</color> - + <color name="chooser_row_divider">@color/list_divider_color_light</color> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 0163fc0e20ce..8fcdc7b1d748 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1031,6 +1031,9 @@ <!-- Boolean indicating whether display white balance is supported. --> <bool name="config_displayWhiteBalanceAvailable">false</bool> + <!-- Boolean indicating whether display white balance should be enabled by default. --> + <bool name="config_displayWhiteBalanceEnabledDefault">false</bool> + <!-- Minimum color temperature, in Kelvin, supported by display white balance. --> <integer name="config_displayWhiteBalanceColorTemperatureMin">4000</integer> @@ -3251,6 +3254,10 @@ <!-- Controls the size of the back gesture inset. --> <dimen name="config_backGestureInset">0dp</dimen> + <!-- Controls whether the navbar needs a scrim with + {@link Window#setEnsureNavigationBarContrastWhenTransparent}. --> + <bool name="config_navBarNeedsScrim">true</bool> + <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows. These values are in DPs and will be converted to pixel sizes internally. --> <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string> diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml index a43f7529c283..f22a91ff75c1 100644 --- a/core/res/res/values/dimens_car.xml +++ b/core/res/res/values/dimens_car.xml @@ -66,9 +66,10 @@ <dimen name="car_padding_0">4dp</dimen> <dimen name="car_padding_1">8dp</dimen> <dimen name="car_padding_2">16dp</dimen> - <dimen name="car_padding_3">28dp</dimen> + <dimen name="car_padding_3">24dp</dimen> <dimen name="car_padding_4">32dp</dimen> <dimen name="car_padding_5">64dp</dimen> + <dimen name="car_padding_6">96dp</dimen> <!-- Radius --> <dimen name="car_radius_1">4dp</dimen> @@ -121,6 +122,9 @@ <!-- Dialog button end margin --> <dimen name="button_end_margin">@*android:dimen/car_padding_4</dimen> + <!-- Dialog top padding when there is no title --> + <dimen name="dialog_no_title_padding_top">@*android:dimen/car_padding_4</dimen> + <!-- Dialog start margin for text view --> <dimen name="text_view_start_margin">@*android:dimen/car_keyline_1</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index fb72da519f53..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" /> @@ -3150,7 +3151,6 @@ <java-symbol type="bool" name="config_setColorTransformAccelerated" /> <java-symbol type="bool" name="config_setColorTransformAcceleratedPerLayer" /> - <java-symbol type="bool" name="config_displayWhiteBalanceAvailable" /> <java-symbol type="bool" name="config_nightDisplayAvailable" /> <java-symbol type="bool" name="config_allowDisablingAssistDisclosure" /> <java-symbol type="integer" name="config_defaultNightDisplayAutoMode" /> @@ -3162,8 +3162,8 @@ <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficients" /> <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficientsNative" /> <java-symbol type="array" name="config_availableColorModes" /> - <java-symbol type="bool" name="config_displayWhiteBalanceAvailable" /> + <java-symbol type="bool" name="config_displayWhiteBalanceEnabledDefault" /> <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureMin" /> <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureMax" /> <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureDefault" /> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 9f20ee6e19a0..78a8db420a67 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -1658,7 +1658,7 @@ easier. <style name="Theme.DeviceDefault.DayNight" parent="Theme.DeviceDefault.Light" /> <!-- Theme used for the intent picker activity. --> - <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.DayNight"> + <style name="Theme.DeviceDefault.ResolverCommon" parent="Theme.DeviceDefault.DayNight"> <item name="windowEnterTransition">@empty</item> <item name="windowExitTransition">@empty</item> <item name="windowIsTranslucent">true</item> @@ -1670,6 +1670,12 @@ easier. <item name="colorControlActivated">?attr/colorControlHighlight</item> <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item> <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item> + <item name="navigationBarColor">?attr/colorBackgroundFloating</item> + <item name="navigationBarDividerColor">@color/chooser_row_divider</item> + </style> + + <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.ResolverCommon"> + <item name="windowLightNavigationBar">true</item> </style> <!-- @hide DeviceDefault themes for the autofill FillUi --> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index ed198e60902b..e4a93e740c4c 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -309,6 +309,7 @@ applications that come with the platform <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/> <permission name="android.permission.SET_WALLPAPER" /> <permission name="android.permission.SET_WALLPAPER_COMPONENT" /> + <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/libs/hwui/tests/unit/CommonPoolTests.cpp b/libs/hwui/tests/unit/CommonPoolTests.cpp index c564ed632786..70a5f5acbb6e 100644 --- a/libs/hwui/tests/unit/CommonPoolTests.cpp +++ b/libs/hwui/tests/unit/CommonPoolTests.cpp @@ -135,4 +135,48 @@ TEST(CommonPool, fullQueue) { for (auto& f : futures) { f.get(); } +} + +class ObjectTracker { + static std::atomic_int sGlobalCount; + +public: + ObjectTracker() { + sGlobalCount++; + } + ObjectTracker(const ObjectTracker&) { + sGlobalCount++; + } + ObjectTracker(ObjectTracker&&) { + sGlobalCount++; + } + ~ObjectTracker() { + sGlobalCount--; + } + + static int count() { return sGlobalCount.load(); } +}; + +std::atomic_int ObjectTracker::sGlobalCount{0}; + +TEST(CommonPool, asyncLifecycleCheck) { + ASSERT_EQ(0, ObjectTracker::count()); + { + ObjectTracker obj; + ASSERT_EQ(1, ObjectTracker::count()); + EXPECT_LT(1, CommonPool::async([obj] { return ObjectTracker::count(); }).get()); + } + CommonPool::waitForIdle(); + ASSERT_EQ(0, ObjectTracker::count()); +} + +TEST(CommonPool, syncLifecycleCheck) { + ASSERT_EQ(0, ObjectTracker::count()); + { + ObjectTracker obj; + ASSERT_EQ(1, ObjectTracker::count()); + EXPECT_LT(1, CommonPool::runSync([obj] { return ObjectTracker::count(); })); + } + CommonPool::waitForIdle(); + ASSERT_EQ(0, ObjectTracker::count()); }
\ No newline at end of file diff --git a/libs/hwui/thread/CommonPool.cpp b/libs/hwui/thread/CommonPool.cpp index 7f94a152cf8d..d011bdfe945e 100644 --- a/libs/hwui/thread/CommonPool.cpp +++ b/libs/hwui/thread/CommonPool.cpp @@ -49,9 +49,13 @@ CommonPool::CommonPool() { } } -void CommonPool::post(Task&& task) { +CommonPool& CommonPool::instance() { static CommonPool pool; - pool.enqueue(std::move(task)); + return pool; +} + +void CommonPool::post(Task&& task) { + instance().enqueue(std::move(task)); } void CommonPool::enqueue(Task&& task) { @@ -86,5 +90,18 @@ void CommonPool::workerLoop() { } } +void CommonPool::waitForIdle() { + instance().doWaitForIdle(); +} + +void CommonPool::doWaitForIdle() { + std::unique_lock lock(mLock); + while (mWaitingThreads != THREAD_COUNT) { + lock.unlock(); + usleep(100); + lock.lock(); + } +} + } // namespace uirenderer } // namespace android
\ No newline at end of file diff --git a/libs/hwui/thread/CommonPool.h b/libs/hwui/thread/CommonPool.h index aef2990d6343..7603eeef4692 100644 --- a/libs/hwui/thread/CommonPool.h +++ b/libs/hwui/thread/CommonPool.h @@ -57,11 +57,13 @@ public: mHead = newHead; } - constexpr T&& pop() { + constexpr T pop() { LOG_ALWAYS_FATAL_IF(mTail == mHead, "empty"); int index = mTail; mTail = (mTail + 1) % SIZE; - return std::move(mBuffer[index]); + T ret = std::move(mBuffer[index]); + mBuffer[index] = nullptr; + return ret; } private: @@ -95,11 +97,17 @@ public: return task.get_future().get(); }; + // For testing purposes only, blocks until all worker threads are parked. + static void waitForIdle(); + private: + static CommonPool& instance(); + CommonPool(); ~CommonPool() {} void enqueue(Task&&); + void doWaitForIdle(); void workerLoop(); diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index d14116f7b555..39740bd46f9f 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -60,6 +60,9 @@ SkColorType PixelFormatToColorType(android::PixelFormat format) { } sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { + if (dataspace == HAL_DATASPACE_UNKNOWN) { + return SkColorSpace::MakeSRGB(); + } skcms_Matrix3x3 gamut; switch (dataspace & HAL_DATASPACE_STANDARD_MASK) { diff --git a/media/Android.bp b/media/Android.bp index 34801813ee87..8746046c220d 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -24,6 +24,10 @@ java_library { "mediaplayer2-protos", ], + permitted_packages: [ + "android.media", + ], + installable: true, // Make sure that the implementaion only relies on SDK or system APIs. diff --git a/media/OWNERS b/media/OWNERS index 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 87035da3d513..72c18f6148f0 100644 --- a/media/apex/java/android/media/MediaPlayer2.java +++ b/media/apex/java/android/media/MediaPlayer2.java @@ -31,6 +31,7 @@ import android.media.MediaDrm.KeyRequest; import android.media.MediaPlayer2.DrmInfo; import android.media.MediaPlayer2Proto.PlayerMessage; import android.media.MediaPlayer2Proto.Value; +import android.media.protobuf.InvalidProtocolBufferException; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; @@ -46,7 +47,6 @@ import android.view.Surface; import android.view.SurfaceHolder; import com.android.internal.annotations.GuardedBy; -import com.android.media.protobuf.InvalidProtocolBufferException; import java.io.ByteArrayOutputStream; import java.io.File; @@ -546,7 +546,7 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { @Override void process() { if (getState() == PLAYER_STATE_PLAYING) { - pause(); + native_pause(); } playNextDataSource(); } diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 3a336780b465..9d4bce7b4301 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -893,7 +893,7 @@ public final class AudioAttributes implements Parcelable { * @param muted true to force muting haptic channels. * @return the same Builder instance. */ - public Builder setMuteHapticChannels(boolean muted) { + public @NonNull Builder setHapticChannelsMuted(boolean muted) { mMuteHapticChannels = muted; return this; } diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 325e227e6f93..790e189e9109 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -25,7 +25,6 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.os.Binder; -import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -1662,7 +1661,7 @@ public class AudioTrack extends PlayerBase * a better solution. * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 112561552) + @UnsupportedAppUsage(trackingBug = 130237544) public int getLatency() { return native_get_latency(); } diff --git a/media/proto/jarjar-rules.txt b/media/proto/jarjar-rules.txt index bfb0b2782486..e73f86dddac1 100644 --- a/media/proto/jarjar-rules.txt +++ b/media/proto/jarjar-rules.txt @@ -1,2 +1,2 @@ -rule com.google.protobuf.** com.android.media.protobuf.@1 +rule com.google.protobuf.** android.media.protobuf.@1 diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml index a5286364dc26..355bdd8dfc98 100644 --- a/packages/CaptivePortalLogin/AndroidManifest.xml +++ b/packages/CaptivePortalLogin/AndroidManifest.xml @@ -18,7 +18,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.captiveportallogin" - android:versionCode="200000000" + android:versionCode="210000000" android:versionName="Q-initial"> <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" /> diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java index 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/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml index 45a59a32ec17..73bfd3010983 100644 --- a/packages/ExtServices/AndroidManifest.xml +++ b/packages/ExtServices/AndroidManifest.xml @@ -17,7 +17,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" package="android.ext.services" - android:versionCode="200000000" + android:versionCode="210000000" android:versionName="1" coreApp="true"> diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index b4588e01dc79..ac05c44c1803 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -19,7 +19,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.networkstack" android:sharedUserId="android.uid.networkstack" - android:versionCode="200000000" + android:versionCode="210000000" android:versionName="29 system image" > diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java index 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/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java index 74057be8434b..ff40d8e00603 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java @@ -20,14 +20,12 @@ import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; import android.location.SettingInjectorService; import android.os.Bundle; import android.os.Handler; @@ -37,9 +35,9 @@ import android.os.Messenger; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; -import android.util.IconDrawableFactory; import android.util.Log; import android.util.Xml; @@ -56,8 +54,8 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashSet; -import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -157,22 +155,8 @@ public class SettingsInjector { * Adds the InjectedSetting information to a Preference object */ private void populatePreference(Preference preference, InjectedSetting setting) { - final PackageManager pm = mContext.getPackageManager(); - Drawable appIcon = null; - try { - final PackageItemInfo itemInfo = new PackageItemInfo(); - itemInfo.icon = setting.iconId; - itemInfo.packageName = setting.packageName; - final ApplicationInfo appInfo = pm.getApplicationInfo(setting.packageName, - PackageManager.GET_META_DATA); - appIcon = IconDrawableFactory.newInstance(mContext) - .getBadgedIcon(itemInfo, appInfo, setting.mUserHandle.getIdentifier()); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Can't get ApplicationInfo for " + setting.packageName, e); - } preference.setTitle(setting.title); preference.setSummary(R.string.loading_injected_setting_summary); - preference.setIcon(appIcon); preference.setOnPreferenceClickListener(new ServiceSettingClickedListener(setting)); } @@ -182,13 +166,15 @@ public class SettingsInjector { * @param profileId Identifier of the user/profile to obtain the injected settings for or * UserHandle.USER_CURRENT for all profiles associated with current user. */ - public List<Preference> getInjectedSettings(Context prefContext, final int profileId) { + public Map<Integer, List<Preference>> getInjectedSettings(Context prefContext, + final int profileId) { final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); final List<UserHandle> profiles = um.getUserProfiles(); - ArrayList<Preference> prefs = new ArrayList<>(); + final ArrayMap<Integer, List<Preference>> result = new ArrayMap<>(); mSettings.clear(); for (UserHandle userHandle : profiles) { if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) { + final List<Preference> prefs = new ArrayList<>(); Iterable<InjectedSetting> settings = getSettings(userHandle); for (InjectedSetting setting : settings) { Preference preference = createPreference(prefContext, setting); @@ -196,12 +182,14 @@ public class SettingsInjector { prefs.add(preference); mSettings.add(new Setting(setting, preference)); } + if (!prefs.isEmpty()) { + result.put(userHandle.getIdentifier(), prefs); + } } } reloadStatusMessages(); - - return prefs; + return result; } /** @@ -303,28 +291,6 @@ public class SettingsInjector { } /** - * Checks wheteher there is any preference that other apps have injected. - * - * @param profileId Identifier of the user/profile to obtain the injected settings for or - * UserHandle.USER_CURRENT for all profiles associated with current user. - */ - public boolean hasInjectedSettings(final int profileId) { - final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - final List<UserHandle> profiles = um.getUserProfiles(); - final int profileCount = profiles.size(); - for (int i = 0; i < profileCount; ++i) { - final UserHandle userHandle = profiles.get(i); - if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) { - Iterable<InjectedSetting> settings = getSettings(userHandle); - for (InjectedSetting setting : settings) { - return true; - } - } - } - return false; - } - - /** * Reloads the status messages for all the preference items. */ public void reloadStatusMessages() { diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 3a9a99385c28..314b74a69147 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -154,6 +154,7 @@ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> <!-- Permission needed to rename bugreport notifications (so they're not shown as Shell) --> <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> + <uses-permission android:name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> <!-- Permission needed to hold a wakelock in dumpstate.cpp (drop_root_user()) --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- Permission needed to enable/disable overlays --> diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivity.java index cec97ab49954..79c691cf45e1 100644 --- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivity.java @@ -87,13 +87,13 @@ import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.utilities.Utilities; import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.shared.recents.model.Task; import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.utilities.Utilities; import com.android.systemui.recents.views.RecentsView; import com.android.systemui.recents.views.SystemBarScrimViews; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; import java.io.FileDescriptor; @@ -370,8 +370,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD MetricsLogger.visible(this, MetricsEvent.OVERVIEW_ACTIVITY); // Getting system scrim colors ignoring wallpaper visibility since it should never be grey. - ColorExtractor.GradientColors systemColors = mColorExtractor.getColors( - ColorExtractor.TYPE_DARK, WallpaperManager.FLAG_SYSTEM, true); + ColorExtractor.GradientColors systemColors = mColorExtractor.getNeutralColors(); // We don't want to interpolate colors because we're defining the initial state. // Gradient should be set/ready when you open "Recents". mRecentsView.setScrimColors(systemColors, false); @@ -397,9 +396,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { // Recents doesn't care about the wallpaper being visible or not, it always // wants to scrim with wallpaper colors - ColorExtractor.GradientColors colors = mColorExtractor.getColors( - WallpaperManager.FLAG_SYSTEM, - ColorExtractor.TYPE_DARK, true /* ignoreVis */); + ColorExtractor.GradientColors colors = mColorExtractor.getNeutralColors(); boolean darkText = colors.supportsDarkText(); if (darkText != mUsingDarkText) { mUsingDarkText = darkText; diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java index 8723fb9ea7c8..e60ffba435ff 100644 --- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java @@ -34,7 +34,6 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.os.IRemoteCallback; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; @@ -50,7 +49,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.colorextraction.drawable.GradientDrawable; +import com.android.internal.colorextraction.drawable.ScrimDrawable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; @@ -87,9 +86,9 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.shared.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.utilities.Utilities; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; import com.android.systemui.shared.recents.view.RecentsTransition; @@ -134,7 +133,7 @@ public class RecentsView extends FrameLayout { private int mDividerSize; private float mBusynessFactor; - private GradientDrawable mBackgroundScrim; + private ScrimDrawable mBackgroundScrim; private ColorDrawable mMultiWindowBackgroundScrim; private ValueAnimator mBackgroundScrimAnimator; private Point mTmpDisplaySize = new Point(); @@ -172,7 +171,7 @@ public class RecentsView extends FrameLayout { mDividerSize = ssp.getDockedDividerSize(context); mTouchHandler = new RecentsViewTouchHandler(this); mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f); - mBackgroundScrim = new GradientDrawable(context); + mBackgroundScrim = new ScrimDrawable(); mMultiWindowBackgroundScrim = new ColorDrawable(); LayoutInflater inflater = LayoutInflater.from(context); @@ -395,7 +394,7 @@ public class RecentsView extends FrameLayout { * @param animated Interpolate colors if true. */ public void setScrimColors(ColorExtractor.GradientColors scrimColors, boolean animated) { - mBackgroundScrim.setColors(scrimColors, animated); + mBackgroundScrim.setColor(scrimColors.getMainColor(), animated); int alpha = mMultiWindowBackgroundScrim.getAlpha(); mMultiWindowBackgroundScrim.setColor(scrimColors.getMainColor()); mMultiWindowBackgroundScrim.setAlpha(alpha); @@ -467,7 +466,6 @@ public class RecentsView extends FrameLayout { // Needs to know the screen size since the gradient never scales up or down // even when bounds change. mContext.getDisplay().getRealSize(mTmpDisplaySize); - mBackgroundScrim.setScreenSize(mTmpDisplaySize.x, mTmpDisplaySize.y); mBackgroundScrim.setBounds(left, top, right, bottom); mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index 42600c157387..834f4fc75dce 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -117,6 +117,7 @@ public interface QSTile { public String expandedAccessibilityClassName; public SlashState slash; public boolean handlesLongClick = true; + public boolean showRippleEffect = true; public boolean copyTo(State other) { if (other == null) throw new IllegalArgumentException(); @@ -135,7 +136,8 @@ public interface QSTile { || !Objects.equals(other.isTransient, isTransient) || !Objects.equals(other.dualTarget, dualTarget) || !Objects.equals(other.slash, slash) - || !Objects.equals(other.handlesLongClick, handlesLongClick); + || !Objects.equals(other.handlesLongClick, handlesLongClick) + || !Objects.equals(other.showRippleEffect, showRippleEffect); other.icon = icon; other.iconSupplier = iconSupplier; other.label = label; @@ -149,6 +151,7 @@ public interface QSTile { other.isTransient = isTransient; other.slash = slash != null ? slash.copy() : null; other.handlesLongClick = handlesLongClick; + other.showRippleEffect = showRippleEffect; return changed; } diff --git a/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml b/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml new file mode 100644 index 000000000000..fe1bb265880c --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml @@ -0,0 +1,31 @@ +<!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="22" + android:viewportHeight="17" + android:width="22dp" + android:height="17dp"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M1.22,8.49l0.43-4.96h4.33v1.17H2.67L2.44,7.41c0.41-0.29,0.85-0.43,1.33-0.43c0.77,0,1.38,0.3,1.83,0.9 s0.66,1.41,0.66,2.43c0,1.03-0.24,1.84-0.72,2.43s-1.14,0.88-1.98,0.88c-0.75,0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07,0.57,0.23,1,0.49,1.29c0.26,0.29,0.59,0.43,1.01,0.43c0.47,0,0.84-0.2,1.1-0.61c0.26-0.41,0.4-0.96,0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.96,8.11,3.47,8.11c-0.4,0-0.72,0.1-0.96,0.31L2.19,8.75L1.22,8.49z" /> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M14.14,12.24l-0.22,0.27c-0.63,0.73-1.55,1.1-2.76,1.1c-1.08,0-1.92-0.36-2.53-1.07c-0.61-0.71-0.93-1.72-0.94-3.02V7.56 c0-1.39,0.28-2.44,0.84-3.13c0.56-0.7,1.39-1.04,2.51-1.04c0.95,0,1.69,0.26,2.23,0.79c0.54,0.53,0.83,1.28,0.89,2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45c-0.29-0.35-0.74-0.52-1.34-0.52c-0.72,0-1.24,0.23-1.57,0.7C9.14,5.63,8.96,6.37,8.95,7.4v2.03 c0,1,0.19,1.77,0.57,2.31c0.38,0.54,0.93,0.8,1.65,0.8c0.67,0,1.19-0.16,1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z" /> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M20.96,8.88h-3.52v3.53h4.1v1.07h-5.35V3.52h5.28V4.6h-4.03V7.8h3.52V8.88z" /> + +</vector> diff --git a/packages/SystemUI/res/values-mcc310-mnc030/config.xml b/packages/SystemUI/res/values-mcc310-mnc030/config.xml new file mode 100644 index 000000000000..26b9192e0cc3 --- /dev/null +++ b/packages/SystemUI/res/values-mcc310-mnc030/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> +</resources> + diff --git a/packages/SystemUI/res/values-mcc310-mnc070/config.xml b/packages/SystemUI/res/values-mcc310-mnc070/config.xml new file mode 100644 index 000000000000..26b9192e0cc3 --- /dev/null +++ b/packages/SystemUI/res/values-mcc310-mnc070/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> +</resources> + diff --git a/packages/SystemUI/res/values-mcc310-mnc170/config.xml b/packages/SystemUI/res/values-mcc310-mnc170/config.xml new file mode 100644 index 000000000000..26b9192e0cc3 --- /dev/null +++ b/packages/SystemUI/res/values-mcc310-mnc170/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> +</resources> + diff --git a/packages/SystemUI/res/values-mcc310-mnc280/config.xml b/packages/SystemUI/res/values-mcc310-mnc280/config.xml new file mode 100644 index 000000000000..26b9192e0cc3 --- /dev/null +++ b/packages/SystemUI/res/values-mcc310-mnc280/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> +</resources> + diff --git a/packages/SystemUI/res/values-mcc310-mnc380/config.xml b/packages/SystemUI/res/values-mcc310-mnc380/config.xml new file mode 100644 index 000000000000..26b9192e0cc3 --- /dev/null +++ b/packages/SystemUI/res/values-mcc310-mnc380/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> +</resources> + diff --git a/packages/SystemUI/res/values-mcc310-mnc410/config.xml b/packages/SystemUI/res/values-mcc310-mnc410/config.xml new file mode 100644 index 000000000000..26b9192e0cc3 --- /dev/null +++ b/packages/SystemUI/res/values-mcc310-mnc410/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> +</resources> + diff --git a/packages/SystemUI/res/values-mcc310-mnc560/config.xml b/packages/SystemUI/res/values-mcc310-mnc560/config.xml new file mode 100644 index 000000000000..26b9192e0cc3 --- /dev/null +++ b/packages/SystemUI/res/values-mcc310-mnc560/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> +</resources> + diff --git a/packages/SystemUI/res/values-mcc310-mnc950/config.xml b/packages/SystemUI/res/values-mcc310-mnc950/config.xml new file mode 100644 index 000000000000..26b9192e0cc3 --- /dev/null +++ b/packages/SystemUI/res/values-mcc310-mnc950/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> +</resources> + diff --git a/packages/SystemUI/res/values-mcc311-mnc180/config.xml b/packages/SystemUI/res/values-mcc311-mnc180/config.xml new file mode 100644 index 000000000000..26b9192e0cc3 --- /dev/null +++ b/packages/SystemUI/res/values-mcc311-mnc180/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2019, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> +</resources> + diff --git a/packages/SystemUI/res/values/internal.xml b/packages/SystemUI/res/values/internal.xml index 930cfce65b61..c29a51f6178e 100644 --- a/packages/SystemUI/res/values/internal.xml +++ b/packages/SystemUI/res/values/internal.xml @@ -17,6 +17,7 @@ <resources> <dimen name="status_bar_height">@*android:dimen/status_bar_height</dimen> <dimen name="navigation_bar_height">@*android:dimen/navigation_bar_height</dimen> + <dimen name="navigation_bar_frame_height">@*android:dimen/navigation_bar_frame_height</dimen> <dimen name="navigation_bar_height_car_mode">@*android:dimen/navigation_bar_height_car_mode</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f47d4b5aac6d..e098bc5acd5c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -424,6 +424,9 @@ <!-- Content description of the data connection type LTE+. [CHAR LIMIT=NONE] --> <string name="data_connection_lte_plus">LTE+</string> + <!-- Content description of the data connection type 5Ge. [CHAR LIMIT=NONE] --> + <string name="data_connection_5ge" translate="false">5Ge</string> + <!-- Content description of the data connection type 5G. [CHAR LIMIT=NONE] --> <string name="data_connection_5g" translate="false">5G</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index b738b576ef2f..70366a839da5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -138,6 +138,7 @@ public class KeyguardClockSwitch extends RelativeLayout { ClockManager clockManager) { super(context, attrs); mStatusBarStateController = statusBarStateController; + mStatusBarState = mStatusBarStateController.getState(); mSysuiColorExtractor = colorExtractor; mClockManager = clockManager; mTransition = new ClockBoundsTransition(); diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java index 64e56f955058..bc00b5cf9a45 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java @@ -20,8 +20,10 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; +import android.net.Uri; import android.os.Handler; import android.os.Looper; +import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -35,6 +37,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManager.DockEventListener; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.plugins.PluginListener; +import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.util.InjectionInflationController; @@ -61,6 +64,7 @@ public final class ClockManager { private final ContentResolver mContentResolver; private final SettingsWrapper mSettingsWrapper; private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + private final CurrentUserTracker mCurrentUserTracker; /** * Observe settings changes to know when to switch the clock face. @@ -68,9 +72,11 @@ public final class ClockManager { private final ContentObserver mContentObserver = new ContentObserver(mMainHandler) { @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - reload(); + public void onChange(boolean selfChange, Uri uri, int userId) { + super.onChange(selfChange, uri, userId); + if (userId == mCurrentUserTracker.getCurrentUserId()) { + reload(); + } } }; @@ -123,6 +129,12 @@ public final class ClockManager { mPluginManager = pluginManager; mContentResolver = contentResolver; mSettingsWrapper = settingsWrapper; + mCurrentUserTracker = new CurrentUserTracker(context) { + @Override + public void onUserSwitched(int newUserId) { + reload(); + } + }; mPreviewClocks = new AvailableClocks(); Resources res = context.getResources(); @@ -203,10 +215,11 @@ public final class ClockManager { mPluginManager.addPluginListener(mPreviewClocks, ClockPlugin.class, true); mContentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), - false, mContentObserver); + false, mContentObserver, UserHandle.USER_ALL); mContentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE), - false, mContentObserver); + false, mContentObserver, UserHandle.USER_ALL); + mCurrentUserTracker.startTracking(); if (mDockManager == null) { mDockManager = SysUiServiceProvider.getComponent(mContext, DockManager.class); } @@ -218,6 +231,7 @@ public final class ClockManager { private void unregister() { mPluginManager.removePluginListener(mPreviewClocks); mContentResolver.unregisterContentObserver(mContentObserver); + mCurrentUserTracker.stopTracking(); if (mDockManager != null) { mDockManager.removeListener(mDockEventListener); } @@ -334,7 +348,8 @@ public final class ClockManager { private ClockPlugin getClockPlugin() { ClockPlugin plugin = null; if (ClockManager.this.isDocked()) { - final String name = mSettingsWrapper.getDockedClockFace(); + final String name = mSettingsWrapper.getDockedClockFace( + mCurrentUserTracker.getCurrentUserId()); if (name != null) { plugin = mClocks.get(name); if (plugin != null) { @@ -342,7 +357,8 @@ public final class ClockManager { } } } - final String name = mSettingsWrapper.getLockScreenCustomClockFace(); + final String name = mSettingsWrapper.getLockScreenCustomClockFace( + mCurrentUserTracker.getCurrentUserId()); if (name != null) { plugin = mClocks.get(name); } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java index 58e11553af9d..e1c658be4c26 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java @@ -34,15 +34,19 @@ public class SettingsWrapper { /** * Gets the value stored in settings for the custom clock face. + * + * @param userId ID of the user. */ - public String getLockScreenCustomClockFace() { - return Settings.Secure.getString(mContentResolver, CUSTOM_CLOCK_FACE); + public String getLockScreenCustomClockFace(int userId) { + return Settings.Secure.getStringForUser(mContentResolver, CUSTOM_CLOCK_FACE, userId); } /** * Gets the value stored in settings for the clock face to use when docked. + * + * @param userId ID of the user. */ - public String getDockedClockFace() { - return Settings.Secure.getString(mContentResolver, DOCKED_CLOCK_FACE); + public String getDockedClockFace(int userId) { + return Settings.Secure.getStringForUser(mContentResolver, DOCKED_CLOCK_FACE, userId); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 4fe09a92a1de..665df777c1c1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -26,16 +26,41 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; */ class Bubble { + private final String mKey; + private final BubbleExpandedView.OnBubbleBlockedListener mListener; + + private boolean mInflated; + public BubbleView iconView; public BubbleExpandedView expandedView; - public String key; public NotificationEntry entry; + Bubble(NotificationEntry e, BubbleExpandedView.OnBubbleBlockedListener listener) { + entry = e; + mKey = e.key; + mListener = listener; + } + + /** @deprecated use the other constructor to defer View creation. */ + @Deprecated Bubble(NotificationEntry e, LayoutInflater inflater, BubbleStackView stackView, BubbleExpandedView.OnBubbleBlockedListener listener) { - entry = e; - key = entry.key; + this(e, listener); + inflate(inflater, stackView); + } + + public String getKey() { + return mKey; + } + + boolean isInflated() { + return mInflated; + } + void inflate(LayoutInflater inflater, BubbleStackView stackView) { + if (mInflated) { + return; + } iconView = (BubbleView) inflater.inflate( R.layout.bubble_view, stackView, false /* attachToRoot */); iconView.setNotif(entry); @@ -44,12 +69,14 @@ class Bubble { R.layout.bubble_expanded_view, stackView, false /* attachToRoot */); expandedView.setEntry(entry, stackView); - expandedView.setOnBlockedListener(listener); + expandedView.setOnBlockedListener(mListener); + mInflated = true; } - public void setEntry(NotificationEntry entry) { - key = entry.key; - iconView.update(entry); - expandedView.update(entry); + void setEntry(NotificationEntry entry) { + if (mInflated) { + iconView.update(entry); + expandedView.update(entry); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 5acf3c24fd6a..418d052e1858 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -220,6 +220,26 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe mSurfaceSynchronizer = synchronizer; } + /** + * BubbleStackView is lazily created by this method the first time a Bubble is added. This + * method initializes the stack view and adds it to the StatusBar just above the scrim. + */ + private void ensureStackViewCreated() { + if (mStackView == null) { + mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer); + ViewGroup sbv = mStatusBarWindowController.getStatusBarView(); + // TODO(b/130237686): When you expand the shade on top of expanded bubble, there is no + // scrim between bubble and the shade + int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1; + sbv.addView(mStackView, bubblePosition, + new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + if (mExpandListener != null) { + mStackView.setExpandListener(mExpandListener); + } + mStackView.setOnBlockedListener(this); + } + } + @Override public void onUiModeChanged() { if (mStackView != null) { @@ -325,27 +345,15 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe /** * Adds or updates a bubble associated with the provided notification entry. * - * @param notif the notification associated with this bubble. + * @param notif the notification associated with this bubble. */ void updateBubble(NotificationEntry notif) { if (mStackView != null && mBubbleData.getBubble(notif.key) != null) { // It's an update mStackView.updateBubble(notif); } else { - if (mStackView == null) { - mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer); - ViewGroup sbv = mStatusBarWindowController.getStatusBarView(); - // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim - // between bubble and the shade - int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1; - sbv.addView(mStackView, bubblePosition, - new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - if (mExpandListener != null) { - mStackView.setExpandListener(mExpandListener); - } - mStackView.setOnBlockedListener(this); - } // It's new + ensureStackViewCreated(); mStackView.addBubble(notif); } if (shouldAutoExpand(notif)) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index cf702870c4ff..fe3f9d192cd5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -106,7 +106,7 @@ public class BubbleData { } public void addBubble(Bubble b) { - mBubbles.put(b.key, b); + mBubbles.put(b.getKey(), b); } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java index a74c3287e1f1..05665b5ae4a2 100644 --- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java +++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java @@ -34,6 +34,7 @@ import com.android.internal.colorextraction.types.ExtractionType; import com.android.internal.colorextraction.types.Tonal; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.policy.ConfigurationController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -46,7 +47,8 @@ import javax.inject.Singleton; * ColorExtractor aware of wallpaper visibility */ @Singleton -public class SysuiColorExtractor extends ColorExtractor implements Dumpable { +public class SysuiColorExtractor extends ColorExtractor implements Dumpable, + ConfigurationController.ConfigurationListener { private static final String TAG = "SysuiColorExtractor"; private final Tonal mTonal; private boolean mWallpaperVisible; @@ -55,15 +57,17 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable { private final GradientColors mWpHiddenColors; @Inject - public SysuiColorExtractor(Context context) { - this(context, new Tonal(context), true); + public SysuiColorExtractor(Context context, ConfigurationController configurationController) { + this(context, new Tonal(context), configurationController, true); } @VisibleForTesting - public SysuiColorExtractor(Context context, ExtractionType type, boolean registerVisibility) { + public SysuiColorExtractor(Context context, ExtractionType type, + ConfigurationController configurationController, boolean registerVisibility) { super(context, type, false /* immediately */); mTonal = type instanceof Tonal ? (Tonal) type : new Tonal(context); mWpHiddenColors = new GradientColors(); + configurationController.addCallback(this); WallpaperColors systemColors = getWallpaperColors(WallpaperManager.FLAG_SYSTEM); updateDefaultGradients(systemColors); @@ -113,8 +117,21 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable { } } - @VisibleForTesting - GradientColors getFallbackColors() { + @Override + public void onUiModeChanged() { + WallpaperColors systemColors = getWallpaperColors(WallpaperManager.FLAG_SYSTEM); + updateDefaultGradients(systemColors); + } + + /** + * Colors the should be using for scrims. + * + * They will be: + * - A light gray if the wallpaper is light + * - A dark gray if the wallpaper is very dark or we're in night mode. + * - Black otherwise + */ + public GradientColors getNeutralColors() { return mWpHiddenColors; } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 7a3f3bef8f5e..411536c2ddbb 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -35,7 +35,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; import android.database.ContentObserver; -import android.graphics.Point; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.ConnectivityManager; @@ -73,7 +72,7 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor.GradientColors; -import com.android.internal.colorextraction.drawable.GradientDrawable; +import com.android.internal.colorextraction.drawable.ScrimDrawable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.telephony.TelephonyIntents; @@ -1503,7 +1502,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final MyAdapter mAdapter; private MultiListLayout mGlobalActionsLayout; private Drawable mBackgroundDrawable; - private final ColorExtractor mColorExtractor; + private final SysuiColorExtractor mColorExtractor; private final GlobalActionsPanelPlugin.PanelViewController mPanelController; private boolean mKeyguardShowing; private boolean mShowing; @@ -1582,7 +1581,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (!shouldUsePanel()) { if (mBackgroundDrawable == null) { - mBackgroundDrawable = new GradientDrawable(mContext); + mBackgroundDrawable = new ScrimDrawable(); } mScrimAlpha = ScrimController.GRADIENT_SCRIM_ALPHA; } else { @@ -1610,16 +1609,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, super.onStart(); mGlobalActionsLayout.updateList(); - if (mBackgroundDrawable instanceof GradientDrawable) { - Point displaySize = new Point(); - mContext.getDisplay().getRealSize(displaySize); + if (mBackgroundDrawable instanceof ScrimDrawable) { mColorExtractor.addOnColorsChangedListener(this); - ((GradientDrawable) mBackgroundDrawable) - .setScreenSize(displaySize.x, displaySize.y); - GradientColors colors = mColorExtractor.getColors( - mKeyguardShowing - ? WallpaperManager.FLAG_LOCK - : WallpaperManager.FLAG_SYSTEM); + GradientColors colors = mColorExtractor.getNeutralColors(); updateColors(colors, false /* animate */); } } @@ -1630,10 +1622,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, * @param animate Interpolates gradient if true, just sets otherwise. */ private void updateColors(GradientColors colors, boolean animate) { - if (!(mBackgroundDrawable instanceof GradientDrawable)) { + if (!(mBackgroundDrawable instanceof ScrimDrawable)) { return; } - ((GradientDrawable) mBackgroundDrawable).setColors(colors, animate); + ((ScrimDrawable) mBackgroundDrawable).setColor(colors.getMainColor(), animate); View decorView = getWindow().getDecorView(); if (colors.supportsDarkText()) { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index 4cf58b736eb9..4065d5b5dd8d 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -19,9 +19,7 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import android.app.Dialog; import android.app.KeyguardManager; -import android.app.WallpaperManager; import android.content.Context; -import android.graphics.Point; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -31,7 +29,7 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.colorextraction.ColorExtractor.GradientColors; -import com.android.internal.colorextraction.drawable.GradientDrawable; +import com.android.internal.colorextraction.drawable.ScrimDrawable; import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.SysUiServiceProvider; @@ -87,7 +85,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks @Override public void showShutdownUi(boolean isReboot, String reason) { - GradientDrawable background = new GradientDrawable(mContext); + ScrimDrawable background = new ScrimDrawable(); background.setAlpha((int) (SHUTDOWN_SCRIM_ALPHA * 255)); Dialog d = new Dialog(mContext, @@ -129,12 +127,8 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks message.setTextColor(color); if (isReboot) message.setText(R.string.reboot_to_reset_message); - Point displaySize = new Point(); - mContext.getDisplay().getRealSize(displaySize); - GradientColors colors = Dependency.get(SysuiColorExtractor.class).getColors( - onKeyguard ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM); - background.setColors(colors, false); - background.setScreenSize(displaySize.x, displaySize.y); + GradientColors colors = Dependency.get(SysuiColorExtractor.class).getNeutralColors(); + background.setColor(colors.getMainColor(), false); d.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java index d1939d0fddb0..477e7d7ebf72 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java @@ -85,9 +85,7 @@ class ImageProcessHelper { Bitmap bitmap = bitmaps[0]; if (bitmap != null) { int[] histogram = processHistogram(bitmap); - Float val = computePercentile85(bitmap, histogram); - bitmaps[0] = null; - return val; + return computePercentile85(bitmap, histogram); } Log.e(TAG, "Per85ComputeTask: Can't get bitmap"); return DEFAULT_PER85; diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 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/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index d40973bfdad7..a732a253f5a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -40,7 +40,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.ImageView.ScaleType; import android.widget.Switch; import com.android.settingslib.Utils; @@ -63,6 +62,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private boolean mTileState; private boolean mCollapsedView; private boolean mClicked; + private boolean mShowRippleEffect = true; private final ImageView mBg; private final int mColorActive; @@ -209,6 +209,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { mCircleColor = circleColor; } + mShowRippleEffect = state.showRippleEffect; setClickable(state.state != Tile.STATE_UNAVAILABLE); setLongClickable(state.handlesLongClick); mIcon.setIcon(state, allowAnimations); @@ -254,7 +255,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { @Override public void setClickable(boolean clickable) { super.setClickable(clickable); - setBackground(clickable ? mRipple : null); + setBackground(clickable && mShowRippleEffect ? mRipple : null); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java index c664a2090c04..d62f10d312d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; import android.content.Intent; +import android.provider.Settings.Secure; import android.service.quicksettings.Tile; import android.widget.Switch; @@ -23,6 +24,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.SecureSetting; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BatteryController; @@ -32,6 +34,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements BatteryController.BatteryStateChangeCallback { private final BatteryController mBatteryController; + private final SecureSetting mSetting; private int mLevel; private boolean mPowerSave; @@ -45,6 +48,12 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements super(host); mBatteryController = batteryController; mBatteryController.observe(getLifecycle(), this); + mSetting = new SecureSetting(mContext, mHandler, Secure.LOW_POWER_WARNING_ACKNOWLEDGED) { + @Override + protected void handleValueChanged(int value, boolean observedChange) { + handleRefreshState(null); + } + }; } @Override @@ -53,12 +62,19 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements } @Override + protected void handleDestroy() { + super.handleDestroy(); + mSetting.setListening(false); + } + + @Override public int getMetricsCategory() { return MetricsEvent.QS_BATTERY_TILE; } @Override public void handleSetListening(boolean listening) { + mSetting.setListening(listening); } @Override @@ -88,6 +104,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements state.contentDescription = state.label; state.value = mPowerSave; state.expandedAccessibilityClassName = Switch.class.getName(); + state.showRippleEffect = mSetting.getValue() == 0; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 4d6693ff6613..7b6fb943f512 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -631,7 +631,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void notifyAssistantVisibilityChanged(float visibility) { try { - mOverviewProxy.onAssistantVisibilityChanged(visibility); + if (mOverviewProxy != null) { + mOverviewProxy.onAssistantVisibilityChanged(visibility); + } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to call onAssistantVisibilityChanged()", e); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index a87e50c50f51..3441591e03ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -83,7 +83,7 @@ public class KeyguardIndicationController implements StateListener { private final int mSlowThreshold; private final int mFastThreshold; - private LockIcon mLockIcon; + private final LockIcon mLockIcon; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private String mRestingIndication; @@ -539,7 +539,6 @@ public class KeyguardIndicationController implements StateListener { protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback { public static final int HIDE_DELAY_MS = 5000; - private int mLastSuccessiveErrorMessage = -1; @Override public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { @@ -577,20 +576,14 @@ public class KeyguardIndicationController implements StateListener { if (!updateMonitor.isUnlockingWithBiometricAllowed()) { return; } + animatePadlockError(); if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.showBouncerMessage(helpString, mInitialTextColorState); } else if (updateMonitor.isScreenOn()) { - mLockIcon.setTransientBiometricsError(true); showTransientIndication(helpString); hideTransientIndicationDelayed(TRANSIENT_BIOMETRIC_ERROR_TIMEOUT); - mHandler.removeMessages(MSG_CLEAR_BIOMETRIC_MSG); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_BIOMETRIC_MSG), - TRANSIENT_BIOMETRIC_ERROR_TIMEOUT); } - // Help messages indicate that there was actually a try since the last error, so those - // are not two successive error messages anymore. - mLastSuccessiveErrorMessage = -1; } @Override @@ -600,15 +593,9 @@ public class KeyguardIndicationController implements StateListener { if (shouldSuppressBiometricError(msgId, biometricSourceType, updateMonitor)) { return; } + animatePadlockError(); if (mStatusBarKeyguardViewManager.isBouncerShowing()) { - // When swiping up right after receiving a biometric error, the bouncer calls - // authenticate leading to the same message being shown again on the bouncer. - // We want to avoid this, as it may confuse the user when the message is too - // generic. - if (mLastSuccessiveErrorMessage != msgId) { - mStatusBarKeyguardViewManager.showBouncerMessage(errString, - mInitialTextColorState); - } + mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState); } else if (updateMonitor.isScreenOn()) { showTransientIndication(errString); // We want to keep this message around in case the screen was off @@ -616,7 +603,13 @@ public class KeyguardIndicationController implements StateListener { } else { mMessageToShowOnScreenOn = errString; } - mLastSuccessiveErrorMessage = msgId; + } + + private void animatePadlockError() { + mLockIcon.setTransientBiometricsError(true); + mHandler.removeMessages(MSG_CLEAR_BIOMETRIC_MSG); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_BIOMETRIC_MSG), + TRANSIENT_BIOMETRIC_ERROR_TIMEOUT); } private boolean shouldSuppressBiometricError(int msgId, @@ -670,21 +663,19 @@ public class KeyguardIndicationController implements StateListener { @Override public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) { super.onBiometricAuthenticated(userId, biometricSourceType); - mLastSuccessiveErrorMessage = -1; mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT); } @Override - public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) { - super.onBiometricAuthFailed(biometricSourceType); - mLastSuccessiveErrorMessage = -1; - } - - @Override public void onUserUnlocked() { if (mVisible) { updateIndication(false); } } + + @Override + public void onKeyguardBouncerChanged(boolean bouncer) { + mLockIcon.setBouncerVisible(bouncer); + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java index cf6e64cef365..04f1c3248a6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java @@ -16,61 +16,33 @@ package com.android.systemui.statusbar; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.util.Log; -import android.view.Display; import android.view.View; -import android.view.WindowManager; import androidx.core.graphics.ColorUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.colorextraction.drawable.GradientDrawable; -import com.android.settingslib.Utils; -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.internal.colorextraction.drawable.ScrimDrawable; /** * A view which can draw a scrim */ -public class ScrimView extends View implements ConfigurationController.ConfigurationListener { - private static final String TAG = "ScrimView"; +public class ScrimView extends View { private final ColorExtractor.GradientColors mColors; - private int mDensity; private float mViewAlpha = 1.0f; - private ValueAnimator mAlphaAnimator; private Drawable mDrawable; private PorterDuffColorFilter mColorFilter; private int mTintColor; - private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener = animation -> { - if (mDrawable == null) { - Log.w(TAG, "Trying to animate null drawable"); - return; - } - mDrawable.setAlpha((int) (255 * (float) animation.getAnimatedValue())); - }; - private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mAlphaAnimator = null; - } - }; private Runnable mChangeRunnable; - private int mCornerRadius; public ScrimView(Context context) { this(context, null); @@ -87,47 +59,10 @@ public class ScrimView extends View implements ConfigurationController.Configura public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mDrawable = new GradientDrawable(context); + mDrawable = new ScrimDrawable(); mDrawable.setCallback(this); mColors = new ColorExtractor.GradientColors(); - updateScreenSize(); updateColorWithTint(false); - initView(); - final Configuration currentConfig = mContext.getResources().getConfiguration(); - mDensity = currentConfig.densityDpi; - } - - private void initView() { - mCornerRadius = getResources().getDimensionPixelSize( - Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - int densityDpi = newConfig.densityDpi; - if (mDensity != densityDpi) { - mDensity = densityDpi; - initView(); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - // We need to know about configuration changes to update the gradient size - // since it's independent from view bounds. - ConfigurationController config = Dependency.get(ConfigurationController.class); - config.addCallback(this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - ConfigurationController config = Dependency.get(ConfigurationController.class); - config.removeCallback(this); } @Override @@ -142,7 +77,6 @@ public class ScrimView extends View implements ConfigurationController.Configura mDrawable.setCallback(this); mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom()); mDrawable.setAlpha((int) (255 * mViewAlpha)); - updateScreenSize(); invalidate(); } @@ -200,15 +134,13 @@ public class ScrimView extends View implements ConfigurationController.Configura } private void updateColorWithTint(boolean animated) { - if (mDrawable instanceof GradientDrawable) { + if (mDrawable instanceof ScrimDrawable) { // Optimization to blend colors and avoid a color filter - GradientDrawable drawable = (GradientDrawable) mDrawable; + ScrimDrawable drawable = (ScrimDrawable) mDrawable; float tintAmount = Color.alpha(mTintColor) / 255f; int mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, tintAmount); - int secondaryTinted = ColorUtils.blendARGB(mColors.getSecondaryColor(), mTintColor, - tintAmount); - drawable.setColors(mainTinted, secondaryTinted, animated); + drawable.setColor(mainTinted, animated); } else { boolean hasAlpha = Color.alpha(mTintColor) != 0; if (hasAlpha) { @@ -250,10 +182,6 @@ public class ScrimView extends View implements ConfigurationController.Configura if (alpha != mViewAlpha) { mViewAlpha = alpha; - if (mAlphaAnimator != null) { - mAlphaAnimator.cancel(); - } - mDrawable.setAlpha((int) (255 * alpha)); if (mChangeRunnable != null) { mChangeRunnable.run(); @@ -270,27 +198,6 @@ public class ScrimView extends View implements ConfigurationController.Configura } @Override - public void onConfigChanged(Configuration newConfig) { - updateScreenSize(); - } - - private void updateScreenSize() { - if (mDrawable instanceof GradientDrawable) { - WindowManager wm = mContext.getSystemService(WindowManager.class); - if (wm == null) { - Log.w(TAG, "Can't resize gradient drawable to fit the screen"); - return; - } - Display display = wm.getDefaultDisplay(); - if (display != null) { - Point size = new Point(); - display.getRealSize(size); - ((GradientDrawable) mDrawable).setScreenSize(size.x, size.y); - } - } - } - - @Override protected boolean canReceivePointerEvents() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 2e85fea4615f..ce8463e70099 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -367,16 +367,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback { @Override public void onFinishedGoingToSleep(int why) { Trace.beginSection("BiometricUnlockController#onFinishedGoingToSleep"); - if (mPendingAuthenticatedUserId != -1) { - + BiometricSourceType pendingType = mPendingAuthenticatedBioSourceType; + int pendingUserId = mPendingAuthenticatedUserId; + if (pendingUserId != -1 && pendingType != null) { // Post this to make sure it's executed after the device is fully locked. - mHandler.post(new Runnable() { - @Override - public void run() { - onBiometricAuthenticated(mPendingAuthenticatedUserId, - mPendingAuthenticatedBioSourceType); - } - }); + mHandler.post(() -> onBiometricAuthenticated(pendingUserId, pendingType)); } mPendingAuthenticatedUserId = -1; mPendingAuthenticatedBioSourceType = null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 3a6756bb677f..79bf6b3e49f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import android.content.Context; import android.content.pm.ParceledListSlice; +import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PointF; @@ -140,6 +141,7 @@ public class EdgeBackGestureHandler implements DisplayListener { private WindowManager.LayoutParams mEdgePanelLp; public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService) { + final Resources res = context.getResources(); mContext = context; mDisplayId = context.getDisplayId(); mMainExecutor = context.getMainExecutor(); @@ -148,10 +150,9 @@ public class EdgeBackGestureHandler implements DisplayListener { mEdgeWidth = QuickStepContract.getEdgeSensitivityWidth(context); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - mSwipeThreshold = context.getResources() - .getDimension(R.dimen.navigation_edge_action_drag_threshold); + mSwipeThreshold = res.getDimension(R.dimen.navigation_edge_action_drag_threshold); - mNavBarHeight = context.getResources().getDimensionPixelSize(R.dimen.navigation_bar_height); + mNavBarHeight = res.getDimensionPixelSize(R.dimen.navigation_bar_frame_height); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index 6ebd6b3f04cd..3cc4a7bfa1eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -57,8 +57,10 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange private int mDensity; private boolean mPulsing; private boolean mDozing; + private boolean mBouncerVisible; private boolean mLastDozing; private boolean mLastPulsing; + private boolean mLastBouncerVisible; private final Runnable mDrawOffTimeout = () -> update(true /* forceUpdate */); private float mDarkAmount; @@ -109,9 +111,9 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange int state = getState(); mIsFaceUnlockState = state == STATE_SCANNING_FACE; if (state != mLastState || mLastDozing != mDozing || mLastPulsing != mPulsing - || mLastScreenOn != mScreenOn || force) { + || mLastScreenOn != mScreenOn || mLastBouncerVisible != mBouncerVisible || force) { int iconAnimRes = getAnimationResForTransition(mLastState, state, mLastPulsing, - mPulsing, mLastDozing, mDozing); + mPulsing, mLastDozing, mDozing, mBouncerVisible); boolean isAnim = iconAnimRes != -1; Drawable icon; @@ -159,6 +161,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange mLastScreenOn = mScreenOn; mLastDozing = mDozing; mLastPulsing = mPulsing; + mLastBouncerVisible = mBouncerVisible; } setVisibility(mDozing && !mPulsing ? GONE : VISIBLE); @@ -231,8 +234,8 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange } private static int getAnimationResForTransition(int oldState, int newState, - boolean wasPulsing, boolean pulsing, - boolean wasDozing, boolean dozing) { + boolean wasPulsing, boolean pulsing, boolean wasDozing, boolean dozing, + boolean bouncerVisible) { // Never animate when screen is off if (dozing && !pulsing) { @@ -249,7 +252,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange return com.android.internal.R.anim.lock_unlock; } else if (justLocked) { return com.android.internal.R.anim.lock_lock; - } else if (newState == STATE_SCANNING_FACE) { + } else if (newState == STATE_SCANNING_FACE && bouncerVisible) { return com.android.internal.R.anim.lock_scanning; } else if (!wasPulsing && pulsing && newState != STATE_LOCK_OPEN) { return com.android.internal.R.anim.lock_in; @@ -298,4 +301,15 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange int color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.WHITE, mDarkAmount); drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); } + + /** + * If bouncer is visible or not. + */ + public void setBouncerVisible(boolean bouncerVisible) { + if (mBouncerVisible == bouncerVisible) { + return; + } + mBouncerVisible = bouncerVisible; + update(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 0d2fe13b9a09..ed7947690e50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -20,7 +20,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.AlarmManager; -import android.app.WallpaperManager; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.Drawable; @@ -121,8 +120,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo private final Handler mHandler; private final SysuiColorExtractor mColorExtractor; - private GradientColors mLockColors; - private GradientColors mSystemColors; + private GradientColors mColors; private boolean mNeedsDrawableColorUpdate; protected float mScrimBehindAlpha; @@ -190,10 +188,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); - mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, - ColorExtractor.TYPE_DARK, true /* ignoreVisibility */); - mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, - ColorExtractor.TYPE_DARK, true /* ignoreVisibility */); + mColors = mColorExtractor.getNeutralColors(); mNeedsDrawableColorUpdate = true; final ScrimState[] states = ScrimState.values(); @@ -201,7 +196,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo states[i].init(mScrimInFront, mScrimBehind, mDozeParameters); states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard); } - mState = ScrimState.UNINITIALIZED; mScrimBehind.setDefaultFocusHighlightEnabled(false); mScrimInFront.setDefaultFocusHighlightEnabled(false); @@ -488,17 +482,15 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo // Make sure we have the right gradients and their opacities will satisfy GAR. if (mNeedsDrawableColorUpdate) { mNeedsDrawableColorUpdate = false; - boolean isKeyguard = mKeyguardUpdateMonitor.isKeyguardVisible() && !mKeyguardOccluded; - GradientColors currentScrimColors = isKeyguard ? mLockColors : mSystemColors; // Only animate scrim color if the scrim view is actually visible boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen; boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen; - mScrimInFront.setColors(currentScrimColors, animateScrimInFront); - mScrimBehind.setColors(currentScrimColors, animateScrimBehind); + mScrimInFront.setColors(mColors, animateScrimInFront); + mScrimBehind.setColors(mColors, animateScrimBehind); // Calculate minimum scrim opacity for white or black text. - int textColor = currentScrimColors.supportsDarkText() ? Color.BLACK : Color.WHITE; - int mainColor = currentScrimColors.getMainColor(); + int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE; + int mainColor = mColors.getMainColor(); float minOpacity = ColorUtils.calculateMinimumBackgroundAlpha(textColor, mainColor, 4.5f /* minimumContrast */) / 255f; mScrimBehindAlpha = Math.max(mScrimBehindAlphaResValue, minOpacity); @@ -815,7 +807,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } public int getBackgroundColor() { - int color = mLockColors.getMainColor(); + int color = mColors.getMainColor(); return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)), Color.red(color), Color.green(color), Color.blue(color)); } @@ -830,18 +822,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo @Override public void onColorsChanged(ColorExtractor colorExtractor, int which) { - if ((which & WallpaperManager.FLAG_LOCK) != 0) { - mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, - ColorExtractor.TYPE_DARK, true /* ignoreVisibility */); - mNeedsDrawableColorUpdate = true; - scheduleUpdate(); - } - if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { - mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, - ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED); - mNeedsDrawableColorUpdate = true; - scheduleUpdate(); - } + mColors = mColorExtractor.getNeutralColors(); + mNeedsDrawableColorUpdate = true; + scheduleUpdate(); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index c5996a1e1b00..8f135c80a1d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -47,6 +47,8 @@ import com.android.systemui.statusbar.policy.NetworkControllerImpl.SubscriptionD import java.io.PrintWriter; import java.util.BitSet; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class MobileSignalController extends SignalController< @@ -73,6 +75,9 @@ public class MobileSignalController extends SignalController< private SignalStrength mSignalStrength; private MobileIconGroup mDefaultIcons; private Config mConfig; + private boolean mInflateSignalStrengths = false; + // Some specific carriers have 5GE network which is special LTE CA network. + private static final int NETWORK_TYPE_LTE_CA_5GE = TelephonyManager.MAX_NETWORK_TYPE + 1; // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't // need listener lists anymore. @@ -114,6 +119,7 @@ public class MobileSignalController extends SignalController< public void setConfiguration(Config config) { mConfig = config; + updateInflateSignalStrength(); mapIconSets(); updateTelephony(); } @@ -236,11 +242,19 @@ public class MobileSignalController extends SignalController< TelephonyIcons.LTE_PLUS); } } + mNetworkToIconLookup.put(NETWORK_TYPE_LTE_CA_5GE, + TelephonyIcons.LTE_CA_5G_E); mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.WFC); } + private void updateInflateSignalStrength() { + mInflateSignalStrengths = SubscriptionManager.getResourcesForSubId(mContext, + mSubscriptionInfo.getSubscriptionId()) + .getBoolean(R.bool.config_inflateSignalStrength); + } + private int getNumLevels() { - if (mConfig.inflateSignalStrengths) { + if (mInflateSignalStrengths) { return SignalStrength.NUM_SIGNAL_STRENGTH_BINS + 1; } return SignalStrength.NUM_SIGNAL_STRENGTH_BINS; @@ -252,7 +266,7 @@ public class MobileSignalController extends SignalController< return SignalDrawable.getCarrierChangeState(getNumLevels()); } else if (mCurrentState.connected) { int level = mCurrentState.level; - if (mConfig.inflateSignalStrengths) { + if (mInflateSignalStrengths) { level++; } boolean dataDisabled = mCurrentState.userSetup @@ -381,6 +395,26 @@ public class MobileSignalController extends SignalController< } } + private boolean isCarrierSpecificDataIcon() { + if (mConfig.patternOfCarrierSpecificDataIcon == null + || mConfig.patternOfCarrierSpecificDataIcon.length() == 0) { + return false; + } + + Pattern stringPattern = Pattern.compile(mConfig.patternOfCarrierSpecificDataIcon); + String[] operatorNames = new String[]{mServiceState.getOperatorAlphaLongRaw(), + mServiceState.getOperatorAlphaShortRaw()}; + for (String opName : operatorNames) { + if (!TextUtils.isEmpty(opName)) { + Matcher matcher = stringPattern.matcher(opName); + if (matcher.find()) { + return true; + } + } + } + return false; + } + /** * Updates the network's name based on incoming spn and plmn. */ @@ -535,6 +569,7 @@ public class MobileSignalController extends SignalController< pw.println(" mSignalStrength=" + mSignalStrength + ","); pw.println(" mDataState=" + mDataState + ","); pw.println(" mDataNetType=" + mDataNetType + ","); + pw.println(" mInflateSignalStrengths=" + mInflateSignalStrengths + ","); } class MobilePhoneStateListener extends PhoneStateListener { @@ -559,12 +594,8 @@ public class MobileSignalController extends SignalController< + " dataState=" + state.getDataRegState()); } mServiceState = state; - if (state != null) { - mDataNetType = state.getDataNetworkType(); - if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE && mServiceState != null && - mServiceState.isUsingCarrierAggregation()) { - mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA; - } + if (mServiceState != null) { + updateDataNetType(mServiceState.getDataNetworkType()); } updateTelephony(); } @@ -576,12 +607,19 @@ public class MobileSignalController extends SignalController< + " type=" + networkType); } mDataState = state; + updateDataNetType(networkType); + updateTelephony(); + } + + private void updateDataNetType(int networkType) { mDataNetType = networkType; - if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE && mServiceState != null && - mServiceState.isUsingCarrierAggregation()) { - mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA; + if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE) { + if (isCarrierSpecificDataIcon()) { + mDataNetType = NETWORK_TYPE_LTE_CA_5GE; + } else if (mServiceState != null && mServiceState.isUsingCarrierAggregation()) { + mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA; + } } - updateTelephony(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index d01430a97783..faf63c838259 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -55,6 +55,7 @@ import android.util.Log; import android.util.MathUtils; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; @@ -108,16 +109,10 @@ public class NetworkControllerImpl extends BroadcastReceiver private final SubscriptionDefaults mSubDefaults; private final DataSaverController mDataSaverController; private final CurrentUserTracker mUserTracker; + private final Object mLock = new Object(); private Config mConfig; - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onActiveDataSubscriptionIdChanged(int subId) { - mActiveMobileDataSubscription = subId; - doUpdateMobileControllers(); - } - }; - + private PhoneStateListener mPhoneStateListener; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; // Subcontrollers. @@ -279,6 +274,14 @@ public class NetworkControllerImpl extends BroadcastReceiver // TODO: Move off of the deprecated CONNECTIVITY_ACTION broadcast and rely on callbacks // exclusively for status bar icons. mConnectivityManager.registerDefaultNetworkCallback(callback, mReceiverHandler); + // Register the listener on our bg looper + mPhoneStateListener = new PhoneStateListener(bgLooper) { + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveMobileDataSubscription = subId; + doUpdateMobileControllers(); + } + }; } public DataSaverController getDataSaverController() { @@ -600,7 +603,9 @@ public class NetworkControllerImpl extends BroadcastReceiver updateNoSims(); return; } - setCurrentSubscriptions(subscriptions); + synchronized (mLock) { + setCurrentSubscriptionsLocked(subscriptions); + } updateNoSims(); recalculateEmergency(); } @@ -628,8 +633,9 @@ public class NetworkControllerImpl extends BroadcastReceiver return false; } + @GuardedBy("mLock") @VisibleForTesting - void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) { + public void setCurrentSubscriptionsLocked(List<SubscriptionInfo> subscriptions) { Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() { @Override public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) { @@ -1102,6 +1108,7 @@ public class NetworkControllerImpl extends BroadcastReceiver boolean hspaDataDistinguishable; boolean inflateSignalStrengths = false; boolean alwaysShowDataRatIcon = false; + public String patternOfCarrierSpecificDataIcon = ""; /** * Mapping from NR 5G status string to an integer. The NR 5G status string should match @@ -1140,6 +1147,8 @@ public class NetworkControllerImpl extends BroadcastReceiver CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL); config.hideLtePlus = b.getBoolean( CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL); + config.patternOfCarrierSpecificDataIcon = b.getString( + CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING); String nr5GIconConfiguration = b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING); if (!TextUtils.isEmpty(nr5GIconConfiguration)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java index e151ca3e23f3..c22ff8ba594b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java @@ -35,6 +35,7 @@ class TelephonyIcons { static final int ICON_3G = R.drawable.ic_3g_mobiledata; static final int ICON_4G = R.drawable.ic_4g_mobiledata; static final int ICON_4G_PLUS = R.drawable.ic_4g_plus_mobiledata; + static final int ICON_5G_E = R.drawable.ic_5g_e_mobiledata; static final int ICON_1X = R.drawable.ic_1x_mobiledata; static final int ICON_5G = R.drawable.ic_5g_mobiledata; static final int ICON_5G_PLUS = R.drawable.ic_5g_plus_mobiledata; @@ -204,6 +205,19 @@ class TelephonyIcons { TelephonyIcons.ICON_LTE_PLUS, true); + static final MobileIconGroup LTE_CA_5G_E = new MobileIconGroup( + "5Ge", + null, + null, + AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH, + 0, 0, + 0, + 0, + AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0], + R.string.data_connection_5ge, + TelephonyIcons.ICON_5G_E, + true); + static final MobileIconGroup NR_5G = new MobileIconGroup( "5G", null, @@ -276,6 +290,7 @@ class TelephonyIcons { ICON_NAME_TO_ICON.put("h+", H_PLUS); ICON_NAME_TO_ICON.put("4g", FOUR_G); ICON_NAME_TO_ICON.put("4g+", FOUR_G_PLUS); + ICON_NAME_TO_ICON.put("5ge", LTE_CA_5G_E); ICON_NAME_TO_ICON.put("lte", LTE); ICON_NAME_TO_ICON.put("lte+", LTE_PLUS); ICON_NAME_TO_ICON.put("5g", NR_5G); diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index 2a84c5d4d44d..9bbfd224079c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -94,7 +94,6 @@ public class Events { public static final int DISMISS_STREAM_GONE = 7; public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8; public static final int DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED = 9; - public static final int DISMISS_REASON_ODI_CAPTIONS_CLICKED = 10; public static final String[] DISMISS_REASONS = { "unknown", "touch_outside", @@ -105,8 +104,7 @@ public class Events { "done_clicked", "a11y_stream_changed", "output_chooser", - "usb_temperature_below_threshold", - "odi_captions_clicked" + "usb_temperature_below_threshold" }; public static final int SHOW_REASON_UNKNOWN = 0; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 2094b36d8294..509537089bf8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -30,7 +30,6 @@ import static android.view.View.GONE; import static android.view.View.VISIBLE; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static com.android.systemui.volume.Events.DISMISS_REASON_ODI_CAPTIONS_CLICKED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; import android.animation.ObjectAnimator; @@ -519,7 +518,6 @@ public class VolumeDialogImpl implements VolumeDialog, mODICaptionsIcon.setOnConfirmedTapListener(() -> { onCaptionIconClicked(); Events.writeEvent(mContext, Events.EVENT_ODI_CAPTIONS_CLICK); - dismissH(DISMISS_REASON_ODI_CAPTIONS_CLICKED); }, mHandler); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java index f2ad958c57ab..17fbe09e07b3 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java @@ -18,12 +18,14 @@ package com.android.keyguard.clock; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.database.ContentObserver; +import android.net.Uri; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -52,6 +54,8 @@ public final class ClockManagerTest extends SysuiTestCase { private static final String BUBBLE_CLOCK = BubbleClockController.class.getName(); private static final Class<?> BUBBLE_CLOCK_CLASS = BubbleClockController.class; + private static final int USER_ID = 0; + private static final Uri SETTINGS_URI = null; private ClockManager mClockManager; private ContentObserver mContentObserver; @@ -106,10 +110,10 @@ public final class ClockManagerTest extends SysuiTestCase { @Test public void getCurrentClock_default() { // GIVEN that settings doesn't contain any values - when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(null); - when(mMockSettingsWrapper.getDockedClockFace()).thenReturn(null); + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(null); + when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(null); // WHEN settings change event is fired - mContentObserver.onChange(false); + mContentObserver.onChange(false, SETTINGS_URI, USER_ID); // THEN the result is null, indicated the default clock face should be used. assertThat(mClockManager.getCurrentClock()).isNull(); } @@ -117,9 +121,9 @@ public final class ClockManagerTest extends SysuiTestCase { @Test public void getCurrentClock_customClock() { // GIVEN that settings is set to the bubble clock face - when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK); + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); // WHEN settings change event is fired - mContentObserver.onChange(false); + mContentObserver.onChange(false, SETTINGS_URI, USER_ID); // THEN the plugin is the bubble clock face. assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS); } @@ -127,9 +131,9 @@ public final class ClockManagerTest extends SysuiTestCase { @Test public void onClockChanged_customClock() { // GIVEN that settings is set to the bubble clock face - when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK); + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); // WHEN settings change event is fired - mContentObserver.onChange(false); + mContentObserver.onChange(false, SETTINGS_URI, USER_ID); // THEN the plugin is the bubble clock face. ArgumentCaptor<ClockPlugin> captor = ArgumentCaptor.forClass(ClockPlugin.class); verify(mMockListener1).onClockChanged(captor.capture()); @@ -139,9 +143,9 @@ public final class ClockManagerTest extends SysuiTestCase { @Test public void onClockChanged_uniqueInstances() { // GIVEN that settings is set to the bubble clock face - when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK); + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); // WHEN settings change event is fired - mContentObserver.onChange(false); + mContentObserver.onChange(false, SETTINGS_URI, USER_ID); // THEN the listeners receive separate instances of the Bubble clock plugin. ArgumentCaptor<ClockPlugin> captor1 = ArgumentCaptor.forClass(ClockPlugin.class); ArgumentCaptor<ClockPlugin> captor2 = ArgumentCaptor.forClass(ClockPlugin.class); @@ -156,9 +160,9 @@ public final class ClockManagerTest extends SysuiTestCase { public void getCurrentClock_badSettingsValue() { // GIVEN that settings contains a value that doesn't correspond to a // custom clock face. - when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn("bad value"); + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn("bad value"); // WHEN settings change event is fired - mContentObserver.onChange(false); + mContentObserver.onChange(false, SETTINGS_URI, USER_ID); // THEN the result is null. assertThat(mClockManager.getCurrentClock()).isNull(); } @@ -174,7 +178,7 @@ public final class ClockManagerTest extends SysuiTestCase { @Test public void getCurrentClock_dockedCustomClock() { // GIVEN settings is set to the bubble clock face - when(mMockSettingsWrapper.getDockedClockFace()).thenReturn(BUBBLE_CLOCK); + when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); // WHEN dock event fires mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED); // THEN the plugin is the bubble clock face. @@ -184,7 +188,7 @@ public final class ClockManagerTest extends SysuiTestCase { @Test public void getCurrentClock_badDockedSettingsValue() { // GIVEN settings contains a value that doesn't correspond to an available clock face. - when(mMockSettingsWrapper.getDockedClockFace()).thenReturn("bad value"); + when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value"); // WHEN dock event fires mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED); // THEN the result is null. @@ -195,8 +199,8 @@ public final class ClockManagerTest extends SysuiTestCase { public void getCurrentClock_badDockedSettingsFallback() { // GIVEN settings contains a value that doesn't correspond to an available clock face, but // locked screen settings is set to bubble clock. - when(mMockSettingsWrapper.getDockedClockFace()).thenReturn("bad value"); - when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK); + when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value"); + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); // WHEN dock event is fired mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED); // THEN the plugin is the bubble clock face. diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java index 1649f9845661..67df60a3dcfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java +++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java @@ -18,6 +18,11 @@ package com.android.systemui.colorextraction; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import android.app.WallpaperColors; import android.app.WallpaperManager; @@ -27,7 +32,9 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.colorextraction.types.Tonal; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.policy.ConfigurationController; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,7 +64,7 @@ public class SysuiColorExtractorTests extends SysuiTestCase { simulateEvent(extractor); extractor.setWallpaperVisible(false); - ColorExtractor.GradientColors fallbackColors = extractor.getFallbackColors(); + ColorExtractor.GradientColors fallbackColors = extractor.getNeutralColors(); for (int type : sTypes) { assertEquals("Not using fallback!", @@ -96,7 +103,7 @@ public class SysuiColorExtractorTests extends SysuiTestCase { extractor.setWallpaperVisible(true); extractor.setHasBackdrop(true); - ColorExtractor.GradientColors fallbackColors = extractor.getFallbackColors(); + ColorExtractor.GradientColors fallbackColors = extractor.getNeutralColors(); for (int type : sTypes) { assertEquals("Not using fallback!", @@ -106,6 +113,19 @@ public class SysuiColorExtractorTests extends SysuiTestCase { } } + @Test + public void onUiModeChanged_reloadsColors() { + Tonal tonal = mock(Tonal.class); + ConfigurationController configurationController = mock(ConfigurationController.class); + SysuiColorExtractor sysuiColorExtractor = new SysuiColorExtractor(getContext(), + tonal, configurationController, false /* registerVisibility */); + verify(configurationController).addCallback(eq(sysuiColorExtractor)); + + reset(tonal); + sysuiColorExtractor.onUiModeChanged(); + verify(tonal).applyFallback(any(), any()); + } + private SysuiColorExtractor getTestableExtractor(ColorExtractor.GradientColors colors) { return new SysuiColorExtractor(getContext(), (inWallpaperColors, outGradientColorsNormal, outGradientColorsDark, @@ -113,7 +133,7 @@ public class SysuiColorExtractorTests extends SysuiTestCase { outGradientColorsNormal.set(colors); outGradientColorsDark.set(colors); outGradientColorsExtraDark.set(colors); - }, false); + }, mock(ConfigurationController.class), false); } private void simulateEvent(SysuiColorExtractor extractor) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java index 2020d4b9562f..87a77577841a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java @@ -30,7 +30,7 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.colorextraction.drawable.GradientDrawable; +import com.android.internal.colorextraction.drawable.ScrimDrawable; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.utils.leaks.LeakCheckedTest; @@ -70,12 +70,10 @@ public class ScrimViewTest extends LeakCheckedTest { @Test public void testCreation_initialColor() { - GradientDrawable drawable = (GradientDrawable) mView.getDrawable(); + ScrimDrawable drawable = (ScrimDrawable) mView.getDrawable(); ColorExtractor.GradientColors colors = mView.getColors(); assertEquals("Main color should be set upon creation", drawable.getMainColor(), colors.getMainColor()); - assertEquals("Secondary color should be set upon creation", - drawable.getSecondaryColor(), colors.getSecondaryColor()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 057f752b5ad1..d2d294bf8d37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; @@ -71,6 +72,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private UnlockMethodCache mUnlockMethodCache; @Mock private TunerService mTunerService; + @Mock + private Handler mHandler; private BiometricUnlockController mBiometricUnlockController; @Before @@ -172,12 +175,24 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat()); } + @Test + public void onFinishedGoingToSleep_authenticatesWhenPending() { + when(mUpdateMonitor.isGoingToSleep()).thenReturn(true); + mBiometricUnlockController.onFinishedGoingToSleep(-1); + verify(mHandler, never()).post(any()); + + mBiometricUnlockController.onBiometricAuthenticated(1 /* userId */, + BiometricSourceType.FACE); + mBiometricUnlockController.onFinishedGoingToSleep(-1); + verify(mHandler).post(any()); + } + private class TestableBiometricUnlockController extends BiometricUnlockController { TestableBiometricUnlockController(boolean faceDismissesKeyguard) { super(mContext, mDozeScrimController, mKeyguardViewMediator, mScrimController, mStatusBar, mUnlockMethodCache, - new Handler(), mUpdateMonitor, mTunerService, 0 /* wakeUpDelay */, + mHandler, mUpdateMonitor, mTunerService, 0 /* wakeUpDelay */, faceDismissesKeyguard); mFaceDismissesKeyguard = faceDismissesKeyguard; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java index ac6544e129b4..0b53c486356f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java @@ -300,7 +300,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // We can only test whether unregister gets called if it thinks its in a listening // state. mNetworkController.mListening = true; - mNetworkController.setCurrentSubscriptions(subscriptions); + mNetworkController.setCurrentSubscriptionsLocked(subscriptions); for (int i = 0; i < testSubscriptions.length; i++) { if (i == indexToSkipController) { diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk index c57d4e9f1395..b9b3a615650f 100644 --- a/packages/overlays/Android.mk +++ b/packages/overlays/Android.mk @@ -18,6 +18,10 @@ include $(CLEAR_VARS) LOCAL_MODULE := frameworks-base-overlays LOCAL_REQUIRED_MODULES := \ AccentColorBlackOverlay \ + AccentColorCinnamonOverlay \ + AccentColorOceanOverlay \ + AccentColorOrchidOverlay \ + AccentColorSpaceOverlay \ AccentColorGreenOverlay \ AccentColorPurpleOverlay \ DisplayCutoutEmulationCornerOverlay \ diff --git a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml index 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/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index a3e7d3685100..54a3ecb22687 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -166,6 +166,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind @Override public void onUnlockUser(int userHandle) { Set<Association> associations = readAllAssociations(userHandle); + if (associations == null || associations.isEmpty()) { + return; + } Set<String> companionAppPackages = new HashSet<>(); for (Association association : associations) { companionAppPackages.add(association.companionAppPackage); diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 2cfcecca5f99..2055b64483d9 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -208,6 +208,7 @@ public class IpSecService extends IIpSecService.Stub { mBinder.linkToDeath(this, 0); } catch (RemoteException e) { binderDied(); + e.rethrowFromSystemServer(); } } } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 8847e32d0fb5..01a3a6fb25d1 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1020,11 +1020,7 @@ class UserController implements Handler.Callback { if (state.state == STATE_RUNNING_UNLOCKED) { // We'll skip all later code, so we must tell listener it's already // unlocked. - try { - unlockListener.onFinished(userId, null); - } catch (RemoteException ignore) { - // Ignore. - } + notifyFinished(userId, unlockListener); } return true; } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index d073bc60c766..4c3bb8c07728 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1840,11 +1840,14 @@ public class AppOpsService extends IAppOpsService.Stub { } private boolean isPackageSuspendedForUser(String pkg, int uid) { + final long identity = Binder.clearCallingIdentity(); try { return AppGlobals.getPackageManager().isPackageSuspendedForUser( pkg, UserHandle.getUserId(uid)); } catch (RemoteException re) { throw new SecurityException("Could not talk to package manager service"); + } finally { + Binder.restoreCallingIdentity(identity); } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3fc2d3712fed..d58888a7c67b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1960,7 +1960,7 @@ public class AudioService extends IAudioService.Stub return; } for (final int groupedStream : avg.getLegacyStreamTypes()) { - setStreamVolume(stream, index, flags, callingPackage, callingPackage, + setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage, Binder.getCallingUid()); } } @@ -3368,8 +3368,14 @@ public class AudioService extends IAudioService.Stub .append(Binder.getCallingPid()).toString(); final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(on, eventSource); if (stateChanged) { - mContext.sendBroadcast(new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED) - .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser( + new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 843ecac14b62..153133a6c669 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -85,6 +86,7 @@ import java.util.Random; public class BiometricService extends SystemService { private static final String TAG = "BiometricService"; + private static final boolean DEBUG = true; private static final int MSG_ON_TASK_STACK_CHANGED = 1; private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2; @@ -96,6 +98,9 @@ public class BiometricService extends SystemService { private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8; private static final int MSG_AUTHENTICATE = 9; private static final int MSG_CANCEL_AUTHENTICATION = 10; + private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS = 11; + private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR = 12; + private static final int MSG_REGISTER_CANCELLATION_CALLBACK = 13; private static final int[] FEATURE_ID = { TYPE_FINGERPRINT, @@ -128,8 +133,12 @@ public class BiometricService extends SystemService { * Authentication is successful, but we're waiting for the user to press "confirm" button. */ private static final int STATE_AUTH_PENDING_CONFIRM = 5; + /** + * Biometric authentication was canceled, but the device is now showing ConfirmDeviceCredential + */ + private static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6; - private final class AuthSession { + private final class AuthSession implements IBinder.DeathRecipient { // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from // <Biometric>Services before we can start authenticating. Pairs that have been returned // are moved to mModalitiesMatched. @@ -164,10 +173,14 @@ public class BiometricService extends SystemService { // Timestamp when hardware authentication occurred private long mAuthenticatedTimeMs; + // TODO(b/123378871): Remove when moved. + private IBiometricConfirmDeviceCredentialCallback mConfirmDeviceCredentialCallback; + AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, int callingUid, int callingPid, int callingUserId, - int modality, boolean requireConfirmation) { + int modality, boolean requireConfirmation, + IBiometricConfirmDeviceCredentialCallback callback) { mModalitiesWaiting = modalities; mToken = token; mSessionId = sessionId; @@ -180,12 +193,25 @@ public class BiometricService extends SystemService { mCallingUserId = callingUserId; mModality = modality; mRequireConfirmation = requireConfirmation; + mConfirmDeviceCredentialCallback = callback; + + if (isFromConfirmDeviceCredential()) { + try { + token.linkToDeath(this, 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to link to death", e); + } + } } boolean isCrypto() { return mSessionId != 0; } + boolean isFromConfirmDeviceCredential() { + return mBundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false); + } + boolean containsCookie(int cookie) { if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) { return true; @@ -195,6 +221,25 @@ public class BiometricService extends SystemService { } return false; } + + // TODO(b/123378871): Remove when moved. + @Override + public void binderDied() { + mHandler.post(() -> { + Slog.e(TAG, "Binder died, killing ConfirmDeviceCredential"); + if (mConfirmDeviceCredentialCallback == null) { + Slog.e(TAG, "Callback is null"); + return; + } + + try { + mConfirmDeviceCredentialCallback.cancel(); + mConfirmDeviceCredentialCallback = null; + } catch (RemoteException e) { + Slog.e(TAG, "Unable to send cancel", e); + } + }); + } } private final class BiometricTaskStackListener extends TaskStackListener { @@ -234,6 +279,14 @@ public class BiometricService extends SystemService { private AuthSession mCurrentAuthSession; private AuthSession mPendingAuthSession; + // TODO(b/123378871): Remove when moved. + // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the + // client (app) receiver. BiometricService internally launches CDCA which invokes + // BiometricService to start authentication (normal path). When auth is success/rejected, + // CDCA will use an aidl method to poke BiometricService - the result will then be forwarded + // to this receiver. + private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver; + private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { @@ -311,7 +364,8 @@ public class BiometricService extends SystemService { (Bundle) args.arg5 /* bundle */, args.argi2 /* callingUid */, args.argi3 /* callingPid */, - args.argi4 /* callingUserId */); + args.argi4 /* callingUserId */, + (IBiometricConfirmDeviceCredentialCallback) args.arg6 /* callback */); args.recycle(); break; } @@ -325,7 +379,28 @@ public class BiometricService extends SystemService { break; } + case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS: { + handleOnConfirmDeviceCredentialSuccess(); + break; + } + + case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR: { + SomeArgs args = (SomeArgs) msg.obj; + handleOnConfirmDeviceCredentialError( + args.argi1 /* error */, + (String) args.arg1 /* errorMsg */); + args.recycle(); + break; + } + + case MSG_REGISTER_CANCELLATION_CALLBACK: { + handleRegisterCancellationCallback( + (IBiometricConfirmDeviceCredentialCallback) msg.obj /* callback */); + break; + } + default: + Slog.e(TAG, "Unknown message: " + msg); break; } } @@ -533,14 +608,6 @@ public class BiometricService extends SystemService { * cancelAuthentication() can go to the right place. */ private final class BiometricServiceWrapper extends IBiometricService.Stub { - // TODO(b/123378871): Remove when moved. - // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the - // client (app) receiver. BiometricService internally launches CDCA which invokes - // BiometricService to start authentication (normal path). When auth is success/rejected, - // CDCA will use an aidl method to poke BiometricService - the result will then be forwarded - // to this receiver. - private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver; - @Override // Binder call public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) { checkInternalPermission(); @@ -554,12 +621,18 @@ public class BiometricService extends SystemService { @Override // Binder call public void authenticate(IBinder token, long sessionId, int userId, - IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle) + IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, + IBiometricConfirmDeviceCredentialCallback callback) throws RemoteException { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); + // TODO(b/123378871): Remove when moved. + if (callback != null) { + checkInternalPermission(); + } + // In the BiometricServiceBase, check do the AppOps and foreground check. if (userId == callingUserId) { // Check the USE_BIOMETRIC permission here. @@ -576,6 +649,12 @@ public class BiometricService extends SystemService { return; } + final boolean isFromConfirmDeviceCredential = + bundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false); + if (isFromConfirmDeviceCredential) { + checkInternalPermission(); + } + // Check the usage of this in system server. Need to remove this check if it becomes // a public API. final boolean useDefaultTitle = @@ -632,6 +711,7 @@ public class BiometricService extends SystemService { args.argi2 = callingUid; args.argi3 = callingPid; args.argi4 = callingUserId; + args.arg6 = callback; mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget(); } @@ -639,35 +719,30 @@ public class BiometricService extends SystemService { @Override // Binder call public void onConfirmDeviceCredentialSuccess() { checkInternalPermission(); - mHandler.post(() -> { - if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "onCDCASuccess null!"); - return; - } - try { - mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException", e); - } - mConfirmDeviceCredentialReceiver = null; - }); + + mHandler.sendEmptyMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS); } @Override // Binder call public void onConfirmDeviceCredentialError(int error, String message) { checkInternalPermission(); - mHandler.post(() -> { - if (mConfirmDeviceCredentialReceiver == null) { - Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message); - return; - } - try { - mConfirmDeviceCredentialReceiver.onError(error, message); - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException", e); - } - mConfirmDeviceCredentialReceiver = null; - }); + + SomeArgs args = SomeArgs.obtain(); + args.argi1 = error; + args.arg1 = message; + mHandler.obtainMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR, args).sendToTarget(); + } + + @Override // Binder call + public void registerCancellationCallback( + IBiometricConfirmDeviceCredentialCallback callback) { + // TODO(b/123378871): Remove when moved. + // This callback replaces the one stored in the current session. If the session is null + // we can ignore this, since it means ConfirmDeviceCredential was launched by something + // else (not BiometricPrompt) + checkInternalPermission(); + + mHandler.obtainMessage(MSG_REGISTER_CANCELLATION_CALLBACK, callback).sendToTarget(); } @Override // Binder call @@ -1104,6 +1179,52 @@ public class BiometricService extends SystemService { } } + private void handleOnConfirmDeviceCredentialSuccess() { + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCASuccess null!"); + return; + } + try { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded(); + if (mCurrentAuthSession != null) { + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + mConfirmDeviceCredentialReceiver = null; + } + + private void handleOnConfirmDeviceCredentialError(int error, String message) { + if (mConfirmDeviceCredentialReceiver == null) { + Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message); + return; + } + try { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + mConfirmDeviceCredentialReceiver.onError(error, message); + if (mCurrentAuthSession != null) { + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + mConfirmDeviceCredentialReceiver = null; + } + + private void handleRegisterCancellationCallback( + IBiometricConfirmDeviceCredentialCallback callback) { + if (mCurrentAuthSession == null) { + Slog.d(TAG, "Current auth session null"); + return; + } + Slog.d(TAG, "Updating cancel callback"); + mCurrentAuthSession.mConfirmDeviceCredentialCallback = callback; + } + private void handleOnError(int cookie, int error, String message) { Slog.d(TAG, "Error: " + error + " cookie: " + cookie); // Errors can either be from the current auth session or the pending auth session. @@ -1114,7 +1235,18 @@ public class BiometricService extends SystemService { // of their intended receivers. try { if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) { - if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { + + if (mCurrentAuthSession.isFromConfirmDeviceCredential()) { + // If we were invoked by ConfirmDeviceCredential, do not delete the current + // auth session since we still need to respond to cancel signal while + if (DEBUG) Slog.d(TAG, "From CDC, transition to CANCELED_SHOWING_CDC state"); + + // Send the error to ConfirmDeviceCredential so that it goes to Pin/Pattern/Pass + // screen + mCurrentAuthSession.mClientReceiver.onError(error, message); + mCurrentAuthSession.mState = STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC; + mStatusBarService.hideBiometricDialog(); + } else if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { mStatusBarService.onBiometricError(message); if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { mActivityTaskManager.unregisterTaskStackListener( @@ -1214,9 +1346,16 @@ public class BiometricService extends SystemService { KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow); mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(); } - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; + + // Do not clean up yet if we are from ConfirmDeviceCredential. We should be in the + // STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC. The session should only be removed when + // ConfirmDeviceCredential is confirmed or canceled. + // TODO(b/123378871): Remove when moved + if (!mCurrentAuthSession.isFromConfirmDeviceCredential()) { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } @@ -1235,7 +1374,8 @@ public class BiometricService extends SystemService { mCurrentAuthSession.mCallingUid, mCurrentAuthSession.mCallingPid, mCurrentAuthSession.mCallingUserId, - mCurrentAuthSession.mModality); + mCurrentAuthSession.mModality, + mCurrentAuthSession.mConfirmDeviceCredentialCallback); } private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation, @@ -1290,7 +1430,8 @@ public class BiometricService extends SystemService { private void handleAuthenticate(IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, - int callingUid, int callingPid, int callingUserId) { + int callingUid, int callingPid, int callingUserId, + IBiometricConfirmDeviceCredentialCallback callback) { mHandler.post(() -> { final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId); @@ -1328,7 +1469,7 @@ public class BiometricService extends SystemService { // Start preparing for authentication. Authentication starts when // all modalities requested have invoked onReadyForAuthentication. authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, - callingUid, callingPid, callingUserId, modality); + callingUid, callingPid, callingUserId, modality, callback); }); } @@ -1343,7 +1484,8 @@ public class BiometricService extends SystemService { */ private void authenticateInternal(IBinder token, long sessionId, int userId, IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, - int callingUid, int callingPid, int callingUserId, int modality) { + int callingUid, int callingPid, int callingUserId, int modality, + IBiometricConfirmDeviceCredentialCallback callback) { try { boolean requireConfirmation = bundle.getBoolean( BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */); @@ -1363,7 +1505,7 @@ public class BiometricService extends SystemService { authenticators.put(modality, cookie); mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId, receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, - modality, requireConfirmation); + modality, requireConfirmation, callback); mPendingAuthSession.mState = STATE_AUTH_CALLED; // No polymorphism :( if ((modality & TYPE_FINGERPRINT) != 0) { @@ -1390,10 +1532,23 @@ public class BiometricService extends SystemService { return; } - // We need to check the current authenticators state. If we're pending confirm - // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client, - // since we won't be getting an onError from the driver. - if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + if (mCurrentAuthSession != null + && mCurrentAuthSession.mState == STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC) { + if (DEBUG) Slog.d(TAG, "Cancel received while ConfirmDeviceCredential showing"); + try { + mCurrentAuthSession.mConfirmDeviceCredentialCallback.cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to cancel ConfirmDeviceCredential", e); + } + + // TODO(b/123378871): Remove when moved. Piggy back on this for now to clean up. + handleOnConfirmDeviceCredentialError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, + getContext().getString(R.string.biometric_error_canceled)); + } else if (mCurrentAuthSession != null + && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { + // We need to check the current authenticators state. If we're pending confirm + // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client, + // since we won't be getting an onError from the driver. try { // Send error to client mCurrentAuthSession.mClientReceiver.onError( @@ -1409,11 +1564,22 @@ public class BiometricService extends SystemService { Slog.e(TAG, "Remote exception", e); } } else { - cancelInternal(token, opPackageName, true /* fromClient */); + boolean fromCDC = false; + if (mCurrentAuthSession != null) { + fromCDC = mCurrentAuthSession.mBundle.getBoolean( + BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false); + } + + if (fromCDC) { + if (DEBUG) Slog.d(TAG, "Cancelling from CDC"); + cancelInternal(token, opPackageName, false /* fromClient */); + } else { + cancelInternal(token, opPackageName, true /* fromClient */); + } + } } - void cancelInternal(IBinder token, String opPackageName, boolean fromClient) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 527539d8ce0d..7733d6779e48 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -50,7 +50,7 @@ import java.util.List; import java.util.Set; /** - * CameraServiceProxy is the system_server analog to the camera service running in mediaserver. + * CameraServiceProxy is the system_server analog to the camera service running in cameraserver. * * @hide */ @@ -74,6 +74,7 @@ public class CameraServiceProxy extends SystemService private static final int MSG_SWITCH_USER = 1; private static final int RETRY_DELAY_TIME = 20; //ms + private static final int RETRY_TIMES = 30; // Maximum entries to keep in usage history before dumping out private static final int MAX_USAGE_HISTORY = 100; @@ -171,7 +172,7 @@ public class CameraServiceProxy extends SystemService " camera service UID!"); return; } - notifySwitchWithRetries(30); + notifySwitchWithRetries(RETRY_TIMES); } @Override @@ -242,7 +243,8 @@ public class CameraServiceProxy extends SystemService public void onStartUser(int userHandle) { synchronized(mLock) { if (mEnabledCameraUsers == null) { - // Initialize mediaserver, or update mediaserver if we are recovering from a crash. + // Initialize cameraserver, or update cameraserver if we are recovering + // from a crash. switchUserLocked(userHandle); } } @@ -324,9 +326,9 @@ public class CameraServiceProxy extends SystemService Set<Integer> currentUserHandles = getEnabledUserHandles(userHandle); mLastUser = userHandle; if (mEnabledCameraUsers == null || !mEnabledCameraUsers.equals(currentUserHandles)) { - // Some user handles have been added or removed, update mediaserver. + // Some user handles have been added or removed, update cameraserver. mEnabledCameraUsers = currentUserHandles; - notifyMediaserverLocked(ICameraService.EVENT_USER_SWITCHED, currentUserHandles); + notifySwitchWithRetriesLocked(RETRY_TIMES); } } @@ -343,12 +345,16 @@ public class CameraServiceProxy extends SystemService private void notifySwitchWithRetries(int retries) { synchronized(mLock) { - if (mEnabledCameraUsers == null) { - return; - } - if (notifyMediaserverLocked(ICameraService.EVENT_USER_SWITCHED, mEnabledCameraUsers)) { - retries = 0; - } + notifySwitchWithRetriesLocked(retries); + } + } + + private void notifySwitchWithRetriesLocked(int retries) { + if (mEnabledCameraUsers == null) { + return; + } + if (notifyCameraserverLocked(ICameraService.EVENT_USER_SWITCHED, mEnabledCameraUsers)) { + retries = 0; } if (retries <= 0) { return; @@ -358,13 +364,13 @@ public class CameraServiceProxy extends SystemService RETRY_DELAY_TIME); } - private boolean notifyMediaserverLocked(int eventType, Set<Integer> updatedUserHandles) { - // Forward the user switch event to the native camera service running in the mediaserver + private boolean notifyCameraserverLocked(int eventType, Set<Integer> updatedUserHandles) { + // Forward the user switch event to the native camera service running in the cameraserver // process. if (mCameraServiceRaw == null) { IBinder cameraServiceBinder = getBinderService(CAMERA_SERVICE_BINDER_NAME); if (cameraServiceBinder == null) { - Slog.w(TAG, "Could not notify mediaserver, camera service not available."); + Slog.w(TAG, "Could not notify cameraserver, camera service not available."); return false; // Camera service not active, cannot evict user clients. } try { @@ -380,7 +386,7 @@ public class CameraServiceProxy extends SystemService try { mCameraServiceRaw.notifySystemEvent(eventType, toArray(updatedUserHandles)); } catch (RemoteException e) { - Slog.w(TAG, "Could not notify mediaserver, remote exception: " + e); + Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e); // Not much we can do if camera service is dead. return false; } diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 9590f8143713..a7d0a5c706b6 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -461,7 +461,9 @@ public final class ColorDisplayService extends SystemService { .setMatrix(mNightDisplayTintController.getColorTemperatureSetting()); } - updateDisplayWhiteBalanceStatus(); + if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) { + updateDisplayWhiteBalanceStatus(); + } final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class); dtm.setColorMode(mode, mNightDisplayTintController.getMatrix()); @@ -624,7 +626,11 @@ public final class ColorDisplayService extends SystemService { return false; } return Secure.getIntForUser(getContext().getContentResolver(), - Secure.DISPLAY_WHITE_BALANCE_ENABLED, 0, mCurrentUser) == 1; + Secure.DISPLAY_WHITE_BALANCE_ENABLED, + getContext().getResources() + .getBoolean(R.bool.config_displayWhiteBalanceEnabledDefault) ? 1 + : 0, + mCurrentUser) == 1; } private boolean isDeviceColorManagedInternal() { diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java index 026837f07356..d6aa2ba02f1f 100644 --- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java +++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java @@ -28,6 +28,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; @@ -73,15 +74,27 @@ public class DisplayTransformManager { private static final int SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR = 1023; private static final int SURFACE_FLINGER_TRANSACTION_QUERY_COLOR_MANAGED = 1030; - private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation"; - private static final String PERSISTENT_PROPERTY_DISPLAY_COLOR = "persist.sys.sf.native_mode"; + @VisibleForTesting + static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation"; + @VisibleForTesting + static final String PERSISTENT_PROPERTY_DISPLAY_COLOR = "persist.sys.sf.native_mode"; private static final float COLOR_SATURATION_NATURAL = 1.0f; private static final float COLOR_SATURATION_BOOSTED = 1.1f; + /** + * Display color modes defined by DisplayColorSetting in + * frameworks/native/services/surfaceflinger/SurfaceFlinger.h. + */ private static final int DISPLAY_COLOR_MANAGED = 0; private static final int DISPLAY_COLOR_UNMANAGED = 1; private static final int DISPLAY_COLOR_ENHANCED = 2; + /** + * Display color mode range reserved for vendor customizations by the RenderIntent definition in + * hardware/interfaces/graphics/common/1.1/types.hal. + */ + private static final int VENDOR_MODE_RANGE_MIN = 256; // 0x100 + private static final int VENDOR_MODE_RANGE_MAX = 511; // 0x1ff /** * Map of level -> color transformation matrix. @@ -257,7 +270,11 @@ public class DisplayTransformManager { } else if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) { applySaturation(COLOR_SATURATION_NATURAL); setDisplayColor(DISPLAY_COLOR_ENHANCED); + } else if (colorMode >= VENDOR_MODE_RANGE_MIN && colorMode <= VENDOR_MODE_RANGE_MAX) { + applySaturation(COLOR_SATURATION_NATURAL); + setDisplayColor(colorMode); } + setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, nightDisplayMatrix); updateConfiguration(); diff --git a/services/core/java/com/android/server/gpu/GpuService.java b/services/core/java/com/android/server/gpu/GpuService.java index 647727f795da..0f73f379900b 100644 --- a/services/core/java/com/android/server/gpu/GpuService.java +++ b/services/core/java/com/android/server/gpu/GpuService.java @@ -36,6 +36,7 @@ import android.os.Build; import android.os.Handler; import android.os.SystemProperties; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.provider.Settings; import android.util.Base64; import android.util.Slog; @@ -69,9 +70,11 @@ public class GpuService extends SystemService { private final String mDriverPackageName; private final PackageManager mPackageManager; private final Object mLock = new Object(); + private final Object mDeviceConfigLock = new Object(); private ContentResolver mContentResolver; private long mGameDriverVersionCode; private SettingsObserver mSettingsObserver; + private DeviceConfigListener mDeviceConfigListener; @GuardedBy("mLock") private Blacklists mBlacklists; @@ -101,10 +104,11 @@ public class GpuService extends SystemService { public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { mContentResolver = mContext.getContentResolver(); - mSettingsObserver = new SettingsObserver(); if (mDriverPackageName == null || mDriverPackageName.isEmpty()) { return; } + mSettingsObserver = new SettingsObserver(); + mDeviceConfigListener = new DeviceConfigListener(); fetchGameDriverPackageProperties(); processBlacklists(); setBlacklist(); @@ -134,6 +138,24 @@ public class GpuService extends SystemService { } } + private final class DeviceConfigListener implements DeviceConfig.OnPropertyChangedListener { + + DeviceConfigListener() { + super(); + DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_GAME_DRIVER, + mContext.getMainExecutor(), this); + } + @Override + public void onPropertyChanged(String namespace, String name, String value) { + synchronized (mDeviceConfigLock) { + if (Settings.Global.GAME_DRIVER_BLACKLISTS.equals(name)) { + parseBlacklists(value != null ? value : ""); + setBlacklist(); + } + } + } + } + private final class PackageReceiver extends BroadcastReceiver { @Override public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { @@ -229,13 +251,17 @@ public class GpuService extends SystemService { } private void processBlacklists() { - // TODO(b/121350991) Switch to DeviceConfig with property listener. - String base64String = - Settings.Global.getString(mContentResolver, Settings.Global.GAME_DRIVER_BLACKLISTS); - if (base64String == null || base64String.isEmpty()) { - return; + String base64String = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_GAME_DRIVER, + Settings.Global.GAME_DRIVER_BLACKLISTS); + if (base64String == null) { + base64String = + Settings.Global.getString(mContentResolver, + Settings.Global.GAME_DRIVER_BLACKLISTS); } + parseBlacklists(base64String != null ? base64String : ""); + } + private void parseBlacklists(String base64String) { synchronized (mLock) { // Reset all blacklists mBlacklists = null; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 6f1929fd464a..d360a6362464 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -4101,13 +4101,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- - boolean setInputMethodEnabledLocked(String id, boolean enabled) { - // Make sure this is a valid input method. - InputMethodInfo imm = mMethodMap.get(id); - if (imm == null) { - throw new IllegalArgumentException("Unknown id: " + mCurMethodId); - } - + /** + * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}. + * + * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently not + * recognized by the system. + * @param enabled {@code true} if {@code id} needs to be enabled. + * @return {@code true} if the IME was previously enabled. {@code false} otherwise. + */ + private boolean setInputMethodEnabledLocked(String id, boolean enabled) { List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings .getEnabledInputMethodsAndSubtypeListLocked(); @@ -4697,6 +4699,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!userHasDebugPriv(mSettings.getCurrentUserId(), shellCommand)) { return ShellCommandResult.SUCCESS; } + // Make sure this is a valid input method. + if (enabled && !mMethodMap.containsKey(id)) { + final PrintWriter error = shellCommand.getErrPrintWriter(); + error.print("Unknown input method "); + error.print(id); + error.println(" cannot be enabled"); + return ShellCommandResult.SUCCESS; + } previouslyEnabled = setInputMethodEnabledLocked(id, enabled); } final PrintWriter pr = shellCommand.getOutPrintWriter(); diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java index 0cabf1d35206..de36deafa4d7 100644 --- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java @@ -1489,8 +1489,10 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { final long token = Binder.clearCallingIdentity(); if (DEBUG_KEY_EVENT) { - Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName + ", pid=" + pid + ", uid=" - + uid + ", asSystem=" + asSystemService + ", event=" + keyEvent); + Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName + + ", opPkg=" + opPackageName + ", pid=" + pid + ", uid=" + uid + + ", asSystem=" + asSystemService + ", event=" + keyEvent + + ", stream=" + stream + ", musicOnly=" + musicOnly); } try { diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 15599111f6e5..f34ace55a72e 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -25,6 +25,7 @@ import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.ConnectivityManager.isNetworkTypeMobile; +import static android.net.NetworkStack.checkNetworkStackPermission; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.INTERFACES_ALL; @@ -866,7 +867,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { VpnInfo[] vpnArray, NetworkState[] networkStates, String activeIface) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); + checkNetworkStackPermission(mContext); assertBandwidthControlEnabled(); final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 0488d3a822ad..4a6eb276bd02 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -79,7 +79,6 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.function.Predicate; /** * Manages the lifecycle of application-provided services bound by system server. @@ -1163,6 +1162,7 @@ abstract public class ManagedServices { @Override public void onNullBinding(ComponentName name) { Slog.v(TAG, "onNullBinding() called with: name = [" + name + "]"); + mServicesBound.remove(servicesBindingTag); } }; if (!mContext.bindServiceAsUser(intent, @@ -1180,6 +1180,11 @@ abstract public class ManagedServices { } } + boolean isBound(ComponentName cn, int userId) { + final Pair<ComponentName, Integer> servicesBindingTag = Pair.create(cn, userId); + return mServicesBound.contains(servicesBindingTag); + } + /** * Remove a service for the given user by ComponentName */ diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f2e56b589bd5..7f1b25ca3ca3 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -29,6 +29,7 @@ import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_ import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; @@ -1515,6 +1516,11 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting + void setZenHelper(ZenModeHelper zenHelper) { + mZenModeHelper = zenHelper; + } + + @VisibleForTesting void setIsAutomotive(boolean isAutomotive) { mIsAutomotive = isAutomotive; } @@ -2855,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() @@ -2863,20 +2869,20 @@ public class NotificationManagerService extends SystemService { throw new SecurityException("Not currently an assistant"); } - return mAssistants.getAllowedAssistantCapabilities(); + return mAssistants.getAllowedAssistantAdjustments(); } @Override - public void allowAssistantCapability(String adjustmentType) { - checkCallerIsSystemOrShell(); + public void allowAssistantAdjustment(String adjustmentType) { + checkCallerIsSystemOrSystemUiOrShell(); mAssistants.allowAdjustmentType(adjustmentType); handleSavePolicyFile(); } @Override - public void disallowAssistantCapability(String adjustmentType) { - checkCallerIsSystemOrShell(); + public void disallowAssistantAdjustment(String adjustmentType) { + checkCallerIsSystemOrSystemUiOrShell(); mAssistants.disallowAdjustmentType(adjustmentType); handleSavePolicyFile(); @@ -3419,8 +3425,7 @@ public class NotificationManagerService extends SystemService { } @Override - public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) - throws RemoteException { + public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) { Preconditions.checkNotNull(automaticZenRule, "automaticZenRule is null"); Preconditions.checkNotNull(automaticZenRule.getName(), "Name is null"); if (automaticZenRule.getOwner() == null @@ -3429,6 +3434,11 @@ public class NotificationManagerService extends SystemService { "Rule must have a conditionproviderservice and/or configuration activity"); } Preconditions.checkNotNull(automaticZenRule.getConditionId(), "ConditionId is null"); + if (automaticZenRule.getZenPolicy() != null + && automaticZenRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) { + throw new IllegalArgumentException("ZenPolicy is only applicable to " + + "INTERRUPTION_FILTER_PRIORITY filters"); + } enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule"); return mZenModeHelper.addAutomaticZenRule(automaticZenRule, @@ -3558,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( @@ -3806,7 +3816,7 @@ public class NotificationManagerService extends SystemService { @Override public ComponentName getAllowedNotificationAssistantForUser(int userId) { - checkCallerIsSystem(); + checkCallerIsSystemOrSystemUiOrShell(); List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); if (allowedComponents.size() > 1) { throw new IllegalStateException( @@ -3889,7 +3899,7 @@ public class NotificationManagerService extends SystemService { @Override public void setNotificationAssistantAccessGrantedForUser(ComponentName assistant, int userId, boolean granted) { - checkCallerIsSystemOrShell(); + checkCallerIsSystemOrSystemUiOrShell(); mAssistants.setUserSet(userId, true); final long identity = Binder.clearCallingIdentity(); try { @@ -3924,10 +3934,6 @@ public class NotificationManagerService extends SystemService { } } if (!foundEnqueued) { - // adjustment arrived too late to apply to enqueued; apply to posted - // However, since the notification is now posted and may have alerted, - // ignore any importance related adjustments - adjustment.getSignals().remove(Adjustment.KEY_IMPORTANCE); applyAdjustmentFromAssistant(token, adjustment); } } @@ -4118,7 +4124,7 @@ public class NotificationManagerService extends SystemService { } return; } - if (mAllowedManagedServicePackages.test(assistant.getPackageName(), userId, + if (!granted || mAllowedManagedServicePackages.test(assistant.getPackageName(), userId, mAssistants.getRequiredPermission())) { mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(), userId, false, granted); @@ -6990,6 +6996,16 @@ public class NotificationManagerService extends SystemService { throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid()); } + private void checkCallerIsSystemOrSystemUiOrShell() { + if (Binder.getCallingUid() == Process.SHELL_UID) { + return; + } + if (isCallerSystemOrPhone()) { + return; + } + getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE, null); + } + private void checkCallerIsSystemOrSameApp(String pkg) { if (isCallerSystemOrPhone()) { return; @@ -7297,7 +7313,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mLock") private ArrayMap<Integer, Boolean> mUserSetMap = new ArrayMap<>(); - private List<String> mAllowedAdjustments = new ArrayList<>(); + private Set<String> mAllowedAdjustments = new ArraySet<>(); public NotificationAssistants(Context context, Object lock, UserProfiles up, IPackageManager pm) { @@ -7385,15 +7401,21 @@ public class NotificationManagerService extends SystemService { synchronized (mLock) { mAllowedAdjustments.add(type); } + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> notifyCapabilitiesChanged(info)); + } } protected void disallowAdjustmentType(String type) { synchronized (mLock) { mAllowedAdjustments.remove(type); } + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> notifyCapabilitiesChanged(info)); + } } - protected List<String> getAllowedAssistantCapabilities() { + protected List<String> getAllowedAssistantAdjustments() { synchronized (mLock) { List<String> types = new ArrayList<>(); types.addAll(mAllowedAdjustments); @@ -7450,6 +7472,15 @@ public class NotificationManagerService extends SystemService { setUserSet(userId, userSet); } + private void notifyCapabilitiesChanged(final ManagedServiceInfo info) { + final INotificationListener assistant = (INotificationListener) info.service; + try { + assistant.onAllowedAdjustmentsChanged(); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (capabilities): " + assistant, ex); + } + } + private void notifySeen(final ManagedServiceInfo info, final ArrayList<String> keys) { final INotificationListener assistant = (INotificationListener) info.service; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index ea7bf2d23495..7e74cc2368cd 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -389,8 +389,8 @@ public class ZenModeHelper { if (mConfig == null) return; newConfig = mConfig.copy(); + setAutomaticZenRuleStateLocked(newConfig, newConfig.automaticRules.get(id), condition); } - setAutomaticZenRuleState(newConfig, newConfig.automaticRules.get(id), condition); } public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition) { @@ -398,14 +398,15 @@ public class ZenModeHelper { synchronized (mConfig) { if (mConfig == null) return; newConfig = mConfig.copy(); - } - setAutomaticZenRuleState(newConfig, - findMatchingRule(newConfig, ruleDefinition, condition), - condition); + setAutomaticZenRuleStateLocked(newConfig, + findMatchingRule(newConfig, ruleDefinition, condition), + condition); + } } - private void setAutomaticZenRuleState(ZenModeConfig config, ZenRule rule, Condition condition) { + private void setAutomaticZenRuleStateLocked(ZenModeConfig config, ZenRule rule, + Condition condition) { if (rule == null) return; rule.condition = condition; diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 944aef5ab223..21b6f12b6f8b 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -324,6 +324,8 @@ class ApexManager { ipw.println("State: ROLLBACK IN PROGRESS"); } else if (si.isRolledBack) { ipw.println("State: ROLLED BACK"); + } else if (si.isRollbackFailed) { + ipw.println("State: ROLLBACK FAILED"); } ipw.decreaseIndent(); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 898437ddae41..6c5abe49fe13 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -757,124 +757,9 @@ public class PackageManagerService extends IPackageManager.Stub @Override public final boolean hasFeature(String feature) { return PackageManagerService.this.hasSystemFeature(feature, 0); } - - final List<PackageParser.Package> getStaticOverlayPackages( - Collection<PackageParser.Package> allPackages, String targetPackageName) { - if ("android".equals(targetPackageName)) { - // Static RROs targeting to "android", ie framework-res.apk, are already applied by - // native AssetManager. - return null; - } - - List<PackageParser.Package> overlayPackages = null; - for (PackageParser.Package p : allPackages) { - if (targetPackageName.equals(p.mOverlayTarget) && p.mOverlayIsStatic) { - if (overlayPackages == null) { - overlayPackages = new ArrayList<>(); - } - overlayPackages.add(p); - } - } - if (overlayPackages != null) { - Comparator<PackageParser.Package> cmp = - Comparator.comparingInt(p -> p.mOverlayPriority); - overlayPackages.sort(cmp); - } - return overlayPackages; - } - - final String[] getStaticOverlayPaths(List<PackageParser.Package> overlayPackages, - String targetPath) { - if (overlayPackages == null || overlayPackages.isEmpty()) { - return null; - } - List<String> overlayPathList = null; - for (PackageParser.Package overlayPackage : overlayPackages) { - if (targetPath == null) { - if (overlayPathList == null) { - overlayPathList = new ArrayList<>(); - } - overlayPathList.add(overlayPackage.baseCodePath); - continue; - } - - try { - // Creates idmaps for system to parse correctly the Android manifest of the - // target package. - // - // OverlayManagerService will update each of them with a correct gid from its - // target package app id. - mInstaller.idmap(targetPath, overlayPackage.baseCodePath, - UserHandle.getSharedAppGid( - UserHandle.getUserGid(UserHandle.USER_SYSTEM))); - if (overlayPathList == null) { - overlayPathList = new ArrayList<>(); - } - overlayPathList.add(overlayPackage.baseCodePath); - } catch (InstallerException e) { - Slog.e(TAG, "Failed to generate idmap for " + targetPath + " and " + - overlayPackage.baseCodePath); - } - } - return overlayPathList == null ? null : overlayPathList.toArray(new String[0]); - } - - String[] getStaticOverlayPaths(String targetPackageName, String targetPath) { - List<PackageParser.Package> overlayPackages; - synchronized (mInstallLock) { - synchronized (mPackages) { - overlayPackages = getStaticOverlayPackages( - mPackages.values(), targetPackageName); - } - // It is safe to keep overlayPackages without holding mPackages because static overlay - // packages can't be uninstalled or disabled. - return getStaticOverlayPaths(overlayPackages, targetPath); - } - } - - @Override public final String[] getOverlayApks(String targetPackageName) { - return getStaticOverlayPaths(targetPackageName, null); - } - - @Override public final String[] getOverlayPaths(String targetPackageName, - String targetPath) { - return getStaticOverlayPaths(targetPackageName, targetPath); - } - } - - class ParallelPackageParserCallback extends PackageParserCallback { - List<PackageParser.Package> mOverlayPackages = null; - - void findStaticOverlayPackages() { - synchronized (mPackages) { - for (PackageParser.Package p : mPackages.values()) { - if (p.mOverlayIsStatic) { - if (mOverlayPackages == null) { - mOverlayPackages = new ArrayList<>(); - } - mOverlayPackages.add(p); - } - } - } - } - - @Override - synchronized String[] getStaticOverlayPaths(String targetPackageName, String targetPath) { - // We can trust mOverlayPackages without holding mPackages because package uninstall - // can't happen while running parallel parsing. - // And we can call mInstaller inside getStaticOverlayPaths without holding mInstallLock - // because mInstallLock is held before running parallel parsing. - // Moreover holding mPackages or mInstallLock on each parsing thread causes dead-lock. - return mOverlayPackages == null ? null : - getStaticOverlayPaths( - getStaticOverlayPackages(mOverlayPackages, targetPackageName), - targetPath); - } } final PackageParser.Callback mPackageParserCallback = new PackageParserCallback(); - final ParallelPackageParserCallback mParallelPackageParserCallback = - new ParallelPackageParserCallback(); // Currently known shared libraries. final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries = new ArrayMap<>(); @@ -2558,8 +2443,6 @@ public class PackageManagerService extends IPackageManager.Stub | SCAN_AS_ODM, 0); - mParallelPackageParserCallback.findStaticOverlayPackages(); - // Find base frameworks (resource packages without code). scanDirTracedLI(frameworkDir, mDefParseFlags @@ -8782,7 +8665,7 @@ public class PackageManagerService extends IPackageManager.Stub } try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser( mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir, - mParallelPackageParserCallback)) { + mPackageParserCallback)) { // Submit files for parsing in parallel int fileCount = 0; for (File file : files) { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index a0f0a3178d1b..1908b3f8b366 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -565,7 +565,8 @@ public class StagingManager { // isRollbackInProgress is included to cover the scenario, when a device is rebooted in // during the rollback, and apexd fails to resume the rollback after reboot. return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown - || apexSessionInfo.isRolledBack || apexSessionInfo.isRollbackInProgress; + || apexSessionInfo.isRolledBack || apexSessionInfo.isRollbackInProgress + || apexSessionInfo.isRollbackFailed; } @GuardedBy("mStagedSessions") diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 5df2f86d6ad7..35fa9406eb79 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -302,6 +302,7 @@ public final class DefaultPermissionGrantPolicy { } public void grantDefaultPermissions(int userId) { + removeSystemFixedStorage(userId); grantPermissionsToSysComponentsAndPrivApps(userId); grantDefaultSystemHandlerPermissions(userId); grantDefaultPermissionExceptions(userId); @@ -310,6 +311,46 @@ public final class DefaultPermissionGrantPolicy { } } + // STOPSHIP: This is meant to fix the devices messed up by storage permission model 2 and + // should be removed once all devices were updated + private void removeSystemFixedStorage(int userId) { + List<PackageInfo> packages = mContext.getPackageManager().getInstalledPackagesAsUser( + DEFAULT_PACKAGE_INFO_QUERY_FLAGS, userId); + + for (PackageInfo pkg : packages) { + if (pkg == null || pkg.requestedPermissions == null) { + continue; + } + + for (String permission : pkg.requestedPermissions) { + if (!(Manifest.permission.READ_EXTERNAL_STORAGE.equals(permission) + || Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission))) { + continue; + } + + int flags = mContext.getPackageManager().getPermissionFlags(permission, + pkg.packageName, UserHandle.of(userId)); + if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) == 0) { + continue; + } + + Log.v(TAG, "Removing system fixed " + pkg.packageName + "/" + permission); + mContext.getPackageManager().updatePermissionFlags(permission, pkg.packageName, + PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, UserHandle.of(userId)); + + if (!doesPackageSupportRuntimePermissions(pkg) + || (flags & (PackageManager.FLAG_PERMISSION_USER_SET + | PackageManager.FLAG_PERMISSION_POLICY_FIXED)) != 0) { + continue; + } + + Log.v(TAG, "Revoking " + pkg.packageName + "/" + permission); + mContext.getPackageManager().revokeRuntimePermission(pkg.packageName, permission, + UserHandle.of(userId)); + } + } + } + private void grantRuntimePermissionsForSystemPackage(int userId, PackageInfo pkg) { Set<String> permissions = new ArraySet<>(); for (String permission : pkg.requestedPermissions) { @@ -1322,6 +1363,9 @@ public final class DefaultPermissionGrantPolicy { private PackageInfo getPackageInfo(String pkg, @PackageManager.PackageInfoFlags int extraFlags) { + if (pkg == null) { + return null; + } try { return mContext.getPackageManager().getPackageInfo(pkg, DEFAULT_PACKAGE_INFO_QUERY_FLAGS | extraFlags); diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 3011808d281c..67f30dc2e9fc 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -22,25 +22,28 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal.PackageListObserver; import android.content.pm.PackageParser; import android.content.pm.PermissionInfo; +import android.os.Process; import android.os.UserHandle; -import android.os.UserManagerInternal; import android.permission.PermissionControllerManager; import android.permission.PermissionManagerInternal; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; -import com.android.internal.util.function.QuadConsumer; -import com.android.internal.util.function.TriConsumer; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; /** @@ -51,11 +54,17 @@ import java.util.concurrent.CountDownLatch; * and app ops - and vise versa. */ public final class PermissionPolicyService extends SystemService { + private static final String PLATFORM_PACKAGE = "android"; private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName(); + // No need to lock as this is populated on boot when the OS is + // single threaded and is never mutated until a reboot. + private static final ArraySet<String> sAllRestrictedPermissions = new ArraySet<>(); + public PermissionPolicyService(@NonNull Context context) { super(context); + cacheAllRestrictedPermissions(context); } @Override @@ -89,6 +98,20 @@ public final class PermissionPolicyService extends SystemService { startWatchingRuntimePermissionChanges(getContext(), userId); } + private static void cacheAllRestrictedPermissions(@NonNull Context context) { + try { + final PackageInfo packageInfo = context.getPackageManager() + .getPackageInfo(PLATFORM_PACKAGE, PackageManager.GET_PERMISSIONS); + for (PermissionInfo permissionInfo : packageInfo.permissions) { + if (permissionInfo.isRestricted()) { + sAllRestrictedPermissions.add(permissionInfo.name); + } + } + } catch (NameNotFoundException impossible) { + /* cannot happen */ + } + } + private static void grantOrUpgradeDefaultRuntimePermissionsInNeeded(@NonNull Context context, @UserIdInt int userId) { final PackageManagerInternal packageManagerInternal = LocalServices.getService( @@ -123,16 +146,6 @@ public final class PermissionPolicyService extends SystemService { } } - private static void onRestrictedPermissionEnabledChange(@NonNull Context context) { - final PermissionManagerInternal permissionManagerInternal = LocalServices - .getService(PermissionManagerInternal.class); - final UserManagerInternal userManagerInternal = LocalServices.getService( - UserManagerInternal.class); - for (int userId : userManagerInternal.getUserIds()) { - synchronizePermissionsAndAppOpsForUser(context, userId); - } - } - private static void startWatchingRuntimePermissionChanges(@NonNull Context context, int userId) { final PermissionManagerInternal permissionManagerInternal = LocalServices.getService( @@ -149,40 +162,66 @@ public final class PermissionPolicyService extends SystemService { @NonNull String packageName, @UserIdInt int userId) { final PackageManagerInternal packageManagerInternal = LocalServices.getService( PackageManagerInternal.class); - final PackageParser.Package pkg = packageManagerInternal - .getPackage(packageName); - if (pkg != null) { - PermissionToOpSynchronizer.syncPackage(context, pkg, userId); + final PackageParser.Package pkg = packageManagerInternal.getPackage(packageName); + if (pkg == null) { + return; + } + final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser(context); + synchroniser.addPackage(context, pkg, userId); + final String[] sharedPkgNames = packageManagerInternal.getPackagesForSharedUserId( + pkg.mSharedUserId, userId); + if (sharedPkgNames != null) { + for (String sharedPkgName : sharedPkgNames) { + final PackageParser.Package sharedPkg = packageManagerInternal + .getPackage(sharedPkgName); + if (sharedPkg != null) { + synchroniser.addPackage(context, sharedPkg, userId); + } + } } + synchroniser.syncPackages(); } private static void synchronizePermissionsAndAppOpsForUser(@NonNull Context context, @UserIdInt int userId) { final PackageManagerInternal packageManagerInternal = LocalServices.getService( PackageManagerInternal.class); - final PermissionToOpSynchronizer synchronizer = new PermissionToOpSynchronizer(context); + final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser(context); packageManagerInternal.forEachPackage((pkg) -> synchronizer.addPackage(context, pkg, userId)); synchronizer.syncPackages(); } - private static class PermissionToOpSynchronizer { + /** + * Synchronizes permission to app ops. You *must* always sync all packages + * in a shared UID at the same time to ensure proper synchronization. + */ + private static class PermissionToOpSynchroniser { private final @NonNull Context mContext; + private final @NonNull SparseIntArray mUids = new SparseIntArray(); private final @NonNull SparseArray<String> mPackageNames = new SparseArray<>(); private final @NonNull SparseIntArray mAllowedUidOps = new SparseIntArray(); private final @NonNull SparseIntArray mDefaultUidOps = new SparseIntArray(); - PermissionToOpSynchronizer(@NonNull Context context) { + PermissionToOpSynchroniser(@NonNull Context context) { mContext = context; } - private void addPackage(@NonNull Context context, - @NonNull PackageParser.Package pkg, @UserIdInt int userId) { - addPackage(context, pkg, userId, this::addAllowedEntry, this::addIgnoredEntry); - } - void syncPackages() { + // TRICKY: we set the app op for a restricted permission to allow if the app + // requesting the permission is whitelisted and to deny if the app requesting + // the permission is not whitelisted. However, there is another case where an + // app in a shared user can access a component in another app in the same shared + // user due to being in the same shared user and not by having the permission + // that guards the component form the rest of the world. We need to handle this. + // The way we do this is by setting app ops corresponding to non requested + // restricted permissions to allow as this would allow the shared uid access + // case and be okay for other apps as they would not have the permission and + // would fail on the permission checks before reaching the app op check. + final SparseArray<List<String>> unrequestedRestrictedPermissionsForUid = + new SparseArray<>(); + final AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class); final int allowedCount = mAllowedUidOps.size(); for (int i = 0; i < allowedCount; i++) { @@ -190,31 +229,84 @@ public final class PermissionPolicyService extends SystemService { final int uid = mAllowedUidOps.valueAt(i); final String packageName = mPackageNames.valueAt(i); setUidModeAllowed(appOpsManager, opCode, uid, packageName); + + // Keep track this permission was requested by the UID. + List<String> unrequestedRestrictedPermissions = + unrequestedRestrictedPermissionsForUid.get(uid); + if (unrequestedRestrictedPermissions == null) { + unrequestedRestrictedPermissions = new ArrayList<>(sAllRestrictedPermissions); + unrequestedRestrictedPermissionsForUid.put(uid, + unrequestedRestrictedPermissions); + } + unrequestedRestrictedPermissions.remove(AppOpsManager.opToPermission(opCode)); + + mUids.delete(uid); } final int defaultCount = mDefaultUidOps.size(); for (int i = 0; i < defaultCount; i++) { final int opCode = mDefaultUidOps.keyAt(i); final int uid = mDefaultUidOps.valueAt(i); setUidModeDefault(appOpsManager, opCode, uid); + + // Keep track this permission was requested by the UID. + List<String> unrequestedRestrictedPermissions = + unrequestedRestrictedPermissionsForUid.get(uid); + if (unrequestedRestrictedPermissions == null) { + unrequestedRestrictedPermissions = new ArrayList<>(sAllRestrictedPermissions); + unrequestedRestrictedPermissionsForUid.put(uid, + unrequestedRestrictedPermissions); + } + unrequestedRestrictedPermissions.remove(AppOpsManager.opToPermission(opCode)); + + mUids.delete(uid); } - } - static void syncPackage(@NonNull Context context, @NonNull PackageParser.Package pkg, - @UserIdInt int userId) { - addPackage(context, pkg, userId, PermissionToOpSynchronizer::setUidModeAllowed, - PermissionToOpSynchronizer::setUidModeDefault); + // Give root access + mUids.put(Process.ROOT_UID, Process.ROOT_UID); + + // Add records for UIDs that don't use any restricted permissions. + final int uidCount = mUids.size(); + for (int i = 0; i < uidCount; i++) { + final int uid = mUids.keyAt(i); + unrequestedRestrictedPermissionsForUid.put(uid, + new ArrayList<>(sAllRestrictedPermissions)); + } + + // Flip ops for all unrequested restricted permission for the UIDs. + final int unrequestedUidCount = unrequestedRestrictedPermissionsForUid.size(); + for (int i = 0; i < unrequestedUidCount; i++) { + final List<String> unrequestedRestrictedPermissions = + unrequestedRestrictedPermissionsForUid.valueAt(i); + if (unrequestedRestrictedPermissions != null) { + final int uid = unrequestedRestrictedPermissionsForUid.keyAt(i); + final String[] packageNames = (uid != Process.ROOT_UID) + ? mContext.getPackageManager().getPackagesForUid(uid) + : new String[] {"root"}; + if (packageNames == null) { + continue; + } + final int permissionCount = unrequestedRestrictedPermissions.size(); + for (int j = 0; j < permissionCount; j++) { + final String permission = unrequestedRestrictedPermissions.get(j); + for (String packageName : packageNames) { + setUidModeAllowed(appOpsManager, + AppOpsManager.permissionToOpCode(permission), uid, + packageName); + } + } + } + } } - private static void addPackage(@NonNull Context context, - @NonNull PackageParser.Package pkg, @UserIdInt int userId, - @NonNull QuadConsumer<AppOpsManager, Integer, Integer, String> allowedConsumer, - @NonNull TriConsumer<AppOpsManager, Integer, Integer> defaultConsumer) { + private void addPackage(@NonNull Context context, + @NonNull PackageParser.Package pkg, @UserIdInt int userId) { final PackageManager packageManager = context.getPackageManager(); - final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); final int uid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.applicationInfo.uid)); final UserHandle userHandle = UserHandle.of(userId); + mUids.put(uid, uid); + final int permissionCount = pkg.requestedPermissions.size(); for (int i = 0; i < permissionCount; i++) { final String permission = pkg.requestedPermissions.get(i); @@ -241,9 +333,10 @@ public final class PermissionPolicyService extends SystemService { if (permissionInfo.isHardRestricted()) { if (applyRestriction) { - defaultConsumer.accept(appOpsManager, opCode, uid); + mDefaultUidOps.put(opCode, uid); } else { - allowedConsumer.accept(appOpsManager, opCode, uid, pkg.packageName); + mPackageNames.put(opCode, pkg.packageName); + mAllowedUidOps.put(opCode, uid); } } else if (permissionInfo.isSoftRestricted()) { //TODO: Implement soft restrictions like storage here. @@ -251,19 +344,6 @@ public final class PermissionPolicyService extends SystemService { } } - @SuppressWarnings("unused") - private void addAllowedEntry(@NonNull AppOpsManager appOpsManager, int opCode, - int uid, @NonNull String packageName) { - mPackageNames.put(opCode, packageName); - mAllowedUidOps.put(opCode, uid); - } - - @SuppressWarnings("unused") - private void addIgnoredEntry(@NonNull AppOpsManager appOpsManager, - int opCode, int uid) { - mDefaultUidOps.put(opCode, uid); - } - private static void setUidModeAllowed(@NonNull AppOpsManager appOpsManager, int opCode, int uid, @NonNull String packageName) { final int currentMode = appOpsManager.unsafeCheckOpRaw(AppOpsManager diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e08e1ff01348..e2253e7f83ce 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -693,13 +693,13 @@ final class ActivityRecord extends ConfigurationContainer { } } - void scheduleTopResumedActivityChanged(boolean onTop) { + boolean scheduleTopResumedActivityChanged(boolean onTop) { if (!attachedToProcess()) { if (DEBUG_STATES) { Slog.w(TAG, "Can't report activity position update - client not running" + ", activityRecord=" + this); } - return; + return false; } try { if (DEBUG_STATES) { @@ -710,7 +710,9 @@ final class ActivityRecord extends ConfigurationContainer { TopResumedActivityChangeItem.obtain(onTop)); } catch (RemoteException e) { // If process died, whatever. + return false; } + return true; } void updateMultiWindowMode() { @@ -3408,7 +3410,6 @@ final class ActivityRecord extends ConfigurationContainer { transaction.addCallback(callbackItem); transaction.setLifecycleStateRequest(lifecycleItem); mAtmService.getLifecycleManager().scheduleTransaction(transaction); - mStackSupervisor.updateTopResumedActivityIfNeeded(); // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only // request resume if this activity is currently resumed, which implies we aren't // sleeping. diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index fad4dbd5613b..419f5be5bbc8 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -1500,7 +1500,6 @@ class ActivityStack extends ConfigurationContainer { + " callers=" + Debug.getCallers(5)); r.setState(RESUMED, "minimalResumeActivityLocked"); r.completeResumeLocked(); - mStackSupervisor.updateTopResumedActivityIfNeeded(); if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE, "Launch completed; removing icicle of " + r.icicle); } @@ -2571,7 +2570,6 @@ class ActivityStack extends ConfigurationContainer { // Protect against recursion. mInResumeTopActivity = true; result = resumeTopActivityInnerLocked(prev, options); - mStackSupervisor.updateTopResumedActivityIfNeeded(); // When resuming the top activity, it may be necessary to pause the top activity (for // example, returning to the lock screen. We suppress the normal pause logic in @@ -2606,6 +2604,7 @@ class ActivityStack extends ConfigurationContainer { if (DEBUG_STACK) Slog.d(TAG_STACK, "setResumedActivity stack:" + this + " + from: " + mResumedActivity + " to:" + r + " reason:" + reason); mResumedActivity = r; + mStackSupervisor.updateTopResumedActivityIfNeeded(); } @GuardedBy("mService") diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 53dc1df5a46a..afdbd73d520d 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -853,7 +853,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // Schedule transaction. mService.getLifecycleManager().scheduleTransaction(clientTransaction); - updateTopResumedActivityIfNeeded(); if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0 && mService.mHasHeavyWeightFeature) { @@ -2321,8 +2320,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity // before the prevTopActivity one hasn't reported back yet. So server never sent the top // resumed state change message to prevTopActivity. - if (prevActivityReceivedTopState) { - prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */); + if (prevActivityReceivedTopState + && prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */)) { scheduleTopResumedStateLossTimeout(prevTopActivity); mTopResumedActivityWaitingForPrev = true; } diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8f2a2d2dae67..8f1709e4d614 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4096,6 +4096,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isSeparateProfileChallengeAllowed(int userHandle) { + if (!isCallerWithSystemUid()) { + throw new SecurityException("Caller must be system"); + } ComponentName profileOwner = getProfileOwner(userHandle); // Profile challenge is supported on N or newer release. return profileOwner != null && diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index fce7599d0b59..01f2f6b26415 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -68,6 +68,7 @@ <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.HARDWARE_TEST"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/servicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java new file mode 100644 index 000000000000..fc74c972ed83 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java @@ -0,0 +1,102 @@ +/* + * 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.server.display.color; + +import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; +import static com.android.server.display.color.DisplayTransformManager.PERSISTENT_PROPERTY_DISPLAY_COLOR; +import static com.android.server.display.color.DisplayTransformManager.PERSISTENT_PROPERTY_SATURATION; + +import static com.google.common.truth.Truth.assertThat; + +import android.hardware.display.ColorDisplayManager; +import android.os.SystemProperties; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class DisplayTransformManagerTest { + + private DisplayTransformManager mDtm; + private float[] mNightDisplayMatrix; + + @Before + public void setUp() { + mDtm = new DisplayTransformManager(); + mNightDisplayMatrix = mDtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY); + + SystemProperties.set(PERSISTENT_PROPERTY_DISPLAY_COLOR, null); + SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, null); + } + + @Test + public void setColorMode_natural() { + mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL, mNightDisplayMatrix); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR, null)) + .isEqualTo("0" /* managed */); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION, null)) + .isEqualTo("1.0" /* natural */); + } + + @Test + public void setColorMode_boosted() { + mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED, mNightDisplayMatrix); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR, null)) + .isEqualTo("0" /* managed */); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION, null)) + .isEqualTo("1.1" /* boosted */); + } + + @Test + public void setColorMode_saturated() { + mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_SATURATED, mNightDisplayMatrix); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR, null)) + .isEqualTo("1" /* unmanaged */); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION, null)) + .isEqualTo("1.0" /* natural */); + } + + @Test + public void setColorMode_automatic() { + mDtm.setColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC, mNightDisplayMatrix); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR, null)) + .isEqualTo("2" /* enhanced */); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION, null)) + .isEqualTo("1.0" /* natural */); + } + + @Test + public void setColorMode_vendor() { + mDtm.setColorMode(0x100, mNightDisplayMatrix); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR, null)) + .isEqualTo(Integer.toString(0x100) /* pass-through */); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION, null)) + .isEqualTo("1.0" /* default */); + } + + @Test + public void setColorMode_outOfBounds() { + mDtm.setColorMode(0x50, mNightDisplayMatrix); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_DISPLAY_COLOR, null)) + .isEqualTo("" /* default */); + assertThat(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION, null)) + .isEqualTo("" /* default */); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 43fe674c5d78..8aaf29a11033 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -23,9 +23,11 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -36,6 +38,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -61,6 +64,7 @@ import com.google.android.collect.Lists; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; @@ -965,6 +969,62 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + public void testOnNullBinding() throws Exception { + Context context = mock(Context.class); + PackageManager pm = mock(PackageManager.class); + ApplicationInfo ai = new ApplicationInfo(); + ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; + + when(context.getPackageName()).thenReturn(mContext.getPackageName()); + when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageManager()).thenReturn(pm); + when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); + + ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + ComponentName cn = ComponentName.unflattenFromString("a/a"); + + service.registerSystemService(cn, 0); + when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> { + Object[] args = invocation.getArguments(); + ServiceConnection sc = (ServiceConnection) args[1]; + sc.onNullBinding(cn); + return true; + }); + + service.registerSystemService(cn, 0); + assertFalse(service.isBound(cn, 0)); + } + + @Test + public void testOnServiceConnected() throws Exception { + Context context = mock(Context.class); + PackageManager pm = mock(PackageManager.class); + ApplicationInfo ai = new ApplicationInfo(); + ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; + + when(context.getPackageName()).thenReturn(mContext.getPackageName()); + when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageManager()).thenReturn(pm); + when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); + + ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm, + APPROVAL_BY_COMPONENT); + ComponentName cn = ComponentName.unflattenFromString("a/a"); + + service.registerSystemService(cn, 0); + when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> { + Object[] args = invocation.getArguments(); + ServiceConnection sc = (ServiceConnection) args[1]; + sc.onServiceConnected(cn, mock(IBinder.class)); + return true; + }); + + service.registerSystemService(cn, 0); + assertTrue(service.isBound(cn, 0)); + } + + @Test public void testOnPackagesChanged_nullValuesPassed_noNullPointers() { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ca7a71ecad75..355ff63d18f7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -71,8 +71,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Activity; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.AutomaticZenRule; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; @@ -117,6 +119,7 @@ import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.NotifyingApp; import android.service.notification.StatusBarNotification; +import android.service.notification.ZenPolicy; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; @@ -345,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"); @@ -2887,7 +2891,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testApplyEnqueuedAdjustmentFromAssistant_importance_onTime() throws Exception { + public void testApplyEnqueuedAdjustmentFromAssistant_importance() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addEnqueuedNotification(r); NotificationManagerService.WorkerHandler handler = mock( @@ -2905,25 +2909,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testApplyEnqueuedAdjustmentFromAssistant_importance_tooLate() throws Exception { - final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); - mService.addNotification(r); - NotificationManagerService.WorkerHandler handler = mock( - NotificationManagerService.WorkerHandler.class); - mService.setHandler(handler); - when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true); - - Bundle signals = new Bundle(); - signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW); - Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); - mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); - - assertEquals(IMPORTANCE_DEFAULT, r.getImportance()); - assertFalse(r.hasAdjustment(KEY_IMPORTANCE)); - } - - @Test public void testApplyEnqueuedAdjustmentFromAssistant_crossUser() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addEnqueuedNotification(r); @@ -4305,6 +4290,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testFlagBubble() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Notif with bubble metadata but not our other misc requirements + NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, + null /* tvExtender */, true /* isBubble */); + + // Say we're foreground + when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( + IMPORTANCE_FOREGROUND); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifs.length); + assertTrue((notifs[0].getNotification().flags & FLAG_BUBBLE) != 0); + assertTrue(mService.getNotificationRecord( + nr.sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test public void testFlagBubbleNotifs_flag_appForeground() throws RemoteException { // Bubbles are allowed! mService.setPreferencesHelper(mPreferencesHelper); @@ -4934,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); @@ -4967,6 +4986,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(USER_SENTIMENT_NEUTRAL, r.getUserSentiment()); } + @Test + public void testAutomaticZenRuleValidation_policyFilterAgreement() throws Exception { + 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), + zenPolicy, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled); + + try { + mBinderService.addAutomaticZenRule(rule); + fail("Zen policy only applies to priority only mode"); + } catch (IllegalArgumentException e) { + // yay + } + + rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), + zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled); + mBinderService.addAutomaticZenRule(rule); + + rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class), + null, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled); + mBinderService.addAutomaticZenRule(rule); + } + + @Test public void testAreNotificationsEnabledForPackage_crossUser() throws Exception { try { mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), @@ -4983,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/telecomm/java/android/telecom/CallRedirectionService.java b/telecomm/java/android/telecom/CallRedirectionService.java index 37fab09cd745..36c637723c0a 100644 --- a/telecomm/java/android/telecom/CallRedirectionService.java +++ b/telecomm/java/android/telecom/CallRedirectionService.java @@ -119,18 +119,18 @@ public abstract class CallRedirectionService extends Service { * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}. The response corresponds to the * latest request via {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)}. * - * @param handle the new phone number to dial + * @param gatewayUri the gateway uri for call redirection. * @param targetPhoneAccount the {@link PhoneAccountHandle} to use when placing the call. * @param confirmFirst Telecom will ask users to confirm the redirection via a yes/no dialog * if the confirmFirst is true, and if the redirection request of this * response was sent with a true flag of allowInteractiveResponse via * {@link #onPlaceCall(Uri, PhoneAccountHandle, boolean)} */ - public final void redirectCall(@NonNull Uri handle, + public final void redirectCall(@NonNull Uri gatewayUri, @NonNull PhoneAccountHandle targetPhoneAccount, boolean confirmFirst) { try { - mCallRedirectionAdapter.redirectCall(handle, targetPhoneAccount, confirmFirst); + mCallRedirectionAdapter.redirectCall(gatewayUri, targetPhoneAccount, confirmFirst); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 52e0ebd334b5..2d8a8cbae59f 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -4321,6 +4321,22 @@ public final class Telephony { * @hide */ public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation"; + + /** + * The current registered raw data network operator name in long alphanumeric format. + * <p> + * This is the same as {@link ServiceState#getOperatorAlphaLongRaw()}. + * @hide + */ + public static final String OPERATOR_ALPHA_LONG_RAW = "operator_alpha_long_raw"; + + /** + * The current registered raw data network operator name in short alphanumeric format. + * <p> + * This is the same as {@link ServiceState#getOperatorAlphaShortRaw()}. + * @hide + */ + public static final String OPERATOR_ALPHA_SHORT_RAW = "operator_alpha_short_raw"; } /** diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 970bbf89b39e..9f6528bc4709 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1312,6 +1312,24 @@ public class CarrierConfigManager { "hide_lte_plus_data_icon_bool"; /** + * The string is used to filter redundant string from PLMN Network Name that's supplied by + * specific carrier. + * + * @hide + */ + public static final String KEY_OPERATOR_NAME_FILTER_PATTERN_STRING = + "operator_name_filter_pattern_string"; + + /** + * The string is used to compare with operator name. If it matches the pattern then show + * specific data icon. + * + * @hide + */ + public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING = + "show_carrier_data_icon_pattern_string"; + + /** * Boolean to decide whether to show precise call failed cause to user * @hide */ @@ -3152,6 +3170,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false); sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false); sDefaults.putBoolean(KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, false); + sDefaults.putString(KEY_OPERATOR_NAME_FILTER_PATTERN_STRING, ""); + sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, ""); sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true); sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_TDSCDMA_BOOL, false); diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java index 374527749354..30875165867a 100644 --- a/telephony/java/android/telephony/CellIdentity.java +++ b/telephony/java/android/telephony/CellIdentity.java @@ -49,10 +49,10 @@ public abstract class CellIdentity implements Parcelable { // long alpha Operator Name String or Enhanced Operator Name String /** @hide */ - protected final String mAlphaLong; + protected String mAlphaLong; // short alpha Operator Name String or Enhanced Operator Name String /** @hide */ - protected final String mAlphaShort; + protected String mAlphaShort; /** @hide */ protected CellIdentity(String tag, int type, String mcc, String mnc, String alphal, @@ -145,6 +145,13 @@ public abstract class CellIdentity implements Parcelable { } /** + * @hide + */ + public void setOperatorAlphaLong(String alphaLong) { + mAlphaLong = alphaLong; + } + + /** * @return The short alpha tag associated with the current scan result (may be the operator * name string or extended operator name string). May be null if unknown. */ @@ -154,6 +161,13 @@ public abstract class CellIdentity implements Parcelable { } /** + * @hide + */ + public void setOperatorAlphaShort(String alphaShort) { + mAlphaShort = alphaShort; + } + + /** * @return a CellLocation object for this CellIdentity * @hide */ diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 8c92e84b45b6..1a160f4f57a6 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -339,6 +339,9 @@ public class ServiceState implements Parcelable { private List<NetworkRegistrationInfo> mNetworkRegistrationInfos = new ArrayList<>(); + private String mOperatorAlphaLongRaw; + private String mOperatorAlphaShortRaw; + /** * get String description of roaming type * @hide @@ -420,6 +423,8 @@ public class ServiceState implements Parcelable { mNetworkRegistrationInfos = s.mNetworkRegistrationInfos == null ? null : new ArrayList<>(s.mNetworkRegistrationInfos); mNrFrequencyRange = s.mNrFrequencyRange; + mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw; + mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw; } /** @@ -453,6 +458,8 @@ public class ServiceState implements Parcelable { mChannelNumber = in.readInt(); mCellBandwidths = in.createIntArray(); mNrFrequencyRange = in.readInt(); + mOperatorAlphaLongRaw = in.readString(); + mOperatorAlphaShortRaw = in.readString(); } public void writeToParcel(Parcel out, int flags) { @@ -478,6 +485,8 @@ public class ServiceState implements Parcelable { out.writeInt(mChannelNumber); out.writeIntArray(mCellBandwidths); out.writeInt(mNrFrequencyRange); + out.writeString(mOperatorAlphaLongRaw); + out.writeString(mOperatorAlphaShortRaw); } public int describeContents() { @@ -836,7 +845,9 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly, mLteEarfcnRsrpBoost, mNetworkRegistrationInfos, - mNrFrequencyRange); + mNrFrequencyRange, + mOperatorAlphaLongRaw, + mOperatorAlphaShortRaw); } @Override @@ -862,6 +873,8 @@ public class ServiceState implements Parcelable { && equalsHandlesNulls(mCdmaDefaultRoamingIndicator, s.mCdmaDefaultRoamingIndicator) && mIsEmergencyOnly == s.mIsEmergencyOnly + && equalsHandlesNulls(mOperatorAlphaLongRaw, s.mOperatorAlphaLongRaw) + && equalsHandlesNulls(mOperatorAlphaShortRaw, s.mOperatorAlphaShortRaw) && (mNetworkRegistrationInfos == null ? s.mNetworkRegistrationInfos == null : s.mNetworkRegistrationInfos != null && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)) @@ -1019,6 +1032,8 @@ public class ServiceState implements Parcelable { .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost) .append(", mNetworkRegistrationInfos=").append(mNetworkRegistrationInfos) .append(", mNrFrequencyRange=").append(mNrFrequencyRange) + .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw) + .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw) .append("}").toString(); } @@ -1056,6 +1071,8 @@ public class ServiceState implements Parcelable { .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN) .build()); + mOperatorAlphaLongRaw = null; + mOperatorAlphaShortRaw = null; } public void setStateOutOfService() { @@ -1297,6 +1314,8 @@ public class ServiceState implements Parcelable { m.putInt("ChannelNumber", mChannelNumber); m.putIntArray("CellBandwidths", mCellBandwidths); m.putInt("mNrFrequencyRange", mNrFrequencyRange); + m.putString("operator-alpha-long-raw", mOperatorAlphaLongRaw); + m.putString("operator-alpha-short-raw", mOperatorAlphaShortRaw); } /** @hide */ @@ -1906,4 +1925,36 @@ public class ServiceState implements Parcelable { return state; } + + /** + * @hide + */ + public void setOperatorAlphaLongRaw(String operatorAlphaLong) { + mOperatorAlphaLongRaw = operatorAlphaLong; + } + + /** + * The current registered raw data network operator name in long alphanumeric format. + * + * @hide + */ + public String getOperatorAlphaLongRaw() { + return mOperatorAlphaLongRaw; + } + + /** + * @hide + */ + public void setOperatorAlphaShortRaw(String operatorAlphaShort) { + mOperatorAlphaShortRaw = operatorAlphaShort; + } + + /** + * The current registered raw data network operator name in short alphanumeric format. + * + * @hide + */ + public String getOperatorAlphaShortRaw() { + return mOperatorAlphaShortRaw; + } } diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index ee28ca23be35..cf15b92ae640 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -87,8 +87,8 @@ public class SubscriptionInfo implements Parcelable { private int mCarrierId; /** - * The source of the name, NAME_SOURCE_UNDEFINED, NAME_SOURCE_DEFAULT_SOURCE, - * NAME_SOURCE_SIM_SOURCE or NAME_SOURCE_USER_INPUT. + * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SOURCE or + * NAME_SOURCE_USER_INPUT. */ private int mNameSource; @@ -103,7 +103,7 @@ public class SubscriptionInfo implements Parcelable { private String mNumber; /** - * Data roaming state, DATA_RAOMING_ENABLE, DATA_RAOMING_DISABLE + * Data roaming state, DATA_ROAMING_ENABLE, DATA_ROAMING_DISABLE */ private int mDataRoaming; @@ -306,8 +306,8 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the source of the name, eg NAME_SOURCE_UNDEFINED, NAME_SOURCE_DEFAULT_SOURCE, - * NAME_SOURCE_SIM_SOURCE or NAME_SOURCE_USER_INPUT. + * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SOURCE or + * NAME_SOURCE_USER_INPUT. * @hide */ @UnsupportedAppUsage @@ -316,8 +316,8 @@ public class SubscriptionInfo implements Parcelable { } /** - * Creates and returns an icon {@code Bitmap} to represent this {@code SubscriptionInfo} in a user - * interface. + * Creates and returns an icon {@code Bitmap} to represent this {@code SubscriptionInfo} in a + * user interface. * * @param context A {@code Context} to get the {@code DisplayMetrics}s from. * diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 57c84a638f12..0c6341111029 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -364,12 +364,6 @@ public class SubscriptionManager { public static final String NAME_SOURCE = "name_source"; /** - * The name_source is undefined - * @hide - */ - public static final int NAME_SOURCE_UNDEFINDED = -1; - - /** * The name_source is the default * @hide */ @@ -1598,27 +1592,16 @@ public class SubscriptionManager { } /** - * Set display name by simInfo index - * @param displayName the display name of SIM card - * @param subId the unique SubscriptionInfo index in database - * @return the number of records updated - * @hide - */ - public int setDisplayName(String displayName, int subId) { - return setDisplayName(displayName, subId, NAME_SOURCE_UNDEFINDED); - } - - /** * Set display name by simInfo index with name source * @param displayName the display name of SIM card * @param subId the unique SubscriptionInfo index in database * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE, - * 2: NAME_SOURCE_USER_INPUT, -1 NAME_SOURCE_UNDEFINED + * 2: NAME_SOURCE_USER_INPUT * @return the number of records updated or < 0 if invalid subId * @hide */ @UnsupportedAppUsage - public int setDisplayName(String displayName, int subId, long nameSource) { + public int setDisplayName(String displayName, int subId, int nameSource) { if (VDBG) { logd("[setDisplayName]+ displayName:" + displayName + " subId:" + subId + " nameSource:" + nameSource); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 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/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 01fdae800972..cfba0529e664 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -145,21 +145,13 @@ interface ISub { int setIconTint(int tint, int subId); /** - * Set display name by simInfo index - * @param displayName the display name of SIM card - * @param subId the unique SubscriptionInfo index in database - * @return the number of records updated - */ - int setDisplayName(String displayName, int subId); - - /** * Set display name by simInfo index with name source * @param displayName the display name of SIM card * @param subId the unique SubscriptionInfo index in database * @param nameSource, 0: DEFAULT_SOURCE, 1: SIM_SOURCE, 2: USER_INPUT * @return the number of records updated */ - int setDisplayNameUsingSrc(String displayName, int subId, long nameSource); + int setDisplayNameUsingSrc(String displayName, int subId, int nameSource); /** * Set phone number by subId diff --git a/tests/ProtoInputStreamTests/Android.mk b/tests/ProtoInputStreamTests/Android.mk new file mode 100644 index 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; +}; diff --git a/tests/net/java/android/net/NetworkStackTest.java b/tests/net/java/android/net/NetworkStackTest.java new file mode 100644 index 000000000000..f7c6c99ba622 --- /dev/null +++ b/tests/net/java/android/net/NetworkStackTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import static android.Manifest.permission.NETWORK_STACK; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; +import static android.net.NetworkStack.checkNetworkStackPermission; +import static android.net.NetworkStack.checkNetworkStackPermissionOr; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class NetworkStackTest { + private static final String [] OTHER_PERMISSION = {"otherpermission1", "otherpermission2"}; + + @Mock Context mCtx; + + @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testCheckNetworkStackPermission() throws Exception { + when(mCtx.checkCallingOrSelfPermission(eq(NETWORK_STACK))).thenReturn(PERMISSION_GRANTED); + when(mCtx.checkCallingOrSelfPermission(eq(PERMISSION_MAINLINE_NETWORK_STACK))) + .thenReturn(PERMISSION_DENIED); + checkNetworkStackPermission(mCtx); + checkNetworkStackPermissionOr(mCtx, OTHER_PERMISSION); + + when(mCtx.checkCallingOrSelfPermission(eq(NETWORK_STACK))).thenReturn(PERMISSION_DENIED); + when(mCtx.checkCallingOrSelfPermission(eq(PERMISSION_MAINLINE_NETWORK_STACK))) + .thenReturn(PERMISSION_GRANTED); + checkNetworkStackPermission(mCtx); + checkNetworkStackPermissionOr(mCtx, OTHER_PERMISSION); + + when(mCtx.checkCallingOrSelfPermission(any())).thenReturn(PERMISSION_DENIED); + + try { + checkNetworkStackPermissionOr(mCtx, OTHER_PERMISSION); + } catch (SecurityException e) { + // Expect to get a SecurityException + return; + } + + fail("Expect fail but permission granted."); + } +} diff --git a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java index 68ff777a0160..22a2c94fc194 100644 --- a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java @@ -18,6 +18,7 @@ package com.android.server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.eq; @@ -134,11 +135,11 @@ public class IpSecServiceRefcountedResourceTest { IBinder binderMock = mock(IBinder.class); doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt()); - RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock); - - // Verify that cleanup is performed (Spy limitations prevent verification of method calls - // for binder death scenario; check refcount to determine if cleanup was performed.) - assertEquals(-1, refcountedResource.mRefCount); + try { + getTestRefcountedResource(binderMock); + fail("Expected exception to propogate when binder fails to link to death"); + } catch (RuntimeException expected) { + } } @Test diff --git a/wifi/java/android/net/wifi/aware/ConfigRequest.java b/wifi/java/android/net/wifi/aware/ConfigRequest.java index 1e46d1b16feb..b07d8edde3d4 100644 --- a/wifi/java/android/net/wifi/aware/ConfigRequest.java +++ b/wifi/java/android/net/wifi/aware/ConfigRequest.java @@ -213,7 +213,7 @@ public final class ConfigRequest implements Parcelable { * Builder used to build {@link ConfigRequest} objects. */ public static final class Builder { - private boolean mSupport5gBand = false; + private boolean mSupport5gBand = true; private int mMasterPreference = 0; private int mClusterLow = CLUSTER_ID_MIN; private int mClusterHigh = CLUSTER_ID_MAX; diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java index 52bb28485c72..db8220b41910 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java @@ -576,7 +576,7 @@ public class WifiAwareManagerTest { equalTo(configRequest.mClusterLow)); collector.checkThat("mMasterPreference", 0, equalTo(configRequest.mMasterPreference)); - collector.checkThat("mSupport5gBand", false, equalTo(configRequest.mSupport5gBand)); + collector.checkThat("mSupport5gBand", true, equalTo(configRequest.mSupport5gBand)); collector.checkThat("mDiscoveryWindowInterval.length", 2, equalTo(configRequest.mDiscoveryWindowInterval.length)); collector.checkThat("mDiscoveryWindowInterval[2.4GHz]", ConfigRequest.DW_INTERVAL_NOT_INIT, |