diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 98dee86a2bdf8..28c64f84f6310 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -212,9 +212,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.os.VibrationAttributes; import android.os.VibrationEffect; -import android.os.Vibrator; import android.provider.DeviceConfig; import android.provider.Settings; import android.service.notification.Adjustment; @@ -370,12 +368,8 @@ public class NotificationManagerService extends SystemService { // 1 second past the ANR timeout. static final int FINISH_TOKEN_TIMEOUT = 11 * 1000; - static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; - static final long SNOOZE_UNTIL_UNSPECIFIED = -1; - static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps - static final int INVALID_UID = -1; static final String ROOT_PKG = "root"; @@ -483,7 +477,6 @@ public class NotificationManagerService extends SystemService { AudioManagerInternal mAudioManagerInternal; // Can be null for wear @Nullable StatusBarManagerInternal mStatusBar; - Vibrator mVibrator; private WindowManagerInternal mWindowManagerInternal; private AlarmManager mAlarmManager; private ICompanionDeviceManager mCompanionManager; @@ -505,7 +498,6 @@ public class NotificationManagerService extends SystemService { private LogicalLight mNotificationLight; LogicalLight mAttentionLight; - private long[] mFallbackVibrationPattern; private boolean mUseAttentionLight; boolean mHasLight = true; boolean mLightEnabled; @@ -584,6 +576,7 @@ public class NotificationManagerService extends SystemService { RankingHelper mRankingHelper; @VisibleForTesting PreferencesHelper mPreferencesHelper; + private VibratorHelper mVibratorHelper; private final UserProfiles mUserProfiles = new UserProfiles(); private NotificationListeners mListeners; @@ -1598,10 +1591,7 @@ public class NotificationManagerService extends SystemService { mVibrateNotificationKey = null; final long identity = Binder.clearCallingIdentity(); try { - // Stop all vibrations with usage of class alarm (ringtone, alarm, notification usages). - int usageFilter = - VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; - mVibrator.cancel(usageFilter); + mVibratorHelper.cancelVibration(); } finally { Binder.restoreCallingIdentity(identity); } @@ -1995,19 +1985,6 @@ public class NotificationManagerService extends SystemService { private SettingsObserver mSettingsObserver; protected ZenModeHelper mZenModeHelper; - static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) { - int[] ar = r.getIntArray(resid); - if (ar == null) { - return def; - } - final int len = ar.length > maxlen ? maxlen : ar.length; - long[] out = new long[len]; - for (int i=0; i { // delay the vibration by the same amount as the notification sound @@ -7727,8 +7690,7 @@ public class NotificationManagerService extends SystemService { // to the reason so we can still debug from bugreports String reason = "Notification (" + record.getSbn().getOpPkg() + " " + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : ""); - mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, - effect, reason, record.getAudioAttributes()); + mVibratorHelper.vibrate(effect, record.getAudioAttributes(), reason); } private boolean isNotificationForCurrentUser(NotificationRecord record) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 6504ca683eb3f..f66cfa99ce986 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -47,6 +47,7 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; +import android.os.VibrationEffect; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.NotificationListenerService; @@ -158,7 +159,7 @@ public final class NotificationRecord { private String mUserExplanation; private boolean mPreChannelsNotification = true; private Uri mSound; - private long[] mVibration; + private VibrationEffect mVibration; private AudioAttributes mAttributes; private NotificationChannel mChannel; private ArrayList mPeopleOverride; @@ -287,29 +288,28 @@ public final class NotificationRecord { return light; } - private long[] calculateVibration() { - long[] vibration; - final long[] defaultVibration = NotificationManagerService.getLongArray( - mContext.getResources(), - com.android.internal.R.array.config_defaultNotificationVibePattern, - NotificationManagerService.VIBRATE_PATTERN_MAXLEN, - NotificationManagerService.DEFAULT_VIBRATE_PATTERN); + private VibrationEffect calculateVibration() { + VibratorHelper helper = new VibratorHelper(mContext); + final Notification notification = getSbn().getNotification(); + final boolean insistent = (notification.flags & Notification.FLAG_INSISTENT) != 0; + VibrationEffect defaultVibration = helper.createDefaultVibration(insistent); + VibrationEffect vibration; if (getChannel().shouldVibrate()) { vibration = getChannel().getVibrationPattern() == null - ? defaultVibration : getChannel().getVibrationPattern(); + ? defaultVibration + : helper.createWaveformVibration(getChannel().getVibrationPattern(), insistent); } else { vibration = null; } if (mPreChannelsNotification && (getChannel().getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { - final Notification notification = getSbn().getNotification(); final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; if (useDefaultVibrate) { vibration = defaultVibration; } else { - vibration = notification.vibrate; + vibration = helper.createWaveformVibration(notification.vibrate, insistent); } } return vibration; @@ -1071,7 +1071,7 @@ public final class NotificationRecord { return mSound; } - public long[] getVibration() { + public VibrationEffect getVibration() { return mVibration; } diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java new file mode 100644 index 0000000000000..f25b0470cc8ae --- /dev/null +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2021 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.notification; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioAttributes; +import android.os.Process; +import android.os.VibrationAttributes; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.util.Slog; + +import com.android.internal.R; +import com.android.server.pm.PackageManagerService; + +import java.util.Arrays; + +/** + * NotificationManagerService helper for functionality related to the vibrator. + */ +public final class VibratorHelper { + private static final String TAG = "NotificationVibratorHelper"; + + private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; + private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps + + private final Vibrator mVibrator; + private final long[] mDefaultPattern; + private final long[] mFallbackPattern; + + public VibratorHelper(Context context) { + mVibrator = context.getSystemService(Vibrator.class); + mDefaultPattern = getLongArray( + context.getResources(), + com.android.internal.R.array.config_defaultNotificationVibePattern, + VIBRATE_PATTERN_MAXLEN, + DEFAULT_VIBRATE_PATTERN); + mFallbackPattern = getLongArray(context.getResources(), + R.array.config_notificationFallbackVibePattern, + VIBRATE_PATTERN_MAXLEN, + DEFAULT_VIBRATE_PATTERN); + } + + /** + * Safely create a {@link VibrationEffect} from given vibration {@code pattern}. + * + *

This method returns {@code null} if the pattern is also {@code null} or invalid. + * + * @param pattern The off/on vibration pattern, where each item is a duration in milliseconds. + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + @Nullable + public static VibrationEffect createWaveformVibration(@Nullable long[] pattern, + boolean insistent) { + try { + if (pattern != null) { + return VibrationEffect.createWaveform(pattern, /* repeat= */ insistent ? 0 : -1); + } + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Error creating vibration waveform with pattern: " + + Arrays.toString(pattern)); + } + return null; + } + + /** + * Vibrate the device with given {@code effect}. + * + *

We need to vibrate as "android" so we can breakthrough DND. + */ + public void vibrate(VibrationEffect effect, AudioAttributes attrs, String reason) { + mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, + effect, reason, attrs); + } + + /** Stop all notification vibrations (ringtone, alarm, notification usages). */ + public void cancelVibration() { + int usageFilter = + VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; + mVibrator.cancel(usageFilter); + } + + /** + * Creates a vibration to be used as fallback when the device is in vibrate mode. + * + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + public VibrationEffect createFallbackVibration(boolean insistent) { + if (mVibrator.hasFrequencyControl()) { + return createChirpVibration(insistent); + } + return createWaveformVibration(mFallbackPattern, insistent); + } + + /** + * Creates a vibration to be used by notifications without a custom pattern. + * + * @param insistent {@code true} if the vibration should loop until it is cancelled. + */ + public VibrationEffect createDefaultVibration(boolean insistent) { + if (mVibrator.hasFrequencyControl()) { + return createChirpVibration(insistent); + } + return createWaveformVibration(mDefaultPattern, insistent); + } + + private static VibrationEffect createChirpVibration(boolean insistent) { + VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform() + .addStep(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 0) + .addRamp(/* amplitude= */ 1, /* frequency= */ -0.25f, /* duration= */ 100) + .addStep(/* amplitude= */ 1, /* duration= */ 150) + .addRamp(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 250); + + if (insistent) { + return waveformBuilder.build(/* repeat= */ 0); + } + + VibrationEffect singleBeat = waveformBuilder.build(); + return VibrationEffect.startComposition() + .addEffect(singleBeat) + .addEffect(singleBeat) + .compose(); + } + + private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { + int[] ar = resources.getIntArray(resId); + if (ar == null) { + return def; + } + final int len = ar.length > maxLength ? maxLength : ar.length; + long[] out = new long[len]; + for (int i = 0; i < len; i++) { + out[i] = ar[i]; + } + return out; + } +} diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index a09bfc57ba233..5dceac2d066cb 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -67,7 +67,7 @@ final class VibratorController { mNativeWrapper = nativeWrapper; mNativeWrapper.init(vibratorId, listener); // TODO(b/167947076): load suggested range from config - mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 100); + mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 200); Preconditions.checkNotNull(mVibratorInfo, "Failed to retrieve data for vibrator %d", vibratorId); } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java index 59370e2106528..9e98e7d0410c6 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -297,7 +298,7 @@ public class VibratorControllerTest { private void mockVibratorCapabilities(int capabilities) { VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping( Float.NaN, Float.NaN, Float.NaN, Float.NaN, null); - when(mNativeWrapperMock.getInfo(/* suggestedFrequencyRange= */ 100)).thenReturn( + when(mNativeWrapperMock.getInfo(anyFloat())).thenReturn( new VibratorInfo.Builder(VIBRATOR_ID) .setCapabilities(capabilities) .setFrequencyMapping(frequencyMapping) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 00eb0f2847196..3862d754b8f5a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -93,6 +93,9 @@ import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.verification.VerificationMode; + +import java.util.Objects; @SmallTest @RunWith(AndroidJUnit4.class) @@ -137,20 +140,19 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { private static final int CUSTOM_LIGHT_COLOR = Color.BLACK; private static final int CUSTOM_LIGHT_ON = 10000; private static final int CUSTOM_LIGHT_OFF = 10000; - private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100}; - private static final VibrationEffect FALLBACK_VIBRATION = - VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1); private static final int MAX_VIBRATION_DELAY = 1000; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + getContext().addMockSystemService(Vibrator.class, mVibrator); when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); + when(mVibrator.hasFrequencyControl()).thenReturn(false); long serviceReturnValue = IntPair.of( AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED, @@ -164,13 +166,12 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger, mNotificationInstanceIdSequence)); + mService.setVibratorHelper(new VibratorHelper(getContext())); mService.setAudioManager(mAudioManager); - mService.setVibrator(mVibrator); mService.setSystemReady(true); mService.setHandler(mHandler); mService.setLights(mLight); mService.setScreenOn(false); - mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN); mService.setUsageStats(mUsageStats); mService.setAccessibilityManager(accessibilityManager); mService.mScreenOn = false; @@ -416,32 +417,34 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { } private void verifyVibrate() { - ArgumentCaptor captor = ArgumentCaptor.forClass(AudioAttributes.class); - verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher), - anyString(), captor.capture()); - assertEquals(0, (captor.getValue().getAllFlags() - & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); + verifyVibrate(/* times= */ 1); } private void verifyVibrate(int times) { - verify(mVibrator, times(times)).vibrate(eq(Process.SYSTEM_UID), - eq(PackageManagerService.PLATFORM_PACKAGE_NAME), any(), anyString(), - any(AudioAttributes.class)); + verifyVibrate(mVibrateOnceMatcher, times(times)); } private void verifyVibrateLooped() { - verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher), - anyString(), any(AudioAttributes.class)); + verifyVibrate(mVibrateLoopMatcher, times(1)); } private void verifyDelayedVibrateLooped() { - verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(), - argThat(mVibrateLoopMatcher), anyString(), any(AudioAttributes.class)); + verifyVibrate(mVibrateLoopMatcher, timeout(MAX_VIBRATION_DELAY).times(1)); } - private void verifyDelayedVibrate() { - verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(), - argThat(mVibrateOnceMatcher), anyString(), any(AudioAttributes.class)); + private void verifyDelayedVibrate(VibrationEffect effect) { + verifyVibrate(argument -> Objects.equals(effect, argument), + timeout(MAX_VIBRATION_DELAY).times(1)); + } + + private void verifyVibrate(ArgumentMatcher effectMatcher, + VerificationMode verification) { + ArgumentCaptor captor = ArgumentCaptor.forClass(AudioAttributes.class); + verify(mVibrator, verification).vibrate(eq(Process.SYSTEM_UID), + eq(PackageManagerService.PLATFORM_PACKAGE_NAME), argThat(effectMatcher), + anyString(), captor.capture()); + assertEquals(0, (captor.getValue().getAllFlags() + & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); } private void verifyStopVibrate() { @@ -761,11 +764,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); - VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1); - - verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(), - eq(effect), anyString(), - (AudioAttributes) anyObject()); + verifyDelayedVibrate(r.getVibration()); assertTrue(r.isInterruptive()); assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @@ -800,8 +799,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); - verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(), - eq(FALLBACK_VIBRATION), anyString(), (AudioAttributes) anyObject()); + verifyDelayedVibrate( + mService.getVibratorHelper().createFallbackVibration(/* insistent= */ false)); verify(mRingtonePlayer, never()).playAsync (anyObject(), anyObject(), anyBoolean(), anyObject()); assertTrue(r.isInterruptive()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index 6a8f602a8350e..5eed30be92795 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -37,6 +37,7 @@ import android.content.pm.PackageManager; import android.graphics.Color; import android.os.Build; import android.os.UserHandle; +import android.os.Vibrator; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; @@ -63,6 +64,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { @Mock TelecomManager mTm; @Mock RankingHandler handler; @Mock PackageManager mPm; + @Mock Vibrator mVibrator; private final String callPkg = "com.android.server.notification"; private final int callUid = 10; @@ -98,6 +100,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { when(mContext.getContentResolver()).thenReturn(getContext().getContentResolver()); when(mContext.getPackageManager()).thenReturn(mPm); when(mContext.getSystemService(eq(Context.TELECOM_SERVICE))).thenReturn(mTm); + when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator); when(mContext.getString(anyInt())).thenCallRealMethod(); when(mContext.getColor(anyInt())).thenCallRealMethod(); when(mTm.getDefaultDialerPackage()).thenReturn(callPkg); 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 38c470da50294..49b63863fec75 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,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.UserHandle; +import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.StatusBarNotification; @@ -82,7 +83,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; -import java.util.Arrays; @SmallTest @RunWith(AndroidJUnit4.class) @@ -91,6 +91,7 @@ public class NotificationRecordTest extends UiServiceTestCase { private final Context mMockContext = mock(Context.class); @Mock private PackageManager mPm; @Mock private ContentResolver mContentResolver; + @Mock private Vibrator mVibrator; private final String mPkg = PKG_O; private final int uid = 9583; @@ -122,6 +123,7 @@ public class NotificationRecordTest extends UiServiceTestCase { public void setUp() { MockitoAnnotations.initMocks(this); + when(mMockContext.getSystemService(eq(Vibrator.class))).thenReturn(mVibrator); when(mMockContext.getResources()).thenReturn(getContext().getResources()); when(mMockContext.getPackageManager()).thenReturn(mPm); when(mMockContext.getContentResolver()).thenReturn(mContentResolver); @@ -203,6 +205,26 @@ public class NotificationRecordTest extends UiServiceTestCase { return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid); } + + private StatusBarNotification getInsistentNotification(boolean defaultVibration) { + final Builder builder = new Builder(mMockContext) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setPriority(Notification.PRIORITY_HIGH); + int defaults = 0; + if (defaultVibration) { + defaults |= Notification.DEFAULT_VIBRATE; + } else { + builder.setVibrate(CUSTOM_VIBRATION); + channel.setVibrationPattern(CUSTOM_CHANNEL_VIBRATION); + } + builder.setDefaults(defaults); + builder.setFlag(Notification.FLAG_INSISTENT, true); + + Notification n = builder.build(); + return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid); + } + private StatusBarNotification getMessagingStyleNotification() { return getMessagingStyleNotification(mPkg); } @@ -309,7 +331,8 @@ public class NotificationRecordTest extends UiServiceTestCase { false /* lights */, false /* defaultLights */, null /* group */); NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); - assertEquals(CUSTOM_VIBRATION, record.getVibration()); + assertEquals(VibratorHelper.createWaveformVibration( + CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration()); } @Test @@ -322,7 +345,8 @@ public class NotificationRecordTest extends UiServiceTestCase { false /* lights */, false /* defaultLights */, null /* group */); NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel); - assertTrue(!Arrays.equals(CUSTOM_VIBRATION, record.getVibration())); + assertNotEquals(VibratorHelper.createWaveformVibration( + CUSTOM_VIBRATION, /* insistent= */ false), record.getVibration()); } @Test @@ -334,7 +358,18 @@ public class NotificationRecordTest extends UiServiceTestCase { false /* lights */, false /* defaultLights */, null /* group */); NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); - assertEquals(CUSTOM_CHANNEL_VIBRATION, record.getVibration()); + assertEquals(VibratorHelper.createWaveformVibration( + CUSTOM_CHANNEL_VIBRATION, /* insistent= */ false), record.getVibration()); + } + + @Test + public void testVibration_insistent_createsInsistentVibrationEffect() { + channel.enableVibration(true); + StatusBarNotification sbn = getInsistentNotification(false /* defaultBuzz */); + + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + assertEquals(VibratorHelper.createWaveformVibration( + CUSTOM_CHANNEL_VIBRATION, /* insistent= */ true), record.getVibration()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java index b83f9f266ad72..4217881495b16 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java @@ -41,6 +41,7 @@ import android.media.AudioAttributes; import android.net.Uri; import android.os.Build; import android.os.UserHandle; +import android.os.Vibrator; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.testing.TestableContentResolver; @@ -84,6 +85,7 @@ public class RankingHelperTest extends UiServiceTestCase { @Mock Context mContext; @Mock ZenModeHelper mMockZenModeHelper; @Mock RankingConfig mConfig; + @Mock Vibrator mVibrator; private NotificationManager.Policy mTestNotificationPolicy; private Notification mNotiGroupGSortA; @@ -125,6 +127,7 @@ public class RankingHelperTest extends UiServiceTestCase { InstrumentationRegistry.getContext().getContentResolver()); when(mContext.getPackageManager()).thenReturn(mPm); when(mContext.getApplicationInfo()).thenReturn(legacy); + when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator); TestableContentResolver contentResolver = getContext().getContentResolver(); contentResolver.setFallbackToExisting(false); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java new file mode 100644 index 0000000000000..c77a474e032c6 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 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.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.when; + +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.UiServiceTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VibratorHelperTest extends UiServiceTestCase { + + private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 }; + + @Mock private Vibrator mVibrator; + + VibratorHelper mVibratorHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + getContext().addMockSystemService(Vibrator.class, mVibrator); + + mVibratorHelper = new VibratorHelper(getContext()); + } + + @Test + public void createWaveformVibration_insistent_createsRepeatingVibration() { + assertRepeatingVibration( + VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true)); + } + + @Test + public void createWaveformVibration_nonInsistent_createsSingleShotVibration() { + assertSingleVibration( + VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false)); + } + + @Test + public void createWaveformVibration_invalidPattern_returnsNullAndDoesNotCrash() { + assertNull(VibratorHelper.createWaveformVibration(null, false)); + assertNull(VibratorHelper.createWaveformVibration(new long[0], false)); + assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false)); + } + + @Test + public void createVibration_insistent_createsRepeatingVibration() { + when(mVibrator.hasFrequencyControl()).thenReturn(false); + assertRepeatingVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ true)); + assertRepeatingVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ true)); + + when(mVibrator.hasFrequencyControl()).thenReturn(true); + assertRepeatingVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ true)); + assertRepeatingVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ true)); + } + + @Test + public void createVibration_nonInsistent_createsSingleShotVibration() { + when(mVibrator.hasFrequencyControl()).thenReturn(false); + assertSingleVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ false)); + assertSingleVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ false)); + + when(mVibrator.hasFrequencyControl()).thenReturn(true); + assertSingleVibration(mVibratorHelper.createDefaultVibration(/* insistent= */ false)); + assertSingleVibration(mVibratorHelper.createFallbackVibration(/* insistent= */ false)); + } + + private void assertRepeatingVibration(VibrationEffect effect) { + assertTrue(getRepeatIndex(effect) >= 0); + } + + private void assertSingleVibration(VibrationEffect effect) { + assertEquals(-1, getRepeatIndex(effect)); + } + + private static int getRepeatIndex(VibrationEffect effect) { + assertTrue("Unknown vibration effect " + effect, + effect instanceof VibrationEffect.Composed); + return ((VibrationEffect.Composed) effect).getRepeatIndex(); + } +}