SystemUI 实现音量条同步功能

需求:SystemUI 实现音量条同步功能

具体问题

以前在SystemUI 下拉框添加了音量条控制,目前发现在SystemUI下拉框显示状态的情况下,

按键或者底部虚拟导航点击音量加减时候,SystemUI音量条不更新。

如下图:两个SystemUI 下拉音量条和系统自带音量条不同步

参考资料:

以前做过Android12 SystemUI 新增音量条功能,如下,现在要解决的就是Android物理按键或者虚拟导航音量加减按键后,音量和Android自带音量条不同步问题。
Android13_SystemUI下拉框新增音量控制条
Android12_SystemUI下拉框新增音量控制条

熟悉了解,下拉框新增音量条控制逻辑和业务实现,熟悉相关的音量模块基本的UI组件。

思路:

参考系统设置实现方案:

设置->提示音->媒体音量 在音量按键点击时候,音量条同步更新的业务流程

参考SystemUI 音量控制逻辑:

参考应用端实现

应用端监听到音量变化 VOLUME_CHANGED_ACTION 广播,然后进行亮度调进度调节

具体代码分析

设置模块,全局搜索 VOLUME_CHANGED_ACTION

java 复制代码
VolumeSliceHelper ->onReceive-



这里可以看出,在监听到音量变化后,如果哪里订阅了相关的uri,那么就通知 notifyChange 一次,接收地方不就是 onChange 方法嘛 。

分析 设置-提示音

进入对应页面,日志显示如下:

java 复制代码
Switching to fragment com.android.settings.notification.SoundSettings
Launching fragment com.android.settings.notification.SoundSettings
SoundSettings
java 复制代码
加载布局文件
@Override
    protected int getPreferenceScreenResId() {
        return R.xml.sound_settings;
    }

	
	具体媒体音量相关
    <!-- Media volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
        android:key="media_volume"
        android:icon="@drawable/ic_media_stream"
        android:title="@string/media_volume_option_title"
        android:order="-180"
        settings:controller="com.android.settings.notification.MediaVolumePreferenceController"/>
MediaVolumePreferenceController

Volume 关联的控制器

java 复制代码
   
public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceController {

    private static final String KEY_MEDIA_VOLUME = "media_volume";

    public MediaVolumePreferenceController(Context context) {
        super(context, KEY_MEDIA_VOLUME);
    }

    @Override
    public int getAvailabilityStatus() {
        return mContext.getResources().getBoolean(R.bool.config_show_media_volume)
                ? AVAILABLE
                : UNSUPPORTED_ON_DEVICE;
    }

    @Override
    public boolean isSliceable() {
        return TextUtils.equals(getPreferenceKey(), KEY_MEDIA_VOLUME);
    }

    @Override
    public boolean isPublicSlice() {
        return true;
    }

    @Override
    public boolean useDynamicSliceSummary() {
        return true;
    }

    @Override
    public String getPreferenceKey() {
        return KEY_MEDIA_VOLUME;
    }

    @Override
    public int getAudioStream() {
        return AudioManager.STREAM_MUSIC;
    }

    @Override
    public int getMuteIcon() {
        return R.drawable.ic_media_stream_off;
    }
}
VolumeSeekBarPreferenceController

它的官方解释

java 复制代码
:/** A slider preference that directly controls an audio stream volume (no dialog) **/
 做了以下几件事情:
   1)加载自己媒体音量的子布局  preference_volume_slider 
   2)将自己传递给 framework 层,并接收音量变化回调 SeekBarVolumizer.Callback 动态更新UI
java 复制代码
/**
 * Base class for preference controller that handles VolumeSeekBarPreference
 */
