Notification的音效,振动,指示灯

前言:在创建一个通知的时候,我们除了指定通知的信息外,还可以指定通知的震动,响铃以及指示灯,今天就从源码的角度来分析下通知的震动,响铃以及指示灯是如何实现的;

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调用我们就不分析了;

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

renshuguo123723

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值