当前位置: 首页 > news >正文

SystemUI 实现音量条同步功能

需求: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);}}}

相关文章:

SystemUI 实现音量条同步功能

需求&#xff1a;SystemUI 实现音量条同步功能 具体问题 以前在SystemUI 下拉框添加了音量条控制&#xff0c;目前发现在SystemUI下拉框显示状态的情况下&#xff0c; 按键或者底部虚拟导航点击音量加减时候&#xff0c;SystemUI音量条不更新。 如下图&#xff1a;两个Syste…...

嵌入式知识点总结 C/C++ 专题提升(一)-关键字

针对于嵌入式软件杂乱的知识点总结起来&#xff0c;提供给读者学习复习对下述内容的强化。 目录 1.C语言宏中"#“和"##"的用法 1.1.(#)字符串化操作符 1.2.(##)符号连接操作符 2.关键字volatile有什么含意?并举出三个不同的例子? 2.1.并行设备的硬件寄存…...

基础入门-传输加密数据格式编码算法密文存储代码混淆逆向保护安全影响

知识点&#xff1a; 1、传输格式&传输数据-类型&编码&算法 2、密码存储&代码混淆-不可逆&非对称性 一、演示案例-传输格式&传输数据-类型&编码&算法 传输格式 JSON XML WebSockets HTML 二进制 自定义 WebSockets&#xff1a;聊天交互较常…...

几个Linux系统安装体验(续): 统信桌面系统

本文介绍统信桌面系统&#xff08;uos&#xff09;的安装。 下载 下载地址&#xff1a; https://www.chinauos.com/resource/download-professional 下载文件&#xff1a;本文下载文件名称为uos-desktop-20-professional-1070-amd64.iso。 下载注意事项&#xff1a;可直接下…...

算法日记6.StarryCoding P52:我们都需要0(异或)

一、题目 二、题解&#xff1a; 1、对于这道题&#xff0c;题意为让我们寻找一个数x使得 b[i]a[i]^x&#xff0c; 并且b[1]^b[2]^b[3]^ b[4]^b[5]....0 2、我们把b[i]给拆开&#xff0c;可以得到 3、又因为^满足结合律&#xff0c;因此&#xff0c;可以把括号给拆开 4、接着…...

【网络协议】RFC3164-The BSD syslog Protocol

引言 Syslog常被称为系统日志或系统记录&#xff0c;是一种标准化的协议&#xff0c;用于网络设备、服务器和应用程序向中央Syslog服务器发送日志消息。互联网工程任务组&#xff08;IETF&#xff09;发布的RFC 3164&#xff0c;专门定义了BSD Syslog协议的规范和实现方式。通…...

SpringCloud -根据服务名获取服务运行实例并进行负载均衡

Nacos注册中心 每个服务启动之后都要向注册中心发送服务注册请求&#xff0c;注册中心可以和各个注册客户端自定义协议实现服务注册和发现。 pom.xml <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-na…...

CentOS 安装Redis

1. 安装 Redis 安装 EPEL 仓库&#xff08;对于 CentOS/RHEL 系统&#xff09;&#xff1a; 首先安装 EPEL 仓库&#xff0c;因为 Redis 存在于 EPEL 仓库中&#xff1a; yum install epel-release安装 Redis 数据库&#xff1a; yum install redis2. 修改 Redis 配置文件 …...

Linux网络 TCP socket

TCP简介 TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议。它位于OSI模型的第四层&#xff0c;主要为应用层提供数据传输服务。TCP通过三次握手建立连接&#xff0c;确保数据在发送和接收过程中的准确性和顺序…...

(一)相机标定——四大坐标系的介绍、对应转换、畸变原理以及OpenCV完整代码实战(C++版)

一、四大坐标系介绍 1&#xff0c;世界坐标系 从这个世界&#xff08;world&#xff09;的视角来看物体 世界坐标系是3D空间坐标&#xff0c;每个点的位置用 ( X w , Y w , Z w ) (X_w,Y_w,Z_w) (Xw​,Yw​,Zw​)表示 2&#xff0c;相机坐标系 相机本身具有一个坐标系&…...

【Linux网络编程】高效I/O--I/O的五种类型

目录 I/O的概念 网络通信的本质 I/O的本质 高效I/O 五种I/O模型 阻塞I/O 非阻塞I/O 信号驱动I/O 多路转接/多路复用I/O 异步I/O 非阻塞I/O的实现 I/O的概念 网络通信的本质 网络通信的本质其实就是I/O I&#xff1a;表示input(输入)O&#xff1a;表示ou…...

企业级NoSQL数据库Redis