public abstract class VolumeSeekBarPreferenceController extends
        AdjustVolumeRestrictedPreferenceController implements LifecycleObserver {

    protected VolumeSeekBarPreference mPreference;
    protected VolumeSeekBarPreference.Callback mVolumePreferenceCallback;
    protected AudioHelper mHelper;

    public VolumeSeekBarPreferenceController(Context context, String key) {
        super(context, key);
        setAudioHelper(new AudioHelper(context));
    }

    @VisibleForTesting
    void setAudioHelper(AudioHelper helper) {
        mHelper = helper;
    }

    public void setCallback(Callback callback) {
        mVolumePreferenceCallback = callback;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        if (isAvailable()) {
            mPreference = screen.findPreference(getPreferenceKey());
            mPreference.setCallback(mVolumePreferenceCallback);
            mPreference.setStream(getAudioStream());
            mPreference.setMuteIcon(getMuteIcon());
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void onResume() {
        if (mPreference != null) {
            mPreference.onActivityResume();
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void onPause() {
        if (mPreference != null) {
            mPreference.onActivityPause();
        }
    }

    @Override
    public int getSliderPosition() {
        if (mPreference != null) {
            return mPreference.getProgress();
        }
        return mHelper.getStreamVolume(getAudioStream());
    }

    @Override
    public boolean setSliderPosition(int position) {
        if (mPreference != null) {
            mPreference.setProgress(position);
        }
        return mHelper.setStreamVolume(getAudioStream(), position);
    }

    @Override
    public int getMax() {
        if (mPreference != null) {
            return mPreference.getMax();
        }
        return mHelper.getMaxVolume(getAudioStream());
    }

    @Override
    public int getMin() {
        if (mPreference != null) {
            return mPreference.getMin();
        }
        return mHelper.getMinVolume(getAudioStream());
    }

    /**
     * @return the audio stream type
     */
    public abstract int getAudioStream();

    protected abstract int getMuteIcon();

}
AdjustVolumeRestrictedPreferenceController

处理音量控制器的基本父类,执行调节音量 官方解释:

java 复制代码
/*** Base class for preference controller that handles preference that enforce adjust     volume  restriction  **/      

VOLUME_CHANGED_ACTION 就是在这里声明监听的

java 复制代码
 @Override
    public IntentFilter getIntentFilter() {
        final IntentFilter filter = new IntentFilter();
        filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
        filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
        filter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);
        filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
        return filter;
    }
java 复制代码
/**
 * Base class for preference controller that handles preference that enforce adjust volume
 * restriction
 */
public abstract class AdjustVolumeRestrictedPreferenceController extends
        SliderPreferenceController {

    private AccountRestrictionHelper mHelper;

    public AdjustVolumeRestrictedPreferenceController(Context context, String key) {
        this(context, new AccountRestrictionHelper(context), key);
    }

    @VisibleForTesting
    AdjustVolumeRestrictedPreferenceController(Context context, AccountRestrictionHelper helper,
            String key) {
        super(context, key);
        mHelper = helper;
    }

    @Override
    public void updateState(Preference preference) {
        if (!(preference instanceof RestrictedPreference)) {
            return;
        }
        mHelper.enforceRestrictionOnPreference((RestrictedPreference) preference,
                UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId());
    }

    @Override
    public IntentFilter getIntentFilter() {
        final IntentFilter filter = new IntentFilter();
        filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
        filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
        filter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);
        filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
        return filter;
    }
}
VolumeSeekBarPreference
java 复制代码
/** A slider preference that directly controls an audio stream volume (no dialog) **/
这个是布局 媒体音量的自定义UI
java 复制代码
/** A slider preference that directly controls an audio stream volume (no dialog) **/
public class VolumeSeekBarPreference extends SeekBarPreference {
    private static final String TAG = "VolumeSeekBarPreference";

    protected SeekBar mSeekBar;
    private int mStream;
    private SeekBarVolumizer mVolumizer;
    private Callback mCallback;
    private ImageView mIconView;
    private TextView mSuppressionTextView;
    private String mSuppressionText;
    private boolean mMuted;
    private boolean mZenMuted;
    private int mIconResId;
    private int mMuteIconResId;
    private boolean mStopped;
    @VisibleForTesting
    AudioManager mAudioManager;

    public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setLayoutResource(R.layout.preference_volume_slider);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayoutResource(R.layout.preference_volume_slider);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    public VolumeSeekBarPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
        setLayoutResource(R.layout.preference_volume_slider);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    public VolumeSeekBarPreference(Context context) {
        super(context);
        setLayoutResource(R.layout.preference_volume_slider);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    public void setStream(int stream) {
        mStream = stream;
        setMax(mAudioManager.getStreamMaxVolume(mStream));
        // Use getStreamMinVolumeInt for non-public stream type
        // eg: AudioManager.STREAM_BLUETOOTH_SCO
        setMin(mAudioManager.getStreamMinVolumeInt(mStream));
        setProgress(mAudioManager.getStreamVolume(mStream));
    }

    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    public void onActivityResume() {
        if (mStopped) {
            init();
        }
    }

    public void onActivityPause() {
        mStopped = true;
        if (mVolumizer != null) {
            mVolumizer.stop();
            mVolumizer = null;
        }
    }

    @Override
    public void onBindViewHolder(PreferenceViewHolder view) {
        super.onBindViewHolder(view);
        mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
        mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
        mSuppressionTextView = (TextView) view.findViewById(R.id.suppression_text);
        init();
    }

    protected void init() {
        if (mSeekBar == null) return;
        final SeekBarVolumizer.Callback sbvc = new SeekBarVolumizer.Callback() {
            @Override
            public void onSampleStarting(SeekBarVolumizer sbv) {
                if (mCallback != null) {
                    mCallback.onSampleStarting(sbv);
                }
            }
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
                if (mCallback != null) {
                    mCallback.onStreamValueChanged(mStream, progress);
                }
            }
            @Override
            public void onMuted(boolean muted, boolean zenMuted) {
                if (mMuted == muted && mZenMuted == zenMuted) return;
                mMuted = muted;
                mZenMuted = zenMuted;
                updateIconView();
            }
            @Override
            public void onStartTrackingTouch(SeekBarVolumizer sbv) {
                if (mCallback != null) {
                    mCallback.onStartTrackingTouch(sbv);
                }
            }
        };
        final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
        if (mVolumizer == null) {
            mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc);
        }
        mVolumizer.start();
        mVolumizer.setSeekBar(mSeekBar);
        updateIconView();
        updateSuppressionText();
        if (!isEnabled()) {
            mSeekBar.setEnabled(false);
            mVolumizer.stop();
        }
    }

    protected void updateIconView() {
        if (mIconView == null) return;
        if (mIconResId != 0) {
            mIconView.setImageResource(mIconResId);
        } else if (mMuteIconResId != 0 && mMuted && !mZenMuted) {
            mIconView.setImageResource(mMuteIconResId);
        } else {
            mIconView.setImageDrawable(getIcon());
        }
    }

    public void showIcon(int resId) {
        // Instead of using setIcon, which will trigger listeners, this just decorates the
        // preference temporarily with a new icon.
        if (mIconResId == resId) return;
        mIconResId = resId;
        updateIconView();
    }

    public void setMuteIcon(int resId) {
        if (mMuteIconResId == resId) return;
        mMuteIconResId = resId;
        updateIconView();
    }

    private Uri getMediaVolumeUri() {
        return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
                + getContext().getPackageName()
                + "/" + R.raw.media_volume);
    }

    public void setSuppressionText(String text) {
        if (Objects.equals(text, mSuppressionText)) return;
        mSuppressionText = text;
        updateSuppressionText();
    }

    protected void updateSuppressionText() {
        if (mSuppressionTextView != null && mSeekBar != null) {
            mSuppressionTextView.setText(mSuppressionText);
            final boolean showSuppression = !TextUtils.isEmpty(mSuppressionText);
            mSuppressionTextView.setVisibility(showSuppression ? View.VISIBLE : View.GONE);
        }
    }

    public interface Callback {
        void onSampleStarting(SeekBarVolumizer sbv);
        void onStreamValueChanged(int stream, int progress);

        /**
         * Callback reporting that the seek bar is start tracking.
         */
        void onStartTrackingTouch(SeekBarVolumizer sbv);
    }
}

