SystemUI 实现音量条同步功能

server/2025/1/18 18:33:47/

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

具体问题

以前在SystemUI 下拉框添加了音量条控制,目前发现在SystemUI下拉框显示状态的情况下,
按键或者底部虚拟导航点击音量加减时候,SystemUI音量条不更新。

如下图:两个SystemUI 下拉音量条和系统自带音量条不同步
在这里插入图片描述

参考资料:

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

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

思路:

参考系统设置实现方案:

设置->提示音->媒体音量 在音量按键点击时候,音量条同步更新的业务流程
在这里插入图片描述

参考SystemUI 音量控制逻辑:

在这里插入图片描述

参考应用端实现

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

具体代码分析

设置模块,全局搜索 VOLUME_CHANGED_ACTION

VolumeSliceHelper ->onReceive-

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

分析 设置-提示音

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

Switching to fragment com.android.settings.notification.SoundSettings
Launching fragment com.android.settings.notification.SoundSettings
SoundSettings
加载布局文件
@Overrideprotected int getPreferenceScreenResId() {return R.xml.sound_settings;}具体媒体音量相关<!-- Media volume --><com.android.settings.notification.VolumeSeekBarPreferenceandroid: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 关联的控制器

public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceController {private static final String KEY_MEDIA_VOLUME = "media_volume";public MediaVolumePreferenceController(Context context) {super(context, KEY_MEDIA_VOLUME);}@Overridepublic int getAvailabilityStatus() {return mContext.getResources().getBoolean(R.bool.config_show_media_volume)? AVAILABLE: UNSUPPORTED_ON_DEVICE;}@Overridepublic boolean isSliceable() {return TextUtils.equals(getPreferenceKey(), KEY_MEDIA_VOLUME);}@Overridepublic boolean isPublicSlice() {return true;}@Overridepublic boolean useDynamicSliceSummary() {return true;}@Overridepublic String getPreferenceKey() {return KEY_MEDIA_VOLUME;}@Overridepublic int getAudioStream() {return AudioManager.STREAM_MUSIC;}@Overridepublic int getMuteIcon() {return R.drawable.ic_media_stream_off;}
}
VolumeSeekBarPreferenceController

它的官方解释

/** A slider preference that directly controls an audio stream volume (no dialog) **/
 做了以下几件事情:1)加载自己媒体音量的子布局  preference_volume_slider 2)将自己传递给 framework 层,并接收音量变化回调 SeekBarVolumizer.Callback 动态更新UI

/*** Base class for preference controller that handles VolumeSeekBarPreference*/
public abstract class VolumeSeekBarPreferenceController extendsAdjustVolumeRestrictedPreferenceController 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));}@VisibleForTestingvoid setAudioHelper(AudioHelper helper) {mHelper = helper;}public void setCallback(Callback callback) {mVolumePreferenceCallback = callback;}@Overridepublic 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();}}@Overridepublic int getSliderPosition() {if (mPreference != null) {return mPreference.getProgress();}return mHelper.getStreamVolume(getAudioStream());}@Overridepublic boolean setSliderPosition(int position) {if (mPreference != null) {mPreference.setProgress(position);}return mHelper.setStreamVolume(getAudioStream(), position);}@Overridepublic int getMax() {if (mPreference != null) {return mPreference.getMax();}return mHelper.getMaxVolume(getAudioStream());}@Overridepublic 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

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

/*** Base class for preference controller that handles preference that enforce adjust     volume  restriction  **/      

VOLUME_CHANGED_ACTION 就是在这里声明监听的

 @Overridepublic 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;}