1.浏览器缓存过期机制 1.1 最后修改时间 last-modified 浏览器缓存机制是优化网页加载速度和减少服务器负载的重要手段。以下是关于浏览器缓存过期机制、Last-Modified 和 ETag 的详细讲解&#xff1a; 一、Last-Modified 头部 定义&#xff1a;Last-Modified 表示服务器上资源…...

Vscode:问题解决办法 及 Tips 总结

Visual Studio Code&#xff08;简称VSCode&#xff09;是一个功能强大的开源代码编辑器&#xff0c;广泛用于各种编程语言和开发场景&#xff0c;本博客主要记录在使用 VSCode 进行verilog开发时遇到的问题及解决办法&#xff0c;使用过程中的技巧 文章目录 扩展安装失败调试配…...

二十三种设计模式-装饰器模式

一、定义与核心思想 装饰器模式是一种结构型设计模式&#xff0c;其核心思想是动态地给一个对象添加一些额外的职责。通过这种方式&#xff0c;可以在不改变原有对象结构的基础上&#xff0c;灵活地增加新的功能&#xff0c;使得对象的行为可以得到扩展&#xff0c;同时又保持…...

架构思考与实践:从通用到场景的转变

在当今复杂多变的商业环境中&#xff0c;企业架构的设计与优化成为了一个关键议题。本文通过一系列随笔&#xff0c;探讨了业务架构的价值、从通用架构到场景架构的转变、恰如其分的架构设计以及如何避免盲目低效等问题。通过对多个实际案例的分析&#xff0c;笔者揭示了架构设…...

Spring MVC(一)

RestController RestController 是由 Controller 和 ResponseBody 两个注解构成的。 Spring 启动的时候会扫描所有包含 Controller 或者 RestController 注解的类&#xff0c;创建出对外的接口&#xff0c;这样外界就可以从这里与服务器实现交互&#xff0c;如果没有这个注解…...

vue3使用tsx渲染复杂逻辑的表单

前置 目前的应用场景是&#xff1a;检查项目是树结构&#xff0c;有的项目还需要动态显示对应的子集 项目用的是uniappvue3tsvite生成的app tsx模版 统一渲染入口 <script lang"ts">import uniForms from dcloudio/uni-ui/lib/uni-forms/uni-forms.vueimport…...

python助力WRF自动化运行

对大部分人而言&#xff0c;特别是新用户&#xff0c;WRF模式的安装繁琐且不必要&#xff0c;可以作为后续进阶掌握的技能&#xff0c;本学习跳过繁琐的安装步骤&#xff0c;直接聚焦模式的运行部分&#xff0c;通过短平快的教学&#xff0c;快速掌握模式运行。进一步将python语…...

Windows 下 Postgres 安装 TimescaleDB 插件

Windows 下 Postgres 安装 TimescaleDB 插件 一、准备工作 安装 PostgreSQL&#xff1a;首先确保你已经在 Windows 系统中成功安装了 PostgreSQL 数据库。可以从 PostgreSQL 官方网站下载适合你系统的安装包&#xff0c;并按照安装向导进行安装。安装过程中&#xff0c;记住设…...

【Vim Masterclass 笔记21】S09L39:Vim 设置与 vimrc 文件的用法示例(二)

文章目录 S09L39 Vim Settings and the Vimrc File - Part 21 Vim 的配色方案与 color 命令2 map 命令3 示例&#xff1a;用 map 命令快速生成 HTML 代码片段4 Vim 中的 Leader 键5 用 mkvimrc 命令自动生成配置文件 写在前面 本篇为 Vim 自定义配置的第二部分。当中的每个知识…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》

&#x1f9e0; LangChain 中 TextSplitter 的使用详解&#xff1a;从基础到进阶&#xff08;附代码&#xff09; 一、前言 在处理大规模文本数据时&#xff0c;特别是在构建知识库或进行大模型训练与推理时&#xff0c;文本切分&#xff08;Text Splitting&#xff09; 是一个…...

React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构

React 实战项目&#xff1a;微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇&#xff01;在前 29 篇文章中&#xff0c;我们从 React 的基础概念逐步深入到高级技巧&#xff0c;涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...

32位寻址与64位寻址

32位寻址与64位寻址 32位寻址是什么&#xff1f; 32位寻址是指计算机的CPU、内存或总线系统使用32位二进制数来标识和访问内存中的存储单元&#xff08;地址&#xff09;&#xff0c;其核心含义与能力如下&#xff1a; 1. 核心定义 地址位宽&#xff1a;CPU或内存控制器用32位…...

FOPLP vs CoWoS

以下是 FOPLP&#xff08;Fan-out panel-level packaging 扇出型面板级封装&#xff09;与 CoWoS&#xff08;Chip on Wafer on Substrate&#xff09;两种先进封装技术的详细对比分析&#xff0c;涵盖技术原理、性能、成本、应用场景及市场趋势等维度&#xff1a; 一、技术原…...