核心代码,监听音量变化,将seekbar 传递到framework 层

java 复制代码
 final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
        if (mVolumizer == null) {
            mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc);
        }
        mVolumizer.start();
        mVolumizer.setSeekBar(mSeekBar);
        updateIconView();
        updateSuppressionText();
        if (!isEnabled()) {
            mSeekBar.setEnabled(false);
            mVolumizer.stop();
        }
SeekBarVolumizer
java 复制代码
\frameworks\base\core\java\android\preference\SeekBarVolumizer.java 

部分核心代码

java 复制代码
 private final class Observer extends ContentObserver {
        public Observer(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            updateSlider();
        }
    }
  
   
      private void updateSlider() {
        if (mSeekBar != null && mAudioManager != null) {
            final int volume = mAudioManager.getStreamVolume(mStreamType);
            final int lastAudibleVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
            final boolean mute = mAudioManager.isStreamMute(mStreamType);
            mUiHandler.postUpdateSlider(volume, lastAudibleVolume, mute);
        }
    }
	
   
   
    public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) {
            obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, new Boolean(mute)).sendToTarget();
        }
   
   
   
   
    @Override
        public void handleMessage(Message msg) {
            if (msg.what == UPDATE_SLIDER) {
                if (mSeekBar != null) {
                    mLastProgress = msg.arg1;
                    mLastAudibleStreamVolume = msg.arg2;
                    final boolean muted = ((Boolean)msg.obj).booleanValue();
                    if (muted != mMuted) {
                        mMuted = muted;
                        if (mCallback != null) {
                            mCallback.onMuted(mMuted, isZenMuted());
                        }
                    }
                    updateSeekBar();
                }
            }
        }
		
		
   
   
    protected void updateSeekBar() {
        final boolean zenMuted = isZenMuted();
        mSeekBar.setEnabled(!zenMuted);
        if (zenMuted) {
            mSeekBar.setProgress(mLastAudibleStreamVolume, true);
        } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
            mSeekBar.setProgress(0, true);
        } else if (mMuted) {
            mSeekBar.setProgress(0, true);
        } else {
            mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume, true);
        }
    }

