diff options
| -rw-r--r-- | services/core/java/com/android/server/notification/NotificationManagerService.java | 238 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java | 541 |
2 files changed, 683 insertions, 96 deletions
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 022b10fb27aa..124d7f1b9c97 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -120,6 +120,7 @@ import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -226,7 +227,7 @@ public class NotificationManagerService extends SystemService { private VrManagerInternal mVrManagerInternal; final IBinder mForegroundToken = new Binder(); - private WorkerHandler mHandler; + private Handler mHandler; private final HandlerThread mRankingThread = new HandlerThread("ranker", Process.THREAD_PRIORITY_BACKGROUND); @@ -572,33 +573,9 @@ public class NotificationManagerService extends SystemService { public void clearEffects() { synchronized (mNotificationList) { if (DBG) Slog.d(TAG, "clearEffects"); - - // sound - mSoundNotificationKey = null; - - 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(); + clearSoundLocked(); + clearVibrateLocked(); + clearLightsLocked(); } } @@ -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() { @Override public void onReceive(Context context, Intent intent) { @@ -863,6 +870,26 @@ public class NotificationManagerService extends SystemService { 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 public void onStart() { Resources resources = getContext().getResources(); @@ -2492,12 +2519,14 @@ public class NotificationManagerService extends SystemService { return false; } - private void buzzBeepBlinkLocked(NotificationRecord record) { + @VisibleForTesting + void buzzBeepBlinkLocked(NotificationRecord record) { boolean buzz = false; boolean beep = false; boolean blink = false; final Notification notification = record.sbn.getNotification(); + final String key = record.getKey(); // Should this notification make noise, vibe, or use the LED? final boolean aboveThreshold = record.getImportance() >= IMPORTANCE_DEFAULT; @@ -2521,9 +2550,15 @@ public class NotificationManagerService extends SystemService { if (disableEffects != null) { 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 - && (!(record.isUpdate - && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) && (record.getUserId() == UserHandle.USER_ALL || record.getUserId() == currentUser || mUserProfiles.isCurrentProfile(record.getUserId())) @@ -2532,10 +2567,6 @@ public class NotificationManagerService extends SystemService { && mAudioManager != null) { if (DBG) Slog.v(TAG, "Interrupting!"); - sendAccessibilityEvent(notification, record.sbn.getPackageName()); - - // sound - // should we use the default notification sound? (indicated either by // DEFAULT_SOUND or because notification.sound is pointing at // Settings.System.NOTIFICATION_SOUND) @@ -2545,8 +2576,6 @@ public class NotificationManagerService extends SystemService { .equals(notification.sound); Uri soundUri = null; - boolean hasValidSound = false; - if (useDefaultSound) { soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; @@ -2559,88 +2588,105 @@ public class NotificationManagerService extends SystemService { 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? final boolean hasCustomVibrate = notification.vibrate != null; // 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 final boolean convertSoundToVibration = - !hasCustomVibrate - && hasValidSound - && (mAudioManager.getRingerModeInternal() - == AudioManager.RINGER_MODE_VIBRATE); + !hasCustomVibrate + && hasValidSound + && (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE); // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; - if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) - && !(mAudioManager.getRingerModeInternal() - == AudioManager.RINGER_MODE_SILENT)) { - mVibrateNotificationKey = record.getKey(); + hasValidVibrate = useDefaultVibrate || convertSoundToVibration || + hasCustomVibrate; + + // We can alert, and we're allowed to alert, but if the developer asked us to only do + // it once, and we already have, then don't. + if (!(record.isUpdate + && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)) { + + sendAccessibilityEvent(notification, record.sbn.getPackageName()); + + if (hasValidSound) { + boolean looping = + (notification.flags & Notification.FLAG_INSISTENT) != 0; + AudioAttributes audioAttributes = audioAttributesForNotification(notification); + mSoundNotificationKey = key; + // 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 (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 { + 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(), - useDefaultVibrate ? mDefaultVibrationPattern - : mFallbackVibrationPattern, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) - ? 0: -1, audioAttributesForNotification(notification)); + notification.vibrate, + ((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; } } + + } + // 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 // release the light - boolean wasShowLights = mLights.remove(record.getKey()); + boolean wasShowLights = mLights.remove(key); if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold && ((record.getSuppressedVisualEffects() & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) { - mLights.add(record.getKey()); + mLights.add(key); updateLightsLocked(); if (mUseAttentionLight) { mAttentionLight.pulse(); @@ -2654,7 +2700,7 @@ public class NotificationManagerService extends SystemService { & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) { if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on"); } else { - EventLogTags.writeNotificationAlert(record.getKey(), + EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0); mHandler.post(mBuzzBeepBlinked); } diff --git a/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java new file mode 100644 index 000000000000..83a59fd85d29 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -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(); + } +} |