summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yuri Lin <yurilin@google.com> 2024-09-04 14:47:21 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-09-04 14:47:21 +0000
commitfccbe170bbc6e71045f8076a313f9c07e44a3ea7 (patch)
tree3475b281fde4c374f762226fb1e923de705cbff5
parenta4546e3cf998fa164a68bcd5f2f0e06893ab070c (diff)
parent1181fd1b6769e6f093cd409e2b9f7aa0f91d7ed9 (diff)
Merge "Limit the size of vibration effects stored on a NotificationChannel" into main
-rw-r--r--core/java/android/app/NotificationChannel.java59
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/os/VibrationEffect.java42
-rw-r--r--core/tests/coretests/src/android/app/NotificationChannelTest.java27
-rw-r--r--core/tests/vibrator/src/android/os/VibrationEffectTest.java80
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);