Merge "Add new default vibration for notifications" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
37de895517
@@ -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<len; i++) {
|
||||
out[i] = ar[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public NotificationManagerService(Context context) {
|
||||
this(context,
|
||||
new NotificationRecordLoggerImpl(),
|
||||
@@ -2041,13 +2018,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
|
||||
@@ -2121,11 +2103,6 @@ public class NotificationManagerService extends SystemService {
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setFallbackVibrationPattern(long[] vibrationPattern) {
|
||||
mFallbackVibrationPattern = vibrationPattern;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setPackageManager(IPackageManager packageManager) {
|
||||
mPackageManager = packageManager;
|
||||
@@ -2200,7 +2177,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;
|
||||
@@ -2288,6 +2264,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.
|
||||
@@ -2309,10 +2286,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()
|
||||
@@ -7439,7 +7412,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
|
||||
@@ -7447,7 +7420,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;
|
||||
@@ -7674,23 +7648,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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
@@ -1071,7 +1071,7 @@ public final class NotificationRecord {
|
||||
return mSound;
|
||||
}
|
||||
|
||||
public long[] getVibration() {
|
||||
public VibrationEffect getVibration() {
|
||||
return mVibration;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user