设置音量控制同步进度条 核心代码 总结

作用
SoundSettings 设置->提示音 面板
sound_settings 设置提示音面板布局
VolumeSeekBarPreference 加载自己媒体音量的子布局 preference_volume_slider ;将自己传递给 framework 层,并接收音量变化回调 SeekBarVolumizer.Callback 动态更新UI
preference_volume_slider VolumeSeekBarPreference UI 类得布局,真正得媒体音量子布局
SeekBarPreference 媒体音量UI自定义UI类得父类 就是支持基本的功能,seekBar 相关的基本功能。 setProgress 方法,原来是在父类中设置并更新UI的
MediaVolumePreferenceController sound_settings布局中对应音量控制器
VolumeSeekBarPreferenceController 处理音量控制器的基本父类
AdjustVolumeRestrictedPreferenceController 处理音量控制器的基本父类,执行调节音量
SeekBarVolumizer 把seek 传递到framework层,framework层实现seekbar 的控制,并回调到App层

分析 SystemUI 层

全局搜索  VOLUME_CHANGED_ACTION 思路分析
VolumeDialogControllerImpl

核心内容 注册了一个音量相关广播 并接收处理

Receiver

onVolumeChangedW

java 复制代码
 private final class Receiver extends BroadcastReceiver {

        public void init() {
            final IntentFilter filter = new IntentFilter();
            filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
           。。。。。。。。。。。。。。。。。。。。。
            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
        }

        public void destroy() {
            mBroadcastDispatcher.unregisterReceiver(this);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            boolean changed = false;
            if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
                final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
                final int oldLevel = intent
                        .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
                if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
                        + " level=" + level + " oldLevel=" + oldLevel);
                changed = updateStreamLevelW(stream, level);
            }
            。。。。。。。。。。。。。。。。。。。。。。。。。
            if (changed) {
                mCallbacks.onStateChanged(mState);
            }
        }
    }


 boolean onVolumeChangedW(int stream, int flags) {
        final boolean showUI = shouldShowUI(flags);
        final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
        final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
        final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
        boolean changed = false;
        if (showUI) {
            changed |= updateActiveStreamW(stream);
        }
        int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream);
        changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);
        changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
        if (changed) {
            mCallbacks.onStateChanged(mState);
        }
        if (showUI) {
            onShowRequestedW(Events.SHOW_REASON_VOLUME_CHANGED);
        }
        if (showVibrateHint) {
            mCallbacks.onShowVibrateHint();
        }
        if (showSilentHint) {
            mCallbacks.onShowSilentHint();
        }
        if (changed && fromKey) {
            Events.writeEvent(Events.EVENT_KEY, stream, lastAudibleStreamVolume);
        }
        return changed;
    }
VolumeDialogImpl

