Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4cc911dd authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Enable the customised vibration of (1) default notification (2) channel...

Merge "Enable the customised vibration of (1) default notification (2) channel notification." into main
parents c24dc5d5 2ea912c3
Loading
Loading
Loading
Loading
+12 −3
Original line number Diff line number Diff line
@@ -330,12 +330,21 @@ public final class NotificationRecord {
        }

        final long[] vibrationPattern = channel.getVibrationPattern();
        if (vibrationPattern == null) {
            return helper.createDefaultVibration(insistent);
        }
        if (vibrationPattern != null) {
            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() {
        VibratorHelper helper = new VibratorHelper(mContext);
        final Notification notification = getSbn().getNotification();
+31 −0
Original line number Diff line number Diff line
@@ -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);
+7 −0
Original line number Diff line number Diff line
@@ -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"
}
+87 −0
Original line number Diff line number Diff line
@@ -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);
@@ -510,6 +517,51 @@ public class NotificationRecordTest extends UiServiceTestCase {
        assertNull(record.getVibration());
    }

    @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 */,
@@ -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();
    }
}
+59 −0
Original line number Diff line number Diff line
@@ -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 {
@@ -85,8 +93,35 @@ public class VibratorHelperTest extends UiServiceTestCase {
        assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0, 0, 0 }, false));
    }

    @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();
    }
}