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