通过回调方法 onStateChanged 找到这个类:

java 复制代码
/**
 * Visual presentation of the volume dialog.
 *
 * A client of VolumeDialogControllerImpl and its state model.
 *
 * Methods ending in "H" must be called on the (ui) handler.
 */

几个核心方法如下

onStateChanged
java 复制代码
  @Override
        public void onStateChanged(State state) {
            onStateChangedH(state);
        }
onStateChangedH
java 复制代码
 protected void onStateChangedH(State state) {
        if (D.BUG) Log.d(TAG, "onStateChangedH() state: " + state.toString());
        if (mState != null && state != null
                && mState.ringerModeInternal != -1
                && mState.ringerModeInternal != state.ringerModeInternal
                && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
            mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));
        }

        mState = state;
        mDynamic.clear();
        // add any new dynamic rows
        for (int i = 0; i < state.states.size(); i++) {
            final int stream = state.states.keyAt(i);
            final StreamState ss = state.states.valueAt(i);
            if (!ss.dynamic) continue;
            mDynamic.put(stream, true);
            if (findRow(stream) == null) {
                addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,
                        false, true);
            }
        }

        if (mActiveStream != state.activeStream) {
            mPrevActiveStream = mActiveStream;
            mActiveStream = state.activeStream;
            VolumeRow activeRow = getActiveRow();
            updateRowsH(activeRow);
            if (mShowing) rescheduleTimeoutH();
        }
        for (VolumeRow row : mRows) {
            updateVolumeRowH(row);
        }
        updateRingerH();
        mWindow.setTitle(composeWindowTitle());
    }

这里看到的是处理UI的操作,SystemUI 从VOLUME广播监听 到 接收 到 传递到 VolumeDialogImpl 控制UI就已经形成了闭环了。

分析SystemUI 音量流程- 模拟流程分析

回到 VolumeDialogControllerImpl 类的广播监听回调方法 onReceive

回调的最终方法:
java 复制代码
     mCallbacks.onStateChanged(mState);
mCallbacks 声明如下:
java 复制代码
protected C mCallbacks = new C();
添加监听
java 复制代码
 public void addCallback(Callbacks callback, Handler handler) {
        mCallbacks.add(callback, handler);
        callback.onAccessibilityModeChanged(mShowA11yStream);
    }
VolumeDialogImpl

设置监听 addCallback

java 复制代码
         public void init(int windowType, Callback callback) {
        initDialog(mActivityManager.getLockTaskModeState());
        mAccessibility.init();
        mController.addCallback(mControllerCallbackH, mHandler);
        mController.getState();
        mConfigurationController.addCallback(this);
    }
mControllerCallbackH
java 复制代码
 private final VolumeDialogController.Callbacks mControllerCallbackH
            = new VolumeDialogController.Callbacks() {
        @Override
        public void onShowRequested(int reason, boolean keyguardLocked, int lockTaskModeState) {
            showH(reason, keyguardLocked, lockTaskModeState);
        }

        @Override
        public void onDismissRequested(int reason) {
            dismissH(reason);
        }

        @Override
        public void onScreenOff() {
            dismissH(Events.DISMISS_REASON_SCREEN_OFF);
        }

        @Override
        public void onStateChanged(State state) {
            onStateChangedH(state);
        }

        @Override
        public void onLayoutDirectionChanged(int layoutDirection) {
            mDialogView.setLayoutDirection(layoutDirection);
        }

        @Override
        public void onConfigurationChanged() {
            mDialog.dismiss();
            mConfigChanged = true;
        }

        @Override
        public void onShowVibrateHint() {
            if (mSilentMode) {
                mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
            }
        }

        @Override
        public void onShowSilentHint() {
            if (mSilentMode) {
                mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
            }
        }

        @Override
        public void onShowSafetyWarning(int flags) {
            showSafetyWarningH(flags);
        }

        @Override
        public void onAccessibilityModeChanged(Boolean showA11yStream) {
            mShowA11yStream = showA11yStream == null ? false : showA11yStream;
            VolumeRow activeRow = getActiveRow();
            if (!mShowA11yStream && STREAM_ACCESSIBILITY == activeRow.stream) {
                dismissH(Events.DISMISS_STREAM_GONE);
            } else {
                updateRowsH(activeRow);
            }
        }

        @Override
        public void onCaptionComponentStateChanged(
                Boolean isComponentEnabled, Boolean fromTooltip) {
            updateODICaptionsH(isComponentEnabled, fromTooltip);
        }
    };

