Add new default vibration for notifications

Use the new WaveformBuilder to create a new default vibration waveform
that also changes the vibration frequency on devices that support it.

Create VibratorHelper in notification package to extract interactions
with the Vibrator service, the creation of VibrationEffect instances
and loading the default and fallback vibrations, using the new pattern
when possible.

Also update NotificationRecord to store a built VibrationEffect instead
of a amplitude-only waveform pattern.

Fix: 186740660
Test: BuzzBeepBlinkTest
      NotificationRecordTest
      VibratorHelperTest
Change-Id: I6c93eb729972abb7a0cfc2b6fd567606224f2f2b
This commit is contained in:
Lais Andrade
2021-05-07 16:52:17 +01:00
parent 33f631ad2a
commit 9b209d5690
10 changed files with 367 additions and 100 deletions

View File

@@ -211,9 +211,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;
@@ -369,12 +367,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";
@@ -482,7 +476,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;
@@ -504,7 +497,6 @@ public class NotificationManagerService extends SystemService {
private LogicalLight mNotificationLight;
LogicalLight mAttentionLight;
private long[] mFallbackVibrationPattern;
private boolean mUseAttentionLight;
boolean mHasLight = true;
boolean mLightEnabled;
@@ -583,6 +575,7 @@ public class NotificationManagerService extends SystemService {
RankingHelper mRankingHelper;
@VisibleForTesting
PreferencesHelper mPreferencesHelper;
private VibratorHelper mVibratorHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
@@ -1597,10 +1590,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);
}
@@ -1994,19 +1984,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<len; i++) {
out[i] = ar[i];
}
return out;
}
public NotificationManagerService(Context context) {
this(context,
new NotificationRecordLoggerImpl(),
@@ -2040,13 +2017,18 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
void setHints(int hints) {
mListenerHints = hints;
VibratorHelper getVibratorHelper() {
return mVibratorHelper;
}
@VisibleForTesting
void setVibrator(Vibrator vibrator) {
mVibrator = vibrator;
void setVibratorHelper(VibratorHelper helper) {
mVibratorHelper = helper;
}
@VisibleForTesting
void setHints(int hints) {
mListenerHints = hints;
}
@VisibleForTesting
@@ -2120,11 +2102,6 @@ public class NotificationManagerService extends SystemService {
mHandler = handler;
}
@VisibleForTesting
void setFallbackVibrationPattern(long[] vibrationPattern) {
mFallbackVibrationPattern = vibrationPattern;
}
@VisibleForTesting
void setPackageManager(IPackageManager packageManager) {
mPackageManager = packageManager;
@@ -2199,7 +2176,6 @@ public class NotificationManagerService extends SystemService {
mPackageManager = packageManager;
mPackageManagerClient = packageManagerClient;
mAppOps = appOps;
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
mAppUsageStats = appUsageStats;
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
mCompanionManager = companionManager;
@@ -2287,6 +2263,7 @@ public class NotificationManagerService extends SystemService {
extractorNames);
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
mVibratorHelper = new VibratorHelper(getContext());
mHistoryManager = historyManager;
// This is a ManagedServices object that keeps track of the listeners.
@@ -2308,10 +2285,6 @@ public class NotificationManagerService extends SystemService {
mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
mFallbackVibrationPattern = getLongArray(resources,
R.array.config_notificationFallbackVibePattern,
VIBRATE_PATTERN_MAXLEN,
DEFAULT_VIBRATE_PATTERN);
mInCallNotificationUri = Uri.parse("file://" +
resources.getString(R.string.config_inCallNotificationSound));
mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
@@ -7392,7 +7365,7 @@ public class NotificationManagerService extends SystemService {
if (mSystemReady && mAudioManager != null) {
Uri soundUri = record.getSound();
hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
long[] vibration = record.getVibration();
VibrationEffect vibration = record.getVibration();
// Demote sound to vibration if vibration missing & phone in vibration mode.
if (vibration == null
&& hasValidSound
@@ -7400,7 +7373,8 @@ public class NotificationManagerService extends SystemService {
== AudioManager.RINGER_MODE_VIBRATE)
&& mAudioManager.getStreamVolume(
AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
vibration = mFallbackVibrationPattern;
boolean insistent = (record.getFlags() & Notification.FLAG_INSISTENT) != 0;
vibration = mVibratorHelper.createFallbackVibration(insistent);
}
hasValidVibrate = vibration != null;
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
@@ -7627,23 +7601,12 @@ public class NotificationManagerService extends SystemService {
return false;
}
private boolean playVibration(final NotificationRecord record, long[] vibration,
private boolean playVibration(final NotificationRecord record, final VibrationEffect effect,
boolean delayVibForSound) {
// Escalate privileges so we can use the vibrator even if the
// notifying app does not have the VIBRATE permission.
final long identity = Binder.clearCallingIdentity();
try {
final VibrationEffect effect;
try {
final boolean insistent =
(record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
effect = VibrationEffect.createWaveform(
vibration, insistent ? 0 : -1 /*repeatIndex*/);
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Error creating vibration waveform with pattern: " +
Arrays.toString(vibration));
return false;
}
if (delayVibForSound) {
new Thread(() -> {
// delay the vibration by the same amount as the notification sound
@@ -7680,8 +7643,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) {

View File

@@ -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<String> 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;
@@ -1067,7 +1067,7 @@ public final class NotificationRecord {
return mSound;
}
public long[] getVibration() {
public VibrationEffect getVibration() {
return mVibration;
}

View File

@@ -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}.
*
* <p>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}.
*
* <p>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;
}
}

View File

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

View File

@@ -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)

View File

@@ -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<AudioAttributes> 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<VibrationEffect> effectMatcher,
VerificationMode verification) {
ArgumentCaptor<AudioAttributes> 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());

View File

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

View File

@@ -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

View File

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

View File

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