android5.1音量调节播放调试音

2017-08-19  本文已影响77人  97690CE50CC872D

设置中音量相关的Fragment调整到了NotificationSettings.java中。
我们可以在R.xml.notification_settings中看到如下
xref: /packages/apps/Settings/res/xml/notification_settings.xml

<!-- Media volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="media_volume"
            android:icon="@*android:drawable/ic_audio_vol"
            android:title="@string/media_volume_option_title" />

    <!-- Alarm volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="alarm_volume"
            android:icon="@*android:drawable/ic_audio_alarm"
            android:title="@string/alarm_volume_option_title" />

    <!-- Ring volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="ring_volume"
            android:icon="@*android:drawable/ic_audio_ring_notif"
            android:title="@string/ring_volume_option_title" />

    <!-- Notification volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
            android:key="notification_volume"
            android:icon="@*android:drawable/ic_audio_ring_notif"
            android:title="@string/notification_volume_option_title" />

这分别代表多媒体、闹钟、来电铃声和系统通知音量。所以在UI层面上用户是通过调节VolumeSeekBarPreference这个seekbar来最终对系统音量进行调整。
回头看NotificationSettings.java中初始化流程如下
xref: /packages/apps/Settings/src/com/android/settings/notification/NotificationSettings.java

//sound是包含上面4个SeekBar的PreferenceCategory
    final PreferenceCategory sound = (PreferenceCategory) findPreference(KEY_SOUND);
        initVolumePreference(KEY_MEDIA_VOLUME, AudioManager.STREAM_MUSIC);
        initVolumePreference(KEY_ALARM_VOLUME, AudioManager.STREAM_ALARM);
    //判断是否支持通话,支持移除NOTIFICATION_VOLUME否则移除RING_VOLUME
        if (mVoiceCapable) {
            mRingOrNotificationPreference =
                    initVolumePreference(KEY_RING_VOLUME, AudioManager.STREAM_RING);
            sound.removePreference(sound.findPreference(KEY_NOTIFICATION_VOLUME));
        } else {
            mRingOrNotificationPreference =
                    initVolumePreference(KEY_NOTIFICATION_VOLUME, AudioManager.STREAM_NOTIFICATION);
            sound.removePreference(sound.findPreference(KEY_RING_VOLUME));
        }
        initRingtones(sound);

    private final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback();

private VolumeSeekBarPreference initVolumePreference(String key, int stream) {
        final VolumeSeekBarPreference volumePref = (VolumeSeekBarPreference) findPreference(key);
    设置callback
        volumePref.setCallback(mVolumeCallback);
    //设置AudioManager的stream,可以在AudioSystem中看到具体int值代表的意义
        volumePref.setStream(stream);
        return volumePref;
}

mVolumeCallback如下,当你拖动seekbar对音量进行调节时这个callback会回馈响应,在界面上进行相应的调整。

private final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback {
    //SeekBarVolumizer是主要负责对系统音量进行调整的类,会在后面描述
        private SeekBarVolumizer mCurrent;  
        @Override
        public void onSampleStarting(SeekBarVolumizer sbv) {
            if (mCurrent != null && mCurrent != sbv) {
                mCurrent.stopSample();
            }
            mCurrent = sbv;
            if (mCurrent != null) {
                mHandler.removeMessages(H.STOP_SAMPLE);
                mHandler.sendEmptyMessageDelayed(H.STOP_SAMPLE, SAMPLE_CUTOFF);
            }
        }   
        @Override
        public void onStreamValueChanged(int stream, int progress) {
            if (stream == AudioManager.STREAM_RING) {
                mHandler.removeMessages(H.UPDATE_RINGER_ICON);
                mHandler.obtainMessage(H.UPDATE_RINGER_ICON, progress, 0).sendToTarget();
            }
    }
        public void stopSample() {
            if (mCurrent != null) {
                mCurrent.stopSample();
            }
        }
    };
    @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_PHONE_RINGTONE:
                    mPhoneRingtonePreference.setSummary((CharSequence) msg.obj);
                    break;
                case UPDATE_NOTIFICATION_RINGTONE:
                    mNotificationRingtonePreference.setSummary((CharSequence) msg.obj);
                    break;
                case STOP_SAMPLE:
                    mVolumeCallback.stopSample();
                    break;
                case UPDATE_RINGER_ICON:
                    updateRingOrNotificationIcon(msg.arg1);
                    break;
            }
    }