分析到 VolumeDialogController.Callbacks mControllerCallbackH 就不用继续分析了。 这个回调 是写在类里面的。

分析 SystemUI 层 分析总结
  • VOLUME_CHANGED_ACTION 监听回调
  • VolumeDialogControllerImpl 注册回调addCallback
  • VolumeDialogImpl 初始化 init 中 注册回调 addCallback

实现方案如下:

实现方案如下:

通过上面的 SystemUI 的音量回调流程分析和设置中音量回调流程,都是从音量监听的地方开始。但针对本需求并不合适在对应地方设置回调来实现 音量的监听。

实现思路

我们也直接监听VOLUME_CHANGED_ACTION 来实现需求

修改文件:

java 复制代码
/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/settings/volume/VolumeController.java

参考SystemUI层的 VOLUME_CHANGED_ACTION 监听

我们其实在VolumeDialogControllerImpl 类中已经分析了 VOLUME_CHANGED_ACTION 广播相关源码分析,接下来就是照葫芦画瓢实现即可。

基本代码如下:

java 复制代码
//wangfangchen add 
import com.android.systemui.volume.Events;
import com.android.systemui.volume.VolumeDialogControllerImpl;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
//wangfangchen end  

public class VolumeController implements ToggleSlider.Listener {
    private static final String TAG = "VolumeController";
    private static final int SLIDER_ANIMATION_DURATION = 3000;

    private static final int MSG_UPDATE_SLIDER = 1;
    private static final int MSG_ATTACH_LISTENER = 2;
    private static final int MSG_DETACH_LISTENER = 3; 
    private final Context mContext;
    private final ToggleSlider mControl;
    private final CurrentUserTracker mUserTracker;
    private final Handler mBackgroundHandler;
    private volatile boolean mAutomatic;  // Brightness adjusted automatically using ambient light.
    private volatile boolean mIsVrModeEnabled;
    private boolean mListening;
    private boolean mExternalChange;
    private boolean mControlValueInitialized;
    private float mBrightnessMin =0;// PowerManager.BRIGHTNESS_MIN;
    private float mBrightnessMax =100;// PowerManager.BRIGHTNESS_MAX;

    private ValueAnimator mSliderAnimator;
	//wangfangchen add 
    private final  Receiver mReceiver = new  Receiver();
    protected final BroadcastDispatcher mBroadcastDispatcher;
    private final  W mWorker;
    long timeFlag = System.currentTimeMillis();
    //wangfangchen end 

    public interface BrightnessStateChangeCallback {
        /** Indicates that some of the brightness settings have changed */
        void onBrightnessLevelChanged();
    }
 

    private final Runnable mStartListeningRunnable = new Runnable() {
        @Override
        public void run() {
            if (mListening) {
                return;
            }
            mListening = true;
            mUserTracker.startTracking();
            mUpdateSliderRunnable.run();
            mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
        }
    };