/*** Base class for preference controller that handles preference that enforce adjust volume* restriction*/
public abstract class AdjustVolumeRestrictedPreferenceController extendsSliderPreferenceController {private AccountRestrictionHelper mHelper;public AdjustVolumeRestrictedPreferenceController(Context context, String key) {this(context, new AccountRestrictionHelper(context), key);}@VisibleForTestingAdjustVolumeRestrictedPreferenceController(Context context, AccountRestrictionHelper helper,String key) {super(context, key);mHelper = helper;}@Overridepublic void updateState(Preference preference) {if (!(preference instanceof RestrictedPreference)) {return;}mHelper.enforceRestrictionOnPreference((RestrictedPreference) preference,UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId());}@Overridepublic 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
/** A slider preference that directly controls an audio stream volume (no dialog) **/
这个是布局 媒体音量的自定义UI

/** 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;@VisibleForTestingAudioManager 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_SCOsetMin(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;}}@Overridepublic 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() {@Overridepublic void onSampleStarting(SeekBarVolumizer sbv) {if (mCallback != null) {mCallback.onSampleStarting(sbv);}}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {if (mCallback != null) {mCallback.onStreamValueChanged(mStream, progress);}}@Overridepublic void onMuted(boolean muted, boolean zenMuted) {if (mMuted == muted && mZenMuted == zenMuted) return;mMuted = muted;mZenMuted = zenMuted;updateIconView();}@Overridepublic 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 层

 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
\frameworks\base\core\java\android\preference\SeekBarVolumizer.java 

部分核心代码

 private final class Observer extends ContentObserver {public Observer(Handler handler) {super(handler);}@Overridepublic 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();}@Overridepublic 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_sliderVolumeSeekBarPreference UI 类得布局,真正得媒体音量子布局
SeekBarPreference媒体音量UI自定义UI类得父类 就是支持基本的功能,seekBar 相关的基本功能。 setProgress 方法,原来是在父类中设置并更新UI的
MediaVolumePreferenceControllersound_settings布局中对应音量控制器
VolumeSeekBarPreferenceController处理音量控制器的基本父类
AdjustVolumeRestrictedPreferenceController处理音量控制器的基本父类,执行调节音量
SeekBarVolumizer把seek 传递到framework层,framework层实现seekbar 的控制,并回调到App层

分析 SystemUI 层

全局搜索  VOLUME_CHANGED_ACTION 思路分析
VolumeDialogControllerImpl

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

 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);}@Overridepublic 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 找到这个类:


/*** 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
  @Overridepublic void onStateChanged(State state) {onStateChangedH(state);}
onStateChangedH
 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 rowsfor (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

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

设置监听 addCallback

         public void init(int windowType, Callback callback) {initDialog(mActivityManager.getLockTaskModeState());mAccessibility.init();mController.addCallback(mControllerCallbackH, mHandler);mController.getState();mConfigurationController.addCallback(this);}
mControllerCallbackH
 private final VolumeDialogController.Callbacks mControllerCallbackH= new VolumeDialogController.Callbacks() {@Overridepublic void onShowRequested(int reason, boolean keyguardLocked, int lockTaskModeState) {showH(reason, keyguardLocked, lockTaskModeState);}@Overridepublic void onDismissRequested(int reason) {dismissH(reason);}@Overridepublic void onScreenOff() {dismissH(Events.DISMISS_REASON_SCREEN_OFF);}@Overridepublic void onStateChanged(State state) {onStateChangedH(state);}@Overridepublic void onLayoutDirectionChanged(int layoutDirection) {mDialogView.setLayoutDirection(layoutDirection);}@Overridepublic void onConfigurationChanged() {mDialog.dismiss();mConfigChanged = true;}@Overridepublic void onShowVibrateHint() {if (mSilentMode) {mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);}}@Overridepublic void onShowSilentHint() {if (mSilentMode) {mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);}}@Overridepublic void onShowSafetyWarning(int flags) {showSafetyWarningH(flags);}@Overridepublic 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);}}@Overridepublic 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 来实现需求

修改文件:

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

参考SystemUI层的 VOLUME_CHANGED_ACTION 监听

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

基本代码如下:

//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() {@Overridepublic void run() {if (mListening) {return;}mListening = true;mUserTracker.startTracking();mUpdateSliderRunnable.run();mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);}};private final Runnable mStopListeningRunnable = new Runnable() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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_MAXmBackgroundHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));mUserTracker = new CurrentUserTracker(broadcastDispatcher) {@Overridepublic 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;}@Overridepublic 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() {@Overridepublic 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;@Injectpublic 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 addprivate 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);}@Overridepublic 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);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;}}}//wangfangchen end}

主要实现 步骤分析说明

  • 类的导入
//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  
  • 类创建声明
	//wangfangchen add private final  Receiver mReceiver = new  Receiver();protected final BroadcastDispatcher mBroadcastDispatcher;private final  W mWorker;long timeFlag = System.currentTimeMillis();//wangfangchen end 
  • 构造方法中赋值变量,注册广播
	    //wangfangchen add mWorker = new W((Looper) Dependency.get(Dependency.BG_LOOPER));mBroadcastDispatcher = broadcastDispatcher;mReceiver.init();//wangfangchen end 
  • 进度条变更onChange 方法中声明时间TAG
     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);}});}}
  • 音量变化监听,从新设置音量值
 @Overridepublic 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);}}}

http://www.ppmy.cn/server/159419.html

相关文章

几个Linux系统安装体验(续): 中科方德服务器系统

本文介绍中科方德服务器系统&#xff08;NFSDesktop&#xff09;的安装。 下载 下载地址&#xff1a; https://www.nfschina.com/index.php?catid68 下载文件&#xff1a;本文下载的文件名称为NFSCNS-4.0-G330-x86_64-241128.iso。 下载注意事项&#xff1a;无法直接下载&…

Zookeeper 数据迁移实战:基础环境搭建与高效迁移方案全览

文章目录 一、Zookeeper数据迁移简介二、迁移zookeeper数据基础环境三、利用快照迁移zookeeper数据1、Node1最新的zk快照文件和日志文件2、将被迁移方node2的zookeeper的集群全部stop3、将源node1集群数据和日志拷贝到指定目录下4、验证优先启动拷贝的数据、日志的zookeeper节点…

Ardupilot开源无人机之Geek SDK进展2024

Ardupilot开源无人机之Geek SDK进展202501 1. 源由2. 状态3. TODO3.1 【进行中】跟踪目标框3.2 【进行中】onnxruntime版本3.3 【完成】CUDA 11.8版本3.4 【完成】pytorch v2.5.1版本3.5 【未开始】Inference性能3.6 【未开始】特定目标集Training 4. Extra-Work4.1 【完成】CU…

自动化之Ansible

一、Ansible介绍 Ansible是一个同时管理多个远程主机的软件(任何可以通过SSH协议登录的机器)&#xff0c;因此Ansible可以管理 运程虚拟机、物理机&#xff0c;也可以是本地主机(linux、windows)。 Ansible通过SSH协议实现 管理节点、远程节点的通信。 只要是能够SSH登录的主机…

接口防篡改+防重放攻击

接口防止重放攻击&#xff1a;重放攻击是指攻击者截获了一次有效请求(如交易请求),并在之后的时间里多次发送相同的请求&#xff0c;从而达到欺骗系统的目的。为了防止重放攻击&#xff0c;通常需要在系统中引入一种机制&#xff0c;使得每个请求都有一个唯一的标识符(如时间戳…

基础IO -- 动静态库(1)

目录 一、认识库 ​编辑 二、静态库 1&#xff09;粗略认识 2&#xff09;理解 一、认识库 C语言标准库&#xff1a; C标准库&#xff1a; 下面才是C的标准库&#xff0c;上面的只是配置过Vim后显示出来的 在Linux中&#xff0c;一般动态库以 .so结尾、静态库以 .a结尾 而在…

ZooKeeper 核心知识全解析:架构、角色、节点与应用

1.ZooKeeper 分布式锁怎么实现的 ZooKeeper 是一个高效的分布式协调服务&#xff0c;它提供了简单的原语集来构建更复杂的同步原语和协调数据结构。利用 ZooKeeper 实现分布式锁主要依赖于它的顺序节点&#xff08;Sequential Node&#xff09;特性以及临时节点&#xff08;Ep…

【SpringBoot】【log】 自定义logback日志配置

前言&#xff1a;默认情况下&#xff0c;SpringBoot内部使用logback作为系统日志实现的框架&#xff0c;将日志输出到控制台&#xff0c;不会写到日志文件。如果在application.properties或application.yml配置&#xff0c;这样只能配置简单的场景&#xff0c;保存路径、日志格…