下面来看VolumeSeekBarPreference.java中可以看到如下代码
xref: /packages/apps/Settings/src/com/android/settings/notification/VolumeSeekBarPreference.java

private SeekBarVolumizer mVolumizer;
    final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
        if (mVolumizer == null) {
            mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc) {
                // we need to piggyback on SBV's SeekBar listener to update our icon
                @Override
                public void onProgressChanged(SeekBar seekBar, int progress,
                        boolean fromTouch) {
                    super.onProgressChanged(seekBar, progress, fromTouch);
            //这里的mCallback就是VolumePreferenceCallback对象
                    mCallback.onStreamValueChanged(mStream, progress);
                }
            };
        }
        mVolumizer.setSeekBar(mSeekBar);
    mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
        mCallback.onStreamValueChanged(mStream, mSeekBar.getProgress());
    @Override
        public void onProgressChanged(SeekBar seekBar, int progress,
            boolean fromTouch) {
            super.onProgressChanged(seekBar, progress, fromTouch);
            mCallback.onStreamValueChanged(mStream, progress);
}

这里我们进行下梳理在NotificationSettings.java内initVolumePreference函数的volumePref.setCallback(mVolumeCallback);把VolumePreferenceCallback这个callbak设置到VolumeSeekBarPreference.java内的mCallback中
使得SeekBarVolumizer内的onProgressChanged进行回调时NotificationSettings.java的相应回调函数都响应。

现在看下/frameworks/base/core/java/android/preference/的SeekBarVolumizer.java

private final Context mContext;
private final Handler mHandler;
private final H mUiHandler = new H();
private final Callback mCallback;
private final Uri mDefaultUri;
private final AudioManager mAudioManager;
private final int mStreamType;
private final int mMaxStreamVolume;
private final Receiver mReceiver = new Receiver();
private final Observer mVolumeObserver;

private int mOriginalStreamVolume;
private Ringtone mRingtone;
private int mLastProgress = -1;
private SeekBar mSeekBar;
private int mVolumeBeforeMute = -1;

private static final int MSG_SET_STREAM_VOLUME = 0;
private static final int MSG_START_SAMPLE = 1;
private static final int MSG_STOP_SAMPLE = 2;
private static final int MSG_INIT_SAMPLE = 3;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;

public SeekBarVolumizer(Context context, int streamType, Uri defaultUri,
        Callback callback) {
    mContext = context;
    mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    mStreamType = streamType;
    mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
    HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
    thread.start();
    mHandler = new Handler(thread.getLooper(), this);
    mCallback = callback;
    mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
    mVolumeObserver = new Observer(mHandler);
    mContext.getContentResolver().registerContentObserver(
            System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
            false, mVolumeObserver);
    mReceiver.setListening(true);

//mDefaultUri为调整seekbar时听到的调试音
    if (defaultUri == null) {
        if (mStreamType == AudioManager.STREAM_RING) {
            defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
        } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
            defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
        } else {
            defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
        }
    }
    mDefaultUri = defaultUri;
    mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
}

public void setSeekBar(SeekBar seekBar) {
    if (mSeekBar != null) {
        mSeekBar.setOnSeekBarChangeListener(null);
    }
    mSeekBar = seekBar;
    mSeekBar.setOnSeekBarChangeListener(null);
    mSeekBar.setMax(mMaxStreamVolume);
    mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
    mSeekBar.setOnSeekBarChangeListener(this);
}

@Override
public boolean handleMessage(Message msg) {
    switch (msg.what) {
    //真正调节系统音量的地方
        case MSG_SET_STREAM_VOLUME:
            mAudioManager.setStreamVolume(mStreamType, mLastProgress,
                    AudioManager.FLAG_SHOW_UI_WARNINGS);
            break;
        case MSG_START_SAMPLE:
            onStartSample();
            break;
        case MSG_STOP_SAMPLE:
            onStopSample();
            break;
        case MSG_INIT_SAMPLE:
            onInitSample();
            break;
        default:
            Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
    }
    return true;
}

//初始化mRingtone,我们知道系统播放的铃声最后是通过mRingtone.play()来实现的
private void onInitSample() {
    mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
    if (mRingtone != null) {
        mRingtone.setStreamType(mStreamType);
    }
}

private void postStartSample() {
    mHandler.removeMessages(MSG_START_SAMPLE);
    mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
            isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
}