    private final Runnable mStopListeningRunnable = new Runnable() {
        @Override
        public void run() {
            if (!mListening) {
                return;
            }
            mListening = false;
            mUserTracker.stopTracking();
            mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
        }
    };

 
    /**
     * Fetch the Volume from the system 
     * background thread.
     */
    private final Runnable mUpdateSliderRunnable = new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "mUpdateSliderRunnable  ");
          int nowVoiceValue = SoundUtils.INSTANCE.get100CurrentVolume();
			  mHandler.obtainMessage(MSG_UPDATE_SLIDER, nowVoiceValue,
                     0).sendToTarget();	
					
					
        }
    };

 
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mExternalChange = true;
            try {
                switch (msg.what) {
                    case MSG_UPDATE_SLIDER:
                        Log.d(TAG, "handleMessage   MSG_UPDATE_SLIDER    ");
                        updateSlider(msg.arg1, msg.arg2 != 0);
                        break;
                    case MSG_ATTACH_LISTENER:
                        Log.d(TAG, "handleMessage   MSG_ATTACH_LISTENER   ");
                        mControl.setOnChangedListener(VolumeController.this);
                        break;
                    case MSG_DETACH_LISTENER:
                        Log.d(TAG, "handleMessage   MSG_DETACH_LISTENER    ");
                        mControl.setOnChangedListener(null);
                        break;
                    
                    default:
                        super.handleMessage(msg);
                }
            } finally {
                mExternalChange = false;
            }
        }
    };

    public VolumeController(Context context, ToggleSlider control,
                            BroadcastDispatcher broadcastDispatcher) {
        Log.d(TAG,"VolumeController:GAMMA_SPACE_MAX:"+GAMMA_SPACE_MAX);
   	    mContext = context;
        mControl = control;
        mControl.setMax(100); //GAMMA_SPACE_MAX
        mBackgroundHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
        mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
            @Override
            public void onUserSwitched(int newUserId) {
                 mBackgroundHandler.post(mUpdateSliderRunnable);
            }
        };
		Log.d(TAG,"VolumeController ,post mUpdateSliderRunnable ");
		mBackgroundHandler.post(mUpdateSliderRunnable);
        
		//wangfangchen add 
		mWorker = new W((Looper) Dependency.get(Dependency.BG_LOOPER));
        mBroadcastDispatcher = broadcastDispatcher;
        mReceiver.init();
		//wangfangchen end 
    }


    public void registerCallbacks() {
        mBackgroundHandler.post(mStartListeningRunnable);
    }

    /** Unregister all call backs, both to and from the controller */
    public void unregisterCallbacks() {
        mBackgroundHandler.post(mStopListeningRunnable);
        mControlValueInitialized = false;
    }

    @Override
     public void onChanged(boolean tracking, int value, boolean stopTracking) {
        Log.d(TAG, "onChanged  tracking:"+tracking+"   value:"+value+"    stopTracking:"+stopTracking);
        if (mExternalChange) return;

        if (mSliderAnimator != null) {
            mSliderAnimator.cancel();
        }
		
        if (!tracking) {
            AsyncTask.execute(new Runnable() {
                    public void run() {
                    //wangfangchen add					
					timeFlag = System.currentTimeMillis();
					Log.d(TAG,"onChanged  setVoice value:"+value+"   timeFlag:"+timeFlag);
					//wangfangchen end   
					SoundUtils.INSTANCE.setVoice(value);
                    }
                });
        }
		
    }

    public void checkRestrictionAndSetEnabled() {
		Log.d(TAG, " checkRestrictionAndSetEnabled ");
        mBackgroundHandler.post(new Runnable() {
            @Override
            public void run() {
                mControl.setEnforcedAdmin(
                        RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
                                UserManager.DISALLOW_CONFIG_BRIGHTNESS,
                                mUserTracker.getCurrentUserId()));
            }
        });
    }

    private void setBrightness(float brightness) {
        Log.d(TAG, "setBrightness   brightness:"+brightness);
     
	 
    }

 

    private void updateSlider(int brightnessValue, boolean inVrMode) {
        Log.d(TAG, "updateSlider   brightnessValue:"+brightnessValue);
        animateSliderTo(brightnessValue);
    }

    private void animateSliderTo(int target) {
		Log.d(TAG,"animateSliderTo target:"+target);
        if (!mControlValueInitialized) {
            // Don't animate the first value since its default state isn't meaningful to users.
            mControl.setValue(target);
            mControlValueInitialized = true;
        }
        if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
            mSliderAnimator.cancel();
        }
        mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target);
        mSliderAnimator.addUpdateListener((ValueAnimator animation) -> {
            mExternalChange = true;
            mControl.setValue((int) animation.getAnimatedValue());
            mExternalChange = false;
        });
        final long animationDuration = SLIDER_ANIMATION_DURATION * Math.abs(
                //mControl.getValue() - target) / GAMMA_SPACE_MAX;
				mControl.getValue() - target) / 100;
		Log.d(TAG,"animateSliderTo animationDuration:"+animationDuration);
        mSliderAnimator.setDuration(animationDuration);
        mSliderAnimator.start();
    }



    /** Factory for creating a {@link VolumeController}. */
    public static class Factory {
        private final Context mContext;
        private final BroadcastDispatcher mBroadcastDispatcher;

        @Inject
        public Factory(Context context, BroadcastDispatcher broadcastDispatcher) {
            mContext = context;
            mBroadcastDispatcher = broadcastDispatcher;
        }

        /** Create a {@link VolumeController} */
        public VolumeController create(ToggleSlider toggleSlider) {
            return new VolumeController(mContext, toggleSlider, mBroadcastDispatcher);
        }
    }

