diff options
| author | 2024-09-04 14:47:21 +0000 | |
|---|---|---|
| committer | 2024-09-04 14:47:21 +0000 | |
| commit | fccbe170bbc6e71045f8076a313f9c07e44a3ea7 (patch) | |
| tree | 3475b281fde4c374f762226fb1e923de705cbff5 | |
| parent | a4546e3cf998fa164a68bcd5f2f0e06893ab070c (diff) | |
| parent | 1181fd1b6769e6f093cd409e2b9f7aa0f91d7ed9 (diff) | |
Merge "Limit the size of vibration effects stored on a NotificationChannel" into main
| -rw-r--r-- | core/java/android/app/NotificationChannel.java | 59 | ||||
| -rw-r--r-- | core/java/android/app/notification.aconfig | 10 | ||||
| -rw-r--r-- | core/java/android/os/VibrationEffect.java | 42 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/NotificationChannelTest.java | 27 | ||||
| -rw-r--r-- | core/tests/vibrator/src/android/os/VibrationEffectTest.java | 80 |
5 files changed, 212 insertions, 6 deletions
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 789c99d8e017..1b29b7a294df 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -168,7 +168,11 @@ public final class NotificationChannel implements Parcelable { /** * @hide */ - public static final int MAX_VIBRATION_LENGTH = 1000; + public static final int MAX_VIBRATION_LENGTH = 500; + /** + * @hide + */ + public static final int MAX_SERIALIZED_VIBRATION_LENGTH = 32_768; private static final String TAG_CHANNEL = "channel"; private static final String ATT_NAME = "name"; @@ -368,6 +372,9 @@ public final class NotificationChannel implements Parcelable { if (Flags.notificationChannelVibrationEffectApi()) { mVibrationEffect = in.readInt() != 0 ? VibrationEffect.CREATOR.createFromParcel(in) : null; + if (Flags.notifChannelCropVibrationEffects() && mVibrationEffect != null) { + mVibrationEffect = getTrimmedVibrationEffect(mVibrationEffect); + } } mUserLockedFields = in.readInt(); mUserVisibleTaskShown = in.readByte() != 0; @@ -582,6 +589,23 @@ public final class NotificationChannel implements Parcelable { return input; } + // Returns trimmed vibration effect or null if not trimmable. + private VibrationEffect getTrimmedVibrationEffect(VibrationEffect effect) { + if (effect == null) { + return null; + } + // trim if possible; check serialized length; reject if it is still too long + VibrationEffect result = effect; + VibrationEffect trimmed = effect.cropToLengthOrNull(MAX_VIBRATION_LENGTH); + if (trimmed != null) { + result = trimmed; + } + if (vibrationToString(result).length() > MAX_SERIALIZED_VIBRATION_LENGTH) { + return null; + } + return result; + } + /** * @hide */ @@ -685,6 +709,11 @@ public final class NotificationChannel implements Parcelable { public void setVibrationPattern(long[] vibrationPattern) { this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0; this.mVibrationPattern = vibrationPattern; + if (Flags.notifChannelCropVibrationEffects()) { + if (vibrationPattern != null && vibrationPattern.length > MAX_VIBRATION_LENGTH) { + this.mVibrationPattern = Arrays.copyOf(vibrationPattern, MAX_VIBRATION_LENGTH); + } + } if (Flags.notificationChannelVibrationEffectApi()) { try { this.mVibrationEffect = @@ -731,9 +760,21 @@ public final class NotificationChannel implements Parcelable { public void setVibrationEffect(@Nullable VibrationEffect effect) { this.mVibrationEnabled = effect != null; this.mVibrationEffect = effect; - this.mVibrationPattern = - effect == null - ? null : effect.computeCreateWaveformOffOnTimingsOrNull(); + if (Flags.notifChannelCropVibrationEffects() && effect != null) { + // Try converting to a vibration pattern and trimming that array. If not convertible + // to a pattern directly, try trimming the vibration effect if possible and storing + // that version instead. + long[] pattern = effect.computeCreateWaveformOffOnTimingsOrNull(); + if (pattern != null) { + setVibrationPattern(pattern); + } else { + this.mVibrationEffect = getTrimmedVibrationEffect(mVibrationEffect); + } + } else { + this.mVibrationPattern = + mVibrationEffect == null + ? null : mVibrationEffect.computeCreateWaveformOffOnTimingsOrNull(); + } } /** @@ -1172,7 +1213,9 @@ public final class NotificationChannel implements Parcelable { if (vibrationEffect != null) { // Restore the effect only if it is not null. This allows to avoid undoing a // `setVibrationPattern` call above, if that was done with a non-null pattern - // (e.g. back up from a version that did not support `setVibrationEffect`). + // (e.g. back up from a version that did not support `setVibrationEffect`), or + // when notif_channel_crop_vibration_effects is true, if there is an equivalent + // vibration pattern available. setVibrationEffect(vibrationEffect); } } @@ -1365,7 +1408,11 @@ public final class NotificationChannel implements Parcelable { out.attribute(null, ATT_VIBRATION, longArrayToString(getVibrationPattern())); } if (getVibrationEffect() != null) { - out.attribute(null, ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect())); + if (!Flags.notifChannelCropVibrationEffects() || getVibrationPattern() == null) { + // When notif_channel_crop_vibration_effects is on, only serialize the vibration + // effect if we do not already have an equivalent vibration pattern. + out.attribute(null, ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect())); + } } if (getUserLockedFields() != 0) { out.attributeInt(null, ATT_USER_LOCKED, getUserLockedFields()); diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 606ca3393de0..9891e8930936 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -128,6 +128,16 @@ flag { } flag { + name: "notif_channel_crop_vibration_effects" + namespace: "systemui" + description: "Limits the size of vibration effects that can be stored in a NotificationChannel" + bug: "345881518" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "evenly_divided_call_style_action_layout" namespace: "systemui" description: "Evenly divides horizontal space for action buttons in CallStyle notifications." diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index f02d4a9ce4a7..64a2dbcb6a83 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -540,6 +540,17 @@ public abstract class VibrationEffect implements Parcelable { /** @hide */ public abstract void validate(); + + /** + * If supported, truncate the length of this vibration effect to the provided length and return + * the result. Will always return null for repeating effects. + * + * @return The desired effect, or {@code null} if truncation is not applicable. + * @hide + */ + @Nullable + public abstract VibrationEffect cropToLengthOrNull(int length); + /** * Gets the estimated duration of the vibration in milliseconds. * @@ -866,6 +877,30 @@ public abstract class VibrationEffect implements Parcelable { } } + /** @hide */ + @Override + @Nullable + public VibrationEffect cropToLengthOrNull(int length) { + // drop repeating effects + if (mRepeatIndex >= 0) { + return null; + } + + int segmentCount = mSegments.size(); + if (segmentCount <= length) { + return this; + } + + ArrayList truncated = new ArrayList(mSegments.subList(0, length)); + Composed updated = new Composed(truncated, mRepeatIndex); + try { + updated.validate(); + } catch (IllegalArgumentException e) { + return null; + } + return updated; + } + @Override public long getDuration() { if (mRepeatIndex >= 0) { @@ -1150,6 +1185,13 @@ public abstract class VibrationEffect implements Parcelable { "Vendor effect bundle must be non-empty"); } + /** @hide */ + @Override + @Nullable + public VibrationEffect cropToLengthOrNull(int length) { + return null; + } + @Override public long getDuration() { return -1; // UNKNOWN diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java index c08e42b7179c..e47ef2df48b9 100644 --- a/core/tests/coretests/src/android/app/NotificationChannelTest.java +++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java @@ -233,6 +233,33 @@ public class NotificationChannelTest { } @Test + @EnableFlags({Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API, + Flags.FLAG_NOTIF_CHANNEL_CROP_VIBRATION_EFFECTS}) + public void testLongVibrationFields_canWriteToXml() throws Exception { + NotificationChannel channel = new NotificationChannel("id", "name", 3); + // populate pattern with contents + long[] pattern = new long[65550 / 2]; + for (int i = 0; i < pattern.length; i++) { + pattern[i] = 100; + } + channel.setVibrationPattern(pattern); // with flag on, also sets effect + + // Send it through parceling & unparceling to simulate being passed through a binder call + NotificationChannel fromParcel = writeToAndReadFromParcel(channel); + assertThat(fromParcel.getVibrationPattern().length).isEqualTo( + NotificationChannel.MAX_VIBRATION_LENGTH); + + // Confirm that this also survives writing to & restoring from XML + NotificationChannel result = backUpAndRestore(fromParcel); + assertThat(result.getVibrationPattern().length).isEqualTo( + NotificationChannel.MAX_VIBRATION_LENGTH); + assertThat(result.getVibrationEffect()).isNotNull(); + assertThat(result.getVibrationEffect() + .computeCreateWaveformOffOnTimingsOrNull()) + .isEqualTo(result.getVibrationPattern()); + } + + @Test public void testRestoreSoundUri_customLookup() throws Exception { Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1"); Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1"); diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java index 4f76dd636c30..f5b04ee759a5 100644 --- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java +++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java @@ -430,6 +430,86 @@ public class VibrationEffectTest { } @Test + public void cropToLength_waveform_underLength() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[]{0, 1, 2}, + /* repeatIndex= */ -1); + VibrationEffect result = effect.cropToLengthOrNull(5); + + assertThat(result).isEqualTo(effect); // unchanged + } + + @Test + public void cropToLength_waveform_overLength() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[]{0, 1, 2, 3, 4, 5, 6}, + /* repeatIndex= */ -1); + VibrationEffect result = effect.cropToLengthOrNull(4); + + assertThat(result).isEqualTo(VibrationEffect.createWaveform( + new long[]{0, 1, 2, 3}, + -1)); + } + + @Test + public void cropToLength_waveform_repeating() { + // repeating waveforms cannot be truncated + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[]{0, 1, 2, 3, 4, 5, 6}, + /* repeatIndex= */ 2); + VibrationEffect result = effect.cropToLengthOrNull(3); + + assertThat(result).isNull(); + } + + @Test + public void cropToLength_waveform_withAmplitudes() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[]{0, 1, 2, 3, 4, 5, 6}, + /* amplitudes= */ new int[]{10, 20, 40, 10, 20, 40, 10}, + /* repeatIndex= */ -1); + VibrationEffect result = effect.cropToLengthOrNull(3); + + assertThat(result).isEqualTo(VibrationEffect.createWaveform( + new long[]{0, 1, 2}, + new int[]{10, 20, 40}, + -1)); + } + + @Test + public void cropToLength_composed() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) + .compose(); + VibrationEffect result = effect.cropToLengthOrNull(1); + + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .compose()); + } + + @Test + public void cropToLength_composed_repeating() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .repeatEffectIndefinitely(TEST_ONE_SHOT) + .compose(); + assertThat(effect.cropToLengthOrNull(1)).isNull(); + } + + @Test + @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + public void cropToLength_vendorEffect() { + PersistableBundle vendorData = new PersistableBundle(); + vendorData.putInt("key", 1); + VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData); + + assertThat(effect.cropToLengthOrNull(2)).isNull(); + } + + @Test public void getRingtones_noPrebakedRingtones() { Resources r = mockRingtoneResources(new String[0]); Context context = mockContext(r); |