//开始播放调试音
private void onStartSample() {
    if (!isSamplePlaying()) {
        if (mCallback != null) {
            mCallback.onSampleStarting(this);
        }
        if (mRingtone != null) {
            try {
                mRingtone.play();
            } catch (Throwable e) {
                Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
            }
        }
    }
}

void postStopSample() {
    // remove pending delayed start messages
    mHandler.removeMessages(MSG_START_SAMPLE);
    mHandler.removeMessages(MSG_STOP_SAMPLE);
    mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
}

//停止播放调试音
private void onStopSample() {
    if (mRingtone != null) {
        mRingtone.stop();
    }
}

public void stop() {
    postStopSample();
    mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
    mSeekBar.setOnSeekBarChangeListener(null);
    mReceiver.setListening(false);
    mHandler.getLooper().quitSafely();
}

public void revertVolume() {
    mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
}

public void onProgressChanged(SeekBar seekBar, int progress,
        boolean fromTouch) {
    if (!fromTouch) {
        return;
    }

    postSetVolume(progress);
}

void postSetVolume(int progress) {
    // Do the volume changing separately to give responsive UI
    mLastProgress = progress;
    mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
    mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
}

public void onStartTrackingTouch(SeekBar seekBar) {
}

public void onStopTrackingTouch(SeekBar seekBar) {
    postStartSample();
}

//判断是否正在播放铃声
public boolean isSamplePlaying() {
    return mRingtone != null && mRingtone.isPlaying();
}

public void startSample() {
    postStartSample();
}

public void stopSample() {
    postStopSample();
}

public SeekBar getSeekBar() {
    return mSeekBar;
}

public void changeVolumeBy(int amount) {
    mSeekBar.incrementProgressBy(amount);
    postSetVolume(mSeekBar.getProgress());
    postStartSample();
    mVolumeBeforeMute = -1;
}

public void muteVolume() {
    if (mVolumeBeforeMute != -1) {
        mSeekBar.setProgress(mVolumeBeforeMute);
        postSetVolume(mVolumeBeforeMute);
        postStartSample();
        mVolumeBeforeMute = -1;
    } else {
        mVolumeBeforeMute = mSeekBar.getProgress();
        mSeekBar.setProgress(0);
        postStopSample();
        postSetVolume(0);
    }
}

public void onSaveInstanceState(VolumeStore volumeStore) {
    if (mLastProgress >= 0) {
        volumeStore.volume = mLastProgress;
        volumeStore.originalVolume = mOriginalStreamVolume;
    }
}

public void onRestoreInstanceState(VolumeStore volumeStore) {
    if (volumeStore.volume != -1) {
        mOriginalStreamVolume = volumeStore.originalVolume;
        mLastProgress = volumeStore.volume;
        postSetVolume(mLastProgress);
    }
}

private final class H extends Handler {
    private static final int UPDATE_SLIDER = 1;

    @Override
    public void handleMessage(Message msg) {
        if (msg.what == UPDATE_SLIDER) {
            if (mSeekBar != null) {
                mSeekBar.setProgress(msg.arg1);
                mLastProgress = mSeekBar.getProgress();
            }
        }
    }

    public void postUpdateSlider(int volume) {
        obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
    }
}

private final class Observer extends ContentObserver {
    public Observer(Handler handler) {
        super(handler);
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        if (mSeekBar != null && mAudioManager != null) {
            final int volume = mAudioManager.getStreamVolume(mStreamType);
            mUiHandler.postUpdateSlider(volume);
        }
    }
}

这里忽视其他所有操作关注一下主要流程,我们知道这个类implements了OnSeekBarChangeListener,这个listener包含了当值发生变化响应的onProgressChanged和开始完成操作的onStartTrackingTouch和onStopTrackingTouch 所以当开始拖动seekbar时onStartTrackingTouch响应无操作,然后onProgressChanged发生变化postSetVolume(progress);从而调用最关键函数mAudioManager.setStreamVolume(mStreamType, mLastProgress, AudioManager.FLAG_SHOW_UI_WARNINGS);实现音量调整,然后调用onStopTrackingTouch,通过handler调用onStartSample函数来播放调试音,在这过程中通过和setting模块绑定的 callback来对setting模块进行响应,这就时和上层setting模块互动的一个主要流程。

感谢闲庭信步_wwdandroid5.0设置模块音量调节流程启发

上一篇下一篇

猜你喜欢

热点阅读