diff options
5 files changed, 196 insertions, 3 deletions
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index e54124608a26..b9f0968b5864 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -330,10 +330,19 @@ public final class NotificationRecord { } final long[] vibrationPattern = channel.getVibrationPattern(); - if (vibrationPattern == null) { - return helper.createDefaultVibration(insistent); + if (vibrationPattern != null) { + return helper.createWaveformVibration(vibrationPattern, insistent); } - return helper.createWaveformVibration(vibrationPattern, insistent); + + if (com.android.server.notification.Flags.notificationVibrationInSoundUri()) { + final VibrationEffect vibrationEffectFromSoundUri = + helper.createVibrationEffectFromSoundUri(channel.getSound()); + if (vibrationEffectFromSoundUri != null) { + return vibrationEffectFromSoundUri; + } + } + + return helper.createDefaultVibration(insistent); } private VibrationEffect calculateVibration() { diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java index 8a0e595176ec..fbe77720b9fc 100644 --- a/services/core/java/com/android/server/notification/VibratorHelper.java +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -24,6 +24,9 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.media.AudioAttributes; +import android.media.RingtoneManager; +import android.media.Utils; +import android.net.Uri; import android.os.Process; import android.os.VibrationAttributes; import android.os.VibrationEffect; @@ -51,6 +54,7 @@ public final class VibratorHelper { @Nullable private final float[] mDefaultPwlePattern; @Nullable private final float[] mFallbackPwlePattern; private final int mDefaultVibrationAmplitude; + private final Context mContext; public VibratorHelper(Context context) { mVibrator = context.getSystemService(Vibrator.class); @@ -68,6 +72,7 @@ public final class VibratorHelper { com.android.internal.R.array.config_notificationFallbackVibeWaveform); mDefaultVibrationAmplitude = context.getResources().getInteger( com.android.internal.R.integer.config_defaultVibrationAmplitude); + mContext = context; } /** @@ -184,6 +189,16 @@ public final class VibratorHelper { * @param insistent {@code true} if the vibration should loop until it is cancelled. */ public VibrationEffect createDefaultVibration(boolean insistent) { + if (com.android.server.notification.Flags.notificationVibrationInSoundUri()) { + final Uri defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(mContext, + RingtoneManager.TYPE_NOTIFICATION); + final VibrationEffect vibrationEffectFromSoundUri = + createVibrationEffectFromSoundUri(defaultRingtoneUri); + if (vibrationEffectFromSoundUri != null) { + return vibrationEffectFromSoundUri; + } + } + if (mVibrator.hasFrequencyControl()) { VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent); if (effect != null) { @@ -193,6 +208,22 @@ public final class VibratorHelper { return createWaveformVibration(mDefaultPattern, insistent); } + /** + * Safely create a {@link VibrationEffect} from given an uri {@code Uri}. + * with query parameter "vibration_uri" + * + * Use this function if the {@code Uri} is with a query parameter "vibration_uri" and the + * vibration_uri represents a valid vibration effect in xml + * + * @param uri {@code Uri} an uri including query parameter "vibraiton_uri" + */ + public @Nullable VibrationEffect createVibrationEffectFromSoundUri(Uri uri) { + if (uri == null) { + return null; + } + return Utils.parseVibrationEffect(mVibrator, Utils.getVibrationUri(uri)); + } + /** Returns if a given vibration can be played by the vibrator that does notification buzz. */ public boolean areEffectComponentsSupported(VibrationEffect effect) { return mVibrator.areVibrationFeaturesSupported(effect); diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index aac2c404fd38..be3adc142fa4 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -156,3 +156,10 @@ flag { description: "This flag enables forced auto-grouping conversations" bug: "336488844" } + +flag { + name: "notification_vibration_in_sound_uri" + namespace: "systemui" + description: "This flag enables sound uri with vibration source" + bug: "358524009" +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index f572e7aa1706..50a5f658f059 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -62,6 +62,8 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.AudioAttributes; +import android.media.RingtoneManager; +import android.media.Utils; import android.metrics.LogMaker; import android.net.Uri; import android.os.Build; @@ -69,6 +71,7 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.VibrationEffect; import android.os.Vibrator; +import android.os.VibratorInfo; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -93,6 +96,9 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.util.ArrayList; @SmallTest @@ -141,6 +147,7 @@ public class NotificationRecordTest extends UiServiceTestCase { when(mMockContext.getSystemService(eq(Vibrator.class))).thenReturn(mVibrator); when(mVibrator.areVibrationFeaturesSupported(any())).thenReturn(true); + when(mVibrator.getInfo()).thenReturn(VibratorInfo.EMPTY_VIBRATOR_INFO); final Resources res = mContext.getResources(); when(mMockContext.getResources()).thenReturn(res); when(mMockContext.getPackageManager()).thenReturn(mPm); @@ -511,6 +518,51 @@ public class NotificationRecordTest extends UiServiceTestCase { } @Test + @EnableFlags(com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI) + public void testVibration_customVibrationForSound_withoutVibrationUri() { + // prepare testing data + Uri backupDefaultUri = RingtoneManager.getActualDefaultRingtoneUri(mMockContext, + RingtoneManager.TYPE_NOTIFICATION); + RingtoneManager.setActualDefaultRingtoneUri(mMockContext, RingtoneManager.TYPE_NOTIFICATION, + Settings.System.DEFAULT_NOTIFICATION_URI); + defaultChannel.enableVibration(true); + defaultChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, CUSTOM_ATTRIBUTES); + StatusBarNotification sbn = getNotification( + /* channelVibrationPattern= */ null, + /* channelVibrationEffect= */ null, + /* insistent= */ false); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); + + try { + assertEquals( + new VibratorHelper(mMockContext).createDefaultVibration(false), + record.getVibration()); + } finally { + // restore the data + RingtoneManager.setActualDefaultRingtoneUri(mMockContext, + RingtoneManager.TYPE_NOTIFICATION, + backupDefaultUri); + } + } + + @Test + @EnableFlags(com.android.server.notification.Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI) + public void testVibration_customVibrationForSound_withVibrationUri() throws IOException { + defaultChannel.enableVibration(true); + VibrationInfo vibration = getTestingVibration(mVibrator); + Uri uriWithVibration = getVibrationUriAppended( + Settings.System.DEFAULT_NOTIFICATION_URI, vibration.mUri); + defaultChannel.setSound(uriWithVibration, CUSTOM_ATTRIBUTES); + StatusBarNotification sbn = getNotification( + /* channelVibrationPattern= */ null, + /* channelVibrationEffect= */ null, + /* insistent= */ false); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); + + assertEquals(vibration.mVibrationEffect, record.getVibration()); + } + + @Test public void testImportance_preUpgrade() { StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, @@ -1594,4 +1646,39 @@ public class NotificationRecordTest extends UiServiceTestCase { assertThat(record.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM); } + + static class VibrationInfo { + public VibrationEffect mVibrationEffect; + public Uri mUri; + VibrationInfo(VibrationEffect vibrationEffect, Uri uri) { + mVibrationEffect = vibrationEffect; + mUri = uri; + } + } + + private static VibrationInfo getTestingVibration(Vibrator vibrator) throws IOException { + File tempVibrationFile = File.createTempFile("test_vibration_file", ".xml"); + FileWriter writer = new FileWriter(tempVibrationFile); + writer.write("<vibration-effect>\n" + + " <waveform-effect>\n" + + " <!-- PRIMING -->\n" + + " <waveform-entry durationMs=\"0\" amplitude=\"0\"/>\n" + + " <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n" + + " <waveform-entry durationMs=\"250\" amplitude=\"0\"/>\n" + + " <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n" + + " <waveform-entry durationMs=\"500\" amplitude=\"0\"/>\n" + + " </waveform-effect>\n" + + "</vibration-effect>"); // Your test XML content + writer.close(); + Uri vibrationUri = Uri.parse(tempVibrationFile.toURI().toString()); + + VibrationEffect vibrationEffect = Utils.parseVibrationEffect(vibrator, vibrationUri); + return new VibrationInfo(vibrationEffect, vibrationUri); + } + + private static Uri getVibrationUriAppended(Uri audioUri, Uri vibrationUri) { + Uri.Builder builder = audioUri.buildUpon(); + builder.appendQueryParameter(Utils.VIBRATION_URI_PARAM, vibrationUri.toString()); + return builder.build(); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java index 0993bec42c6a..4d2396c78d16 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java @@ -22,8 +22,12 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.when; +import android.media.Utils; +import android.net.Uri; import android.os.VibrationEffect; import android.os.Vibrator; +import android.os.VibratorInfo; +import android.provider.Settings; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -36,6 +40,10 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + @SmallTest @RunWith(AndroidJUnit4.class) public class VibratorHelperTest extends UiServiceTestCase { @@ -86,7 +94,34 @@ public class VibratorHelperTest extends UiServiceTestCase { } @Test + public void createVibrationEffectFromSoundUri_nullInput() { + assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(null)); + } + + @Test + public void createVibrationEffectFromSoundUri_emptyUri() { + assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(Uri.EMPTY)); + } + + @Test + public void createVibrationEffectFromSoundUri_uriWithoutRequiredQueryParameter() { + Uri uri = Settings.System.DEFAULT_NOTIFICATION_URI; + assertNull(mVibratorHelper.createVibrationEffectFromSoundUri(uri)); + } + + @Test + public void createVibrationEffectFromSoundUri_uriWithVibrationUri() throws IOException { + // prepare the uri with vibration + when(mVibrator.getInfo()).thenReturn(VibratorInfo.EMPTY_VIBRATOR_INFO); + Uri validUri = getVibrationUriAppended(Settings.System.DEFAULT_NOTIFICATION_URI); + + assertSingleVibration(mVibratorHelper.createVibrationEffectFromSoundUri(validUri)); + } + + @Test public void createVibration_insistent_createsRepeatingVibration() { + when(mVibrator.getInfo()).thenReturn(VibratorInfo.EMPTY_VIBRATOR_INFO); + when(mVibrator.hasFrequencyControl()).thenReturn(false); assertRepeatingVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ true)); assertRepeatingVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ true)); @@ -98,6 +133,8 @@ public class VibratorHelperTest extends UiServiceTestCase { @Test public void createVibration_nonInsistent_createsSingleShotVibration() { + when(mVibrator.getInfo()).thenReturn(VibratorInfo.EMPTY_VIBRATOR_INFO); + when(mVibrator.hasFrequencyControl()).thenReturn(false); assertSingleVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ false)); assertSingleVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ false)); @@ -120,4 +157,26 @@ public class VibratorHelperTest extends UiServiceTestCase { effect instanceof VibrationEffect.Composed); return ((VibrationEffect.Composed) effect).getRepeatIndex(); } + + private static Uri getVibrationUriAppended(Uri baseUri) throws IOException { + File tempVibrationFile = File.createTempFile("test_vibration_file", ".xml"); + FileWriter writer = new FileWriter(tempVibrationFile); + writer.write("<vibration-effect>\n" + + " <waveform-effect>\n" + + " <!-- PRIMING -->\n" + + " <waveform-entry durationMs=\"0\" amplitude=\"0\"/>\n" + + " <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n" + + " <waveform-entry durationMs=\"250\" amplitude=\"0\"/>\n" + + " <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n" + + " <waveform-entry durationMs=\"500\" amplitude=\"0\"/>\n" + + " </waveform-effect>\n" + + "</vibration-effect>"); // Your test XML content + writer.close(); + + Uri.Builder builder = baseUri.buildUpon(); + builder.appendQueryParameter( + Utils.VIBRATION_URI_PARAM, + tempVibrationFile.toURI().toString()); + return builder.build(); + } } |