Merge "actually cancel sounds when we get a quiet update" into nyc-dev
This commit is contained in:
@@ -120,6 +120,7 @@ import android.view.accessibility.AccessibilityManager;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.android.internal.R;
|
import com.android.internal.R;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.statusbar.NotificationVisibility;
|
import com.android.internal.statusbar.NotificationVisibility;
|
||||||
import com.android.internal.util.FastXmlSerializer;
|
import com.android.internal.util.FastXmlSerializer;
|
||||||
import com.android.internal.util.Preconditions;
|
import com.android.internal.util.Preconditions;
|
||||||
@@ -226,7 +227,7 @@ public class NotificationManagerService extends SystemService {
|
|||||||
private VrManagerInternal mVrManagerInternal;
|
private VrManagerInternal mVrManagerInternal;
|
||||||
|
|
||||||
final IBinder mForegroundToken = new Binder();
|
final IBinder mForegroundToken = new Binder();
|
||||||
private WorkerHandler mHandler;
|
private Handler mHandler;
|
||||||
private final HandlerThread mRankingThread = new HandlerThread("ranker",
|
private final HandlerThread mRankingThread = new HandlerThread("ranker",
|
||||||
Process.THREAD_PRIORITY_BACKGROUND);
|
Process.THREAD_PRIORITY_BACKGROUND);
|
||||||
|
|
||||||
@@ -572,33 +573,9 @@ public class NotificationManagerService extends SystemService {
|
|||||||
public void clearEffects() {
|
public void clearEffects() {
|
||||||
synchronized (mNotificationList) {
|
synchronized (mNotificationList) {
|
||||||
if (DBG) Slog.d(TAG, "clearEffects");
|
if (DBG) Slog.d(TAG, "clearEffects");
|
||||||
|
clearSoundLocked();
|
||||||
// sound
|
clearVibrateLocked();
|
||||||
mSoundNotificationKey = null;
|
clearLightsLocked();
|
||||||
|
|
||||||
long identity = Binder.clearCallingIdentity();
|
|
||||||
try {
|
|
||||||
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
|
|
||||||
if (player != null) {
|
|
||||||
player.stopAsync();
|
|
||||||
}
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
} finally {
|
|
||||||
Binder.restoreCallingIdentity(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// vibrate
|
|
||||||
mVibrateNotificationKey = null;
|
|
||||||
identity = Binder.clearCallingIdentity();
|
|
||||||
try {
|
|
||||||
mVibrator.cancel();
|
|
||||||
} finally {
|
|
||||||
Binder.restoreCallingIdentity(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// light
|
|
||||||
mLights.clear();
|
|
||||||
updateLightsLocked();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,6 +635,36 @@ public class NotificationManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private void clearSoundLocked() {
|
||||||
|
mSoundNotificationKey = null;
|
||||||
|
long identity = Binder.clearCallingIdentity();
|
||||||
|
try {
|
||||||
|
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
|
||||||
|
if (player != null) {
|
||||||
|
player.stopAsync();
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
} finally {
|
||||||
|
Binder.restoreCallingIdentity(identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearVibrateLocked() {
|
||||||
|
mVibrateNotificationKey = null;
|
||||||
|
long identity = Binder.clearCallingIdentity();
|
||||||
|
try {
|
||||||
|
mVibrator.cancel();
|
||||||
|
} finally {
|
||||||
|
Binder.restoreCallingIdentity(identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearLightsLocked() {
|
||||||
|
// light
|
||||||
|
mLights.clear();
|
||||||
|
updateLightsLocked();
|
||||||
|
}
|
||||||
|
|
||||||
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
|
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@@ -863,6 +870,26 @@ public class NotificationManagerService extends SystemService {
|
|||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setAudioManager(AudioManager audioMananger) {
|
||||||
|
mAudioManager = audioMananger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setVibrator(Vibrator vibrator) {
|
||||||
|
mVibrator = vibrator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setSystemReady(boolean systemReady) {
|
||||||
|
mSystemReady = systemReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setHandler(Handler handler) {
|
||||||
|
mHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
Resources resources = getContext().getResources();
|
Resources resources = getContext().getResources();
|
||||||
@@ -2492,12 +2519,14 @@ public class NotificationManagerService extends SystemService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buzzBeepBlinkLocked(NotificationRecord record) {
|
@VisibleForTesting
|
||||||
|
void buzzBeepBlinkLocked(NotificationRecord record) {
|
||||||
boolean buzz = false;
|
boolean buzz = false;
|
||||||
boolean beep = false;
|
boolean beep = false;
|
||||||
boolean blink = false;
|
boolean blink = false;
|
||||||
|
|
||||||
final Notification notification = record.sbn.getNotification();
|
final Notification notification = record.sbn.getNotification();
|
||||||
|
final String key = record.getKey();
|
||||||
|
|
||||||
// Should this notification make noise, vibe, or use the LED?
|
// Should this notification make noise, vibe, or use the LED?
|
||||||
final boolean aboveThreshold = record.getImportance() >= IMPORTANCE_DEFAULT;
|
final boolean aboveThreshold = record.getImportance() >= IMPORTANCE_DEFAULT;
|
||||||
@@ -2521,9 +2550,15 @@ public class NotificationManagerService extends SystemService {
|
|||||||
if (disableEffects != null) {
|
if (disableEffects != null) {
|
||||||
ZenLog.traceDisableEffects(record, disableEffects);
|
ZenLog.traceDisableEffects(record, disableEffects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remember if this notification already owns the notification channels.
|
||||||
|
boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
|
||||||
|
boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
|
||||||
|
|
||||||
|
// These are set inside the conditional if the notification is allowed to make noise.
|
||||||
|
boolean hasValidVibrate = false;
|
||||||
|
boolean hasValidSound = false;
|
||||||
if (disableEffects == null
|
if (disableEffects == null
|
||||||
&& (!(record.isUpdate
|
|
||||||
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
|
|
||||||
&& (record.getUserId() == UserHandle.USER_ALL ||
|
&& (record.getUserId() == UserHandle.USER_ALL ||
|
||||||
record.getUserId() == currentUser ||
|
record.getUserId() == currentUser ||
|
||||||
mUserProfiles.isCurrentProfile(record.getUserId()))
|
mUserProfiles.isCurrentProfile(record.getUserId()))
|
||||||
@@ -2532,10 +2567,6 @@ public class NotificationManagerService extends SystemService {
|
|||||||
&& mAudioManager != null) {
|
&& mAudioManager != null) {
|
||||||
if (DBG) Slog.v(TAG, "Interrupting!");
|
if (DBG) Slog.v(TAG, "Interrupting!");
|
||||||
|
|
||||||
sendAccessibilityEvent(notification, record.sbn.getPackageName());
|
|
||||||
|
|
||||||
// sound
|
|
||||||
|
|
||||||
// should we use the default notification sound? (indicated either by
|
// should we use the default notification sound? (indicated either by
|
||||||
// DEFAULT_SOUND or because notification.sound is pointing at
|
// DEFAULT_SOUND or because notification.sound is pointing at
|
||||||
// Settings.System.NOTIFICATION_SOUND)
|
// Settings.System.NOTIFICATION_SOUND)
|
||||||
@@ -2545,8 +2576,6 @@ public class NotificationManagerService extends SystemService {
|
|||||||
.equals(notification.sound);
|
.equals(notification.sound);
|
||||||
|
|
||||||
Uri soundUri = null;
|
Uri soundUri = null;
|
||||||
boolean hasValidSound = false;
|
|
||||||
|
|
||||||
if (useDefaultSound) {
|
if (useDefaultSound) {
|
||||||
soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
|
soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||||
|
|
||||||
@@ -2559,88 +2588,105 @@ public class NotificationManagerService extends SystemService {
|
|||||||
hasValidSound = (soundUri != null);
|
hasValidSound = (soundUri != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasValidSound) {
|
|
||||||
boolean looping =
|
|
||||||
(notification.flags & Notification.FLAG_INSISTENT) != 0;
|
|
||||||
AudioAttributes audioAttributes = audioAttributesForNotification(notification);
|
|
||||||
mSoundNotificationKey = record.getKey();
|
|
||||||
// do not play notifications if stream volume is 0 (typically because
|
|
||||||
// ringer mode is silent) or if there is a user of exclusive audio focus
|
|
||||||
if ((mAudioManager.getStreamVolume(
|
|
||||||
AudioAttributes.toLegacyStreamType(audioAttributes)) != 0)
|
|
||||||
&& !mAudioManager.isAudioFocusExclusive()) {
|
|
||||||
final long identity = Binder.clearCallingIdentity();
|
|
||||||
try {
|
|
||||||
final IRingtonePlayer player =
|
|
||||||
mAudioManager.getRingtonePlayer();
|
|
||||||
if (player != null) {
|
|
||||||
if (DBG) Slog.v(TAG, "Playing sound " + soundUri
|
|
||||||
+ " with attributes " + audioAttributes);
|
|
||||||
player.playAsync(soundUri, record.sbn.getUser(), looping,
|
|
||||||
audioAttributes);
|
|
||||||
beep = true;
|
|
||||||
}
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
} finally {
|
|
||||||
Binder.restoreCallingIdentity(identity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// vibrate
|
|
||||||
// Does the notification want to specify its own vibration?
|
// Does the notification want to specify its own vibration?
|
||||||
final boolean hasCustomVibrate = notification.vibrate != null;
|
final boolean hasCustomVibrate = notification.vibrate != null;
|
||||||
|
|
||||||
// new in 4.2: if there was supposed to be a sound and we're in vibrate
|
// new in 4.2: if there was supposed to be a sound and we're in vibrate
|
||||||
// mode, and no other vibration is specified, we fall back to vibration
|
// mode, and no other vibration is specified, we fall back to vibration
|
||||||
final boolean convertSoundToVibration =
|
final boolean convertSoundToVibration =
|
||||||
!hasCustomVibrate
|
!hasCustomVibrate
|
||||||
&& hasValidSound
|
&& hasValidSound
|
||||||
&& (mAudioManager.getRingerModeInternal()
|
&& (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE);
|
||||||
== AudioManager.RINGER_MODE_VIBRATE);
|
|
||||||
|
|
||||||
// The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
|
// The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
|
||||||
final boolean useDefaultVibrate =
|
final boolean useDefaultVibrate =
|
||||||
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
|
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
|
||||||
|
|
||||||
if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
|
hasValidVibrate = useDefaultVibrate || convertSoundToVibration ||
|
||||||
&& !(mAudioManager.getRingerModeInternal()
|
hasCustomVibrate;
|
||||||
== AudioManager.RINGER_MODE_SILENT)) {
|
|
||||||
mVibrateNotificationKey = record.getKey();
|
|
||||||
|
|
||||||
if (useDefaultVibrate || convertSoundToVibration) {
|
// We can alert, and we're allowed to alert, but if the developer asked us to only do
|
||||||
// Escalate privileges so we can use the vibrator even if the
|
// it once, and we already have, then don't.
|
||||||
// notifying app does not have the VIBRATE permission.
|
if (!(record.isUpdate
|
||||||
long identity = Binder.clearCallingIdentity();
|
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)) {
|
||||||
try {
|
|
||||||
mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
|
sendAccessibilityEvent(notification, record.sbn.getPackageName());
|
||||||
useDefaultVibrate ? mDefaultVibrationPattern
|
|
||||||
: mFallbackVibrationPattern,
|
if (hasValidSound) {
|
||||||
((notification.flags & Notification.FLAG_INSISTENT) != 0)
|
boolean looping =
|
||||||
? 0: -1, audioAttributesForNotification(notification));
|
(notification.flags & Notification.FLAG_INSISTENT) != 0;
|
||||||
buzz = true;
|
AudioAttributes audioAttributes = audioAttributesForNotification(notification);
|
||||||
} finally {
|
mSoundNotificationKey = key;
|
||||||
Binder.restoreCallingIdentity(identity);
|
// do not play notifications if stream volume is 0 (typically because
|
||||||
|
// ringer mode is silent) or if there is a user of exclusive audio focus
|
||||||
|
if ((mAudioManager.getStreamVolume(
|
||||||
|
AudioAttributes.toLegacyStreamType(audioAttributes)) != 0)
|
||||||
|
&& !mAudioManager.isAudioFocusExclusive()) {
|
||||||
|
final long identity = Binder.clearCallingIdentity();
|
||||||
|
try {
|
||||||
|
final IRingtonePlayer player =
|
||||||
|
mAudioManager.getRingtonePlayer();
|
||||||
|
if (player != null) {
|
||||||
|
if (DBG) Slog.v(TAG, "Playing sound " + soundUri
|
||||||
|
+ " with attributes " + audioAttributes);
|
||||||
|
player.playAsync(soundUri, record.sbn.getUser(), looping,
|
||||||
|
audioAttributes);
|
||||||
|
beep = true;
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
} finally {
|
||||||
|
Binder.restoreCallingIdentity(identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasValidVibrate && !(mAudioManager.getRingerModeInternal()
|
||||||
|
== AudioManager.RINGER_MODE_SILENT)) {
|
||||||
|
mVibrateNotificationKey = key;
|
||||||
|
|
||||||
|
if (useDefaultVibrate || convertSoundToVibration) {
|
||||||
|
// Escalate privileges so we can use the vibrator even if the
|
||||||
|
// notifying app does not have the VIBRATE permission.
|
||||||
|
long identity = Binder.clearCallingIdentity();
|
||||||
|
try {
|
||||||
|
mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
|
||||||
|
useDefaultVibrate ? mDefaultVibrationPattern
|
||||||
|
: mFallbackVibrationPattern,
|
||||||
|
((notification.flags & Notification.FLAG_INSISTENT) != 0)
|
||||||
|
? 0: -1, audioAttributesForNotification(notification));
|
||||||
|
buzz = true;
|
||||||
|
} finally {
|
||||||
|
Binder.restoreCallingIdentity(identity);
|
||||||
|
}
|
||||||
|
} else if (notification.vibrate.length > 1) {
|
||||||
|
// If you want your own vibration pattern, you need the VIBRATE
|
||||||
|
// permission
|
||||||
|
mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
|
||||||
|
notification.vibrate,
|
||||||
|
((notification.flags & Notification.FLAG_INSISTENT) != 0)
|
||||||
|
? 0: -1, audioAttributesForNotification(notification));
|
||||||
|
buzz = true;
|
||||||
}
|
}
|
||||||
} else if (notification.vibrate.length > 1) {
|
|
||||||
// If you want your own vibration pattern, you need the VIBRATE
|
|
||||||
// permission
|
|
||||||
mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
|
|
||||||
notification.vibrate,
|
|
||||||
((notification.flags & Notification.FLAG_INSISTENT) != 0)
|
|
||||||
? 0: -1, audioAttributesForNotification(notification));
|
|
||||||
buzz = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// If a notification is updated to remove the actively playing sound or vibrate,
|
||||||
|
// cancel that feedback now
|
||||||
|
if (wasBeep && !hasValidSound) {
|
||||||
|
clearSoundLocked();
|
||||||
|
}
|
||||||
|
if (wasBuzz && !hasValidVibrate) {
|
||||||
|
clearVibrateLocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
// light
|
// light
|
||||||
// release the light
|
// release the light
|
||||||
boolean wasShowLights = mLights.remove(record.getKey());
|
boolean wasShowLights = mLights.remove(key);
|
||||||
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
|
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold
|
||||||
&& ((record.getSuppressedVisualEffects()
|
&& ((record.getSuppressedVisualEffects()
|
||||||
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {
|
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {
|
||||||
mLights.add(record.getKey());
|
mLights.add(key);
|
||||||
updateLightsLocked();
|
updateLightsLocked();
|
||||||
if (mUseAttentionLight) {
|
if (mUseAttentionLight) {
|
||||||
mAttentionLight.pulse();
|
mAttentionLight.pulse();
|
||||||
@@ -2654,7 +2700,7 @@ public class NotificationManagerService extends SystemService {
|
|||||||
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) {
|
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) {
|
||||||
if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on");
|
if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on");
|
||||||
} else {
|
} else {
|
||||||
EventLogTags.writeNotificationAlert(record.getKey(),
|
EventLogTags.writeNotificationAlert(key,
|
||||||
buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
|
buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
|
||||||
mHandler.post(mBuzzBeepBlinked);
|
mHandler.post(mBuzzBeepBlinked);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,541 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.app.ActivityManager;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.Notification.Builder;
|
||||||
|
import android.media.AudioAttributes;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.service.notification.NotificationListenerService.Ranking;
|
||||||
|
import android.service.notification.StatusBarNotification;
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import static org.mockito.Matchers.anyBoolean;
|
||||||
|
import static org.mockito.Matchers.anyInt;
|
||||||
|
import static org.mockito.Matchers.anyObject;
|
||||||
|
import static org.mockito.Matchers.anyString;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BuzzBeepBlinkTest extends AndroidTestCase {
|
||||||
|
|
||||||
|
@Mock AudioManager mAudioManager;
|
||||||
|
@Mock Vibrator mVibrator;
|
||||||
|
@Mock android.media.IRingtonePlayer mRingtonePlayer;
|
||||||
|
@Mock Handler mHandler;
|
||||||
|
|
||||||
|
private NotificationManagerService mService;
|
||||||
|
private String mPkg = "com.android.server.notification";
|
||||||
|
private int mId = 1001;
|
||||||
|
private int mOtherId = 1002;
|
||||||
|
private String mTag = null;
|
||||||
|
private int mUid = 1000;
|
||||||
|
private int mPid = 2000;
|
||||||
|
private int mScore = 10;
|
||||||
|
private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
|
||||||
|
when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
|
||||||
|
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
|
||||||
|
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
|
||||||
|
|
||||||
|
mService = new NotificationManagerService(getContext());
|
||||||
|
mService.setAudioManager(mAudioManager);
|
||||||
|
mService.setVibrator(mVibrator);
|
||||||
|
mService.setSystemReady(true);
|
||||||
|
mService.setHandler(mHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Convenience functions for creating notification records
|
||||||
|
//
|
||||||
|
|
||||||
|
private NotificationRecord getNoisyOtherNotification() {
|
||||||
|
return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
|
||||||
|
true /* noisy */, true /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getBeepyNotification() {
|
||||||
|
return getNotificationRecord(mId, false /* insistent */, false /* once */,
|
||||||
|
true /* noisy */, false /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getBeepyOnceNotification() {
|
||||||
|
return getNotificationRecord(mId, false /* insistent */, true /* once */,
|
||||||
|
true /* noisy */, false /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getQuietNotification() {
|
||||||
|
return getNotificationRecord(mId, false /* insistent */, false /* once */,
|
||||||
|
false /* noisy */, false /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getQuietOtherNotification() {
|
||||||
|
return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
|
||||||
|
false /* noisy */, false /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getQuietOnceNotification() {
|
||||||
|
return getNotificationRecord(mId, false /* insistent */, true /* once */,
|
||||||
|
false /* noisy */, false /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getInsistentBeepyNotification() {
|
||||||
|
return getNotificationRecord(mId, true /* insistent */, false /* once */,
|
||||||
|
true /* noisy */, false /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getBuzzyNotification() {
|
||||||
|
return getNotificationRecord(mId, false /* insistent */, false /* once */,
|
||||||
|
false /* noisy */, true /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getBuzzyOnceNotification() {
|
||||||
|
return getNotificationRecord(mId, false /* insistent */, true /* once */,
|
||||||
|
false /* noisy */, true /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getInsistentBuzzyNotification() {
|
||||||
|
return getNotificationRecord(mId, true /* insistent */, false /* once */,
|
||||||
|
false /* noisy */, true /* buzzy*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
|
||||||
|
boolean noisy, boolean buzzy) {
|
||||||
|
final Builder builder = new Builder(getContext())
|
||||||
|
.setContentTitle("foo")
|
||||||
|
.setSmallIcon(android.R.drawable.sym_def_app_icon)
|
||||||
|
.setPriority(Notification.PRIORITY_HIGH)
|
||||||
|
.setOnlyAlertOnce(once);
|
||||||
|
|
||||||
|
int defaults = 0;
|
||||||
|
if (noisy) {
|
||||||
|
defaults |= Notification.DEFAULT_SOUND;
|
||||||
|
}
|
||||||
|
if (buzzy) {
|
||||||
|
defaults |= Notification.DEFAULT_VIBRATE;
|
||||||
|
}
|
||||||
|
builder.setDefaults(defaults);
|
||||||
|
|
||||||
|
Notification n = builder.build();
|
||||||
|
if (insistent) {
|
||||||
|
n.flags |= Notification.FLAG_INSISTENT;
|
||||||
|
}
|
||||||
|
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, mPid,
|
||||||
|
mScore, n, mUser, System.currentTimeMillis());
|
||||||
|
return new NotificationRecord(getContext(), sbn);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Convenience functions for interacting with mocks
|
||||||
|
//
|
||||||
|
|
||||||
|
private void verifyNeverBeep() throws RemoteException {
|
||||||
|
verify(mRingtonePlayer, never()).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
|
||||||
|
anyBoolean(), (AudioAttributes) anyObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyBeep() throws RemoteException {
|
||||||
|
verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
|
||||||
|
eq(true), (AudioAttributes) anyObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyBeepLooped() throws RemoteException {
|
||||||
|
verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
|
||||||
|
eq(false), (AudioAttributes) anyObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNeverStopAudio() throws RemoteException {
|
||||||
|
verify(mRingtonePlayer, never()).stopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyStopAudio() throws RemoteException {
|
||||||
|
verify(mRingtonePlayer, times(1)).stopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNeverVibrate() {
|
||||||
|
verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(),
|
||||||
|
anyInt(), (AudioAttributes) anyObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyVibrate() {
|
||||||
|
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
|
||||||
|
eq(-1), (AudioAttributes) anyObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyVibrateLooped() {
|
||||||
|
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
|
||||||
|
eq(0), (AudioAttributes) anyObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyStopVibrate() {
|
||||||
|
verify(mVibrator, times(1)).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNeverStopVibrate() throws RemoteException {
|
||||||
|
verify(mVibrator, never()).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testBeep() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
verifyBeepLooped();
|
||||||
|
verifyNeverVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Tests
|
||||||
|
//
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testBeepInsistently() throws Exception {
|
||||||
|
NotificationRecord r = getInsistentBeepyNotification();
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
verifyBeep();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testNoInterruptionForMin() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
r.setImportance(Ranking.IMPORTANCE_MIN, "foo");
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
verifyNeverBeep();
|
||||||
|
verifyNeverVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testNoInterruptionForIntercepted() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
r.setIntercepted(true);
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
verifyNeverBeep();
|
||||||
|
verifyNeverVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testBeepTwice() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
Mockito.reset(mRingtonePlayer);
|
||||||
|
|
||||||
|
// update should beep
|
||||||
|
r.isUpdate = true;
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
verifyBeepLooped();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testHonorAlertOnlyOnceForBeep() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
NotificationRecord s = getBeepyOnceNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
Mockito.reset(mRingtonePlayer);
|
||||||
|
|
||||||
|
// update should not beep
|
||||||
|
mService.buzzBeepBlinkLocked(s);
|
||||||
|
verifyNeverBeep();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testNoisyUpdateDoesNotCancelAudio() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
r.isUpdate = true;
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
verifyNeverStopAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testNoisyOnceUpdateDoesNotCancelAudio() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
NotificationRecord s = getBeepyOnceNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
mService.buzzBeepBlinkLocked(s);
|
||||||
|
|
||||||
|
verifyNeverStopAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testQuietUpdateDoesNotCancelAudioFromOther() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
NotificationRecord s = getQuietNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
NotificationRecord other = getNoisyOtherNotification();
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
mService.buzzBeepBlinkLocked(other); // this takes the audio stream
|
||||||
|
Mockito.reset(mRingtonePlayer);
|
||||||
|
|
||||||
|
// should not stop noise, since we no longer own it
|
||||||
|
mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
|
||||||
|
verifyNeverStopAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testQuietInterloperDoesNotCancelAudio() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
NotificationRecord other = getQuietOtherNotification();
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
Mockito.reset(mRingtonePlayer);
|
||||||
|
|
||||||
|
// should not stop noise, since it does not own it
|
||||||
|
mService.buzzBeepBlinkLocked(other);
|
||||||
|
verifyNeverStopAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testQuietUpdateCancelsAudio() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
NotificationRecord s = getQuietNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
Mockito.reset(mRingtonePlayer);
|
||||||
|
|
||||||
|
// quiet update should stop making noise
|
||||||
|
mService.buzzBeepBlinkLocked(s);
|
||||||
|
verifyStopAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testQuietOnceUpdateCancelsAudio() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
NotificationRecord s = getQuietOnceNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
Mockito.reset(mRingtonePlayer);
|
||||||
|
|
||||||
|
// stop making noise - this is a weird corner case, but quiet should override once
|
||||||
|
mService.buzzBeepBlinkLocked(s);
|
||||||
|
verifyStopAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testDemoteSoundToVibrate() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
|
||||||
|
// the phone is quiet
|
||||||
|
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
|
||||||
|
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
verifyNeverBeep();
|
||||||
|
verifyVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testDemotInsistenteSoundToVibrate() throws Exception {
|
||||||
|
NotificationRecord r = getInsistentBeepyNotification();
|
||||||
|
|
||||||
|
// the phone is quiet
|
||||||
|
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
|
||||||
|
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
verifyVibrateLooped();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testVibrate() throws Exception {
|
||||||
|
NotificationRecord r = getBuzzyNotification();
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
verifyNeverBeep();
|
||||||
|
verifyVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testInsistenteVibrate() throws Exception {
|
||||||
|
NotificationRecord r = getInsistentBuzzyNotification();
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
verifyVibrateLooped();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testVibratTwice() throws Exception {
|
||||||
|
NotificationRecord r = getBuzzyNotification();
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
Mockito.reset(mVibrator);
|
||||||
|
|
||||||
|
// update should vibrate
|
||||||
|
r.isUpdate = true;
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
verifyVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testHonorAlertOnlyOnceForBuzz() throws Exception {
|
||||||
|
NotificationRecord r = getBuzzyNotification();
|
||||||
|
NotificationRecord s = getBuzzyOnceNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
Mockito.reset(mVibrator);
|
||||||
|
|
||||||
|
// update should not beep
|
||||||
|
mService.buzzBeepBlinkLocked(s);
|
||||||
|
verifyNeverVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testNoisyUpdateDoesNotCancelVibrate() throws Exception {
|
||||||
|
NotificationRecord r = getBuzzyNotification();
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
r.isUpdate = true;
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
verifyNeverStopVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testNoisyOnceUpdateDoesNotCancelVibrate() throws Exception {
|
||||||
|
NotificationRecord r = getBuzzyNotification();
|
||||||
|
NotificationRecord s = getBuzzyOnceNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
mService.buzzBeepBlinkLocked(s);
|
||||||
|
|
||||||
|
verifyNeverStopVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testQuietUpdateDoesNotCancelVibrateFromOther() throws Exception {
|
||||||
|
NotificationRecord r = getBuzzyNotification();
|
||||||
|
NotificationRecord s = getQuietNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
NotificationRecord other = getNoisyOtherNotification();
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
mService.buzzBeepBlinkLocked(other); // this takes the vibrate stream
|
||||||
|
Mockito.reset(mVibrator);
|
||||||
|
|
||||||
|
// should not stop vibrate, since we no longer own it
|
||||||
|
mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
|
||||||
|
verifyNeverStopVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testQuietInterloperDoesNotCancelVibrate() throws Exception {
|
||||||
|
NotificationRecord r = getBuzzyNotification();
|
||||||
|
NotificationRecord other = getQuietOtherNotification();
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
Mockito.reset(mVibrator);
|
||||||
|
|
||||||
|
// should not stop noise, since it does not own it
|
||||||
|
mService.buzzBeepBlinkLocked(other);
|
||||||
|
verifyNeverStopVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testQuietUpdateCancelsVibrate() throws Exception {
|
||||||
|
NotificationRecord r = getBuzzyNotification();
|
||||||
|
NotificationRecord s = getQuietNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
// quiet update should stop making noise
|
||||||
|
mService.buzzBeepBlinkLocked(s);
|
||||||
|
verifyStopVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testQuietOnceUpdateCancelsvibrate() throws Exception {
|
||||||
|
NotificationRecord r = getBuzzyNotification();
|
||||||
|
NotificationRecord s = getQuietOnceNotification();
|
||||||
|
s.isUpdate = true;
|
||||||
|
|
||||||
|
// set up internal state
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
Mockito.reset(mVibrator);
|
||||||
|
|
||||||
|
// stop making noise - this is a weird corner case, but quiet should override once
|
||||||
|
mService.buzzBeepBlinkLocked(s);
|
||||||
|
verifyStopVibrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
public void testQuietUpdateCancelsDemotedVibrate() throws Exception {
|
||||||
|
NotificationRecord r = getBeepyNotification();
|
||||||
|
NotificationRecord s = getQuietNotification();
|
||||||
|
|
||||||
|
// the phone is quiet
|
||||||
|
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
|
||||||
|
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
|
||||||
|
|
||||||
|
mService.buzzBeepBlinkLocked(r);
|
||||||
|
|
||||||
|
// quiet update should stop making noise
|
||||||
|
mService.buzzBeepBlinkLocked(s);
|
||||||
|
verifyStopVibrate();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user