//wangfangchen add
    private final class Receiver extends BroadcastReceiver {

        public void init() {
            final IntentFilter filter = new IntentFilter();
            filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
        }

        public void destroy() {
            mBroadcastDispatcher.unregisterReceiver(this);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            boolean changed = false;
            if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
                final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
                final int oldLevel = intent
                        .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
                 Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
                        + " level=" + level + " oldLevel=" + oldLevel);
                 Log.d(TAG," mBackgroundHandler:"+mBackgroundHandler+"   mUpdateSliderRunnable:"+mUpdateSliderRunnable);
				 
				 long  timeNow = System.currentTimeMillis();
				 long spereteTime=timeNow-timeFlag;
				 Log.d(TAG," spereteTime:"+spereteTime);
                 if(spereteTime<100){
				  Log.d(TAG," time is to short return ");
				  return ;
				 }
                 if(mBackgroundHandler!=null&&mUpdateSliderRunnable!=null){
                     mBackgroundHandler.post(mUpdateSliderRunnable);
                 }

            }
        }
    }


    boolean onVolumeChangedW(int stream, int flags) {
        Log.d(TAG,"onVolumeChangedW  stream:"+stream+"  flags:"+flags);
        return true;
    }


    private final class W extends Handler {
        private static final int VOLUME_CHANGED = 1;


        W(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
            }
        }
    }
	//wangfangchen end


}

主要实现 步骤分析说明

  • 类的导入
java 复制代码
//wangfangchen add 
import com.android.systemui.volume.Events;
import com.android.systemui.volume.VolumeDialogControllerImpl;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
//wangfangchen end  
  • 类创建声明
java 复制代码
	//wangfangchen add 
    private final  Receiver mReceiver = new  Receiver();
    protected final BroadcastDispatcher mBroadcastDispatcher;
    private final  W mWorker;
    long timeFlag = System.currentTimeMillis();
    //wangfangchen end 
  • 构造方法中赋值变量,注册广播
java 复制代码
	    //wangfangchen add 
		mWorker = new W((Looper) Dependency.get(Dependency.BG_LOOPER));
        mBroadcastDispatcher = broadcastDispatcher;
        mReceiver.init();
		//wangfangchen end 
  • 进度条变更onChange 方法中声明时间TAG
java 复制代码
     public void onChanged(boolean tracking, int value, boolean stopTracking) {
        Log.d(TAG, "onChanged  tracking:"+tracking+"   value:"+value+"    stopTracking:"+stopTracking);
        if (mExternalChange) return;

        if (mSliderAnimator != null) {
            mSliderAnimator.cancel();
        }
		
        if (!tracking) {
            AsyncTask.execute(new Runnable() {
                    public void run() {
                    //wangfangchen add					
					timeFlag = System.currentTimeMillis();
					Log.d(TAG,"onChanged  setVoice value:"+value+"   timeFlag:"+timeFlag);
					//wangfangchen end   
					SoundUtils.INSTANCE.setVoice(value);
                    }
                });
        }
		
    }
  • 音量变化监听,从新设置音量值
java 复制代码
 @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            boolean changed = false;
            if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
                final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
                final int oldLevel = intent
                        .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
                 Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
                        + " level=" + level + " oldLevel=" + oldLevel);
                 Log.d(TAG," mBackgroundHandler:"+mBackgroundHandler+"   mUpdateSliderRunnable:"+mUpdateSliderRunnable);
				 
				 long  timeNow = System.currentTimeMillis();
				 long spereteTime=timeNow-timeFlag;
				 Log.d(TAG," spereteTime:"+spereteTime);
                 if(spereteTime<100){
				  Log.d(TAG," time is to short return ");
				  return ;
				 }
                 if(mBackgroundHandler!=null&&mUpdateSliderRunnable!=null){
                     mBackgroundHandler.post(mUpdateSliderRunnable);
                 }

            }
        }