前言:在创建一个通知的时候,我们除了指定通知的信息外,还可以指定通知的震动,响铃以及指示灯,今天就从源码的角度来分析下通知的震动,响铃以及指示灯是如何实现的;
1.首先,要想在通知来临时开启指示灯,需要使用调用下面函数:
Settings.System.putInt(mContext.getContentResolver(),NOTIFICATION_LIGHT_PULSE, val ? 1 : 0);
NOTIFICATION_LIGHT_PULSE是一个字符串-->"notification_light_pulse",它位于frameworks/base/core/java/android/provider/Settings.java中;
这个开关一般是在设置中开启和关闭的,如果发现给通知设置了音效,震动等无效果,那就去设置里面看看这个通知开关是否打开了;
2.通知震动,响铃以及指示灯的处理是由NotificationManagerService来完成的;
NotificationManagerService在处理一条发过来的通知的时候,除了会在状态栏显示这条通知,还会处理他的附带信息,比如震动,响铃以及指示灯的处理等;
对于震动,响铃以及指示灯的处理在buzzBeepBlinkLocked(r)函数中,来看下这个函数:
void buzzBeepBlinkLocked(NotificationRecord record) {
boolean buzz = false;
boolean beep = false;
boolean blink = false;
//上层传递过来的Notification
final Notification notification = record.sbn.getNotification();
final String key = record.getKey();
//record.getImportance()表示用户的重要性,有些应用的通知是没有资格开启震动,响铃和指示灯的
final boolean aboveThreshold =
record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;
//如果这条通知已经占用了声音通知的通道,也就是说,上次的通知声音就是它造成的
boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
//如果这条通知已经占用了震动通知的通道
boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
boolean hasValidVibrate = false;
boolean hasValidSound = false;
boolean sentAccessibilityEvent = false;
if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
sendAccessibilityEvent(notification, record.sbn.getPackageName());
sentAccessibilityEvent = true;
}
if (aboveThreshold && isNotificationForCurrentUser(record)) {//用户有资格开启震动,响铃和指示灯
if (mSystemReady && mAudioManager != null) {
//通知声音的音源
Uri soundUri = record.getSound();
hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
long[] vibration = record.getVibration();
//如果此时手机是震动模式,当通知设置了声音的时候,将播放声音调成震动
if (vibration == null
&& hasValidSound
&& (mAudioManager.getRingerModeInternal()
== AudioManager.RINGER_MODE_VIBRATE)
&& mAudioManager.getStreamVolume(
AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
vibration = mFallbackVibrationPattern;
}
hasValidVibrate = vibration != null;
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {//还要判断一下当前是否需要静音
if (!sentAccessibilityEvent) {
sendAccessibilityEvent(notification, record.sbn.getPackageName());
sentAccessibilityEvent = true;
}
if (hasValidSound) {
//保存制造通知声音的Notification
mSoundNotificationKey = key;
if (mInCall) {
//如果此时是通话状态,就播放通话通知的音效
playInCallNotification();
beep = true;
} else {
//播放我们设置的通知音效
beep = playSound(record, soundUri);
}
}
//判断此时是静音模式
final boolean ringerModeSilent =
mAudioManager.getRingerModeInternal()
== AudioManager.RINGER_MODE_SILENT;
if (!mInCall && hasValidVibrate && !ringerModeSilent) {
mVibrateNotificationKey = key;
//如果既不在通话,又不是静音模式,那就震动
buzz = playVibration(record, vibration, hasValidSound);
}
}
}
}
//如果这条就是上次制造出声音和震动的通知,那么此时的通知中没有设置音源和震动,那就直接停止播放通知上条通知的音效和震动
if (wasBeep && !hasValidSound) {
clearSoundLocked();
}
if (wasBuzz && !hasValidVibrate) {
clearVibrateLocked();
}
//先将该通知从mLights中移除,如果wasShowLights为true,表示之前该通知已经显示过指示灯了
boolean wasShowLights = mLights.remove(key);
if (record.getLight() != null && aboveThreshold
&& ((record.getSuppressedVisualEffects()
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) {
//重新添加
mLights.add(key);
//更新指示灯的状态
updateLightsLocked();
//用户自定义了指示灯的颜色和闪烁频率
if (mUseAttentionLight) {
mAttentionLight.pulse();
}
blink = true;
} else if (wasShowLights) {
updateLightsLocked();
}
if (buzz || beep || blink) {
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ALERT)
.setType(MetricsEvent.TYPE_OPEN)
.setSubtype((buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)));
EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0);
}
}
3.流程分析完了,再来看下具体的功能:
(1)播放通知音效:
playSound(record, soundUri):
private boolean playSound(final NotificationRecord record, Uri soundUri) {
//looping表示是否循环播放音效
boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
//如果音频焦点没有被占用,或者此时不是震动模式,那就播放音效吧
if (!mAudioManager.isAudioFocusExclusive() && (mAudioManager.getRingerModeInternal()
!= AudioManager.RINGER_MODE_VIBRATE || mAudioManager.getStreamVolume(
AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) {
final long identity = Binder.clearCallingIdentity();
try {
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
if (player != null) {
//播放音效
player.playAsync(soundUri, record.sbn.getUser(), looping,
record.getAudioAttributes());
return true;
}
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(identity);
}
}
return false;
}
(2)通知震动
playVibration(record, vibration, hasValidSound):
private boolean playVibration(final NotificationRecord record, long[] vibration,
boolean delayVibForSound) {
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(() -> {
final int waitMs = mAudioManager.getFocusRampTimeMs(
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
record.getAudioAttributes());
if (DBG) Slog.v(TAG, "Delaying vibration by " + waitMs + "ms");
try {
Thread.sleep(waitMs);
} catch (InterruptedException e) {
}
mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
effect, record.getAudioAttributes());
}).start();
} else {
mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
effect, record.getAudioAttributes());
}
return true;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
(3)指示灯显示
void updateLightsLocked() {
NotificationRecord ledNotification = null;
while (ledNotification == null && !mLights.isEmpty()) {
final String owner = mLights.get(mLights.size() - 1);
ledNotification = mNotificationsByKey.get(owner);
if (ledNotification == null) {
Slog.wtfStack(TAG, "LED Notification does not exist: " + owner);
mLights.remove(owner);
}
}
//亮屏状态,通话状态,指示灯是关闭的
if (ledNotification == null || mInCall || mScreenOn) {
mNotificationLight.turnOff();
} else {
NotificationRecord.Light light = ledNotification.getLight();
if (light != null && mNotificationPulseEnabled) {
// pulse repeatedly
//指示灯闪烁
mNotificationLight.setFlashing(mBatteryLowARGB, Light.LIGHT_FLASH_HARDWARE,
light.onMs, light.offMs);
}
}
}
这样,就分析完了通知的震动,指示灯以及音效;
至于API调用我们就不分析了;