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
加载布局文件
@Override
protected int getPreferenceScreenResId() {
return R.xml.sound_settings;
}
具体媒体音量相关
<!-- Media volume -->
<com.android.settings.notification.VolumeSeekBarPreference
android:key="media_volume"
android:icon="@drawable/ic_media_stream"
android:title="@string/media_volume_option_title"
android:order="-180"
settings:controller="com.android.settings.notification.MediaVolumePreferenceController"/>
MediaVolumePreferenceController
Volume 关联的控制器
public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceController {
private static final String KEY_MEDIA_VOLUME = "media_volume";
public MediaVolumePreferenceController(Context context) {
super(context, KEY_MEDIA_VOLUME);
}
@Override
public int getAvailabilityStatus() {
return mContext.getResources().getBoolean(R.bool.config_show_media_volume)
? AVAILABLE
: UNSUPPORTED_ON_DEVICE;
}
@Override
public boolean isSliceable() {
return TextUtils.equals(getPreferenceKey(), KEY_MEDIA_VOLUME);
}
@Override
public boolean isPublicSlice() {
return true;
}
@Override
public boolean useDynamicSliceSummary() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY_MEDIA_VOLUME;
}
@Override
public int getAudioStream() {
return AudioManager.STREAM_MUSIC;
}
@Override
public int getMuteIcon() {
return R.drawable.ic_media_stream_off;
}
}
VolumeSeekBarPreferenceController
它的官方解释
:/** 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 extends
AdjustVolumeRestrictedPreferenceController implements LifecycleObserver {
protected VolumeSeekBarPreference mPreference;
protected VolumeSeekBarPreference.Callback mVolumePreferenceCallback;
protected AudioHelper mHelper;
public VolumeSeekBarPreferenceController(Context context, String key) {
super(context, key);
setAudioHelper(new AudioHelper(context));
}
@VisibleForTesting
void setAudioHelper(AudioHelper helper) {
mHelper = helper;
}
public void setCallback(Callback callback) {
mVolumePreferenceCallback = callback;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
if (isAvailable()) {
mPreference = screen.findPreference(getPreferenceKey());
mPreference.setCallback(mVolumePreferenceCallback);
mPreference.setStream(getAudioStream());
mPreference.setMuteIcon(getMuteIcon());
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
if (mPreference != null) {
mPreference.onActivityResume();
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void onPause() {
if (mPreference != null) {
mPreference.onActivityPause();
}
}
@Override
public int getSliderPosition() {
if (mPreference != null) {
return mPreference.getProgress();
}
return mHelper.getStreamVolume(getAudioStream());
}
@Override
public boolean setSliderPosition(int position) {
if (mPreference != null) {
mPreference.setProgress(position);
}
return mHelper.setStreamVolume(getAudioStream(), position);
}
@Override
public int getMax() {
if (mPreference != null) {
return mPreference.getMax();
}
return mHelper.getMaxVolume(getAudioStream());
}
@Override
public int getMin() {
if (mPreference != null) {
return mPreference.getMin();
}
return mHelper.getMinVolume(getAudioStream());
}
/**
* @return the audio stream type
*/
public abstract int getAudioStream();
protected abstract int getMuteIcon();
}
AdjustVolumeRestrictedPreferenceController
处理音量控制器的基本父类,执行调节音量 官方解释:
/*** Base class for preference controller that handles preference that enforce adjust volume restriction **/
VOLUME_CHANGED_ACTION 就是在这里声明监听的
@Override
public IntentFilter getIntentFilter() {
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
filter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);
filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
return filter;
}
/**
* Base class for preference controller that handles preference that enforce adjust volume
* restriction
*/
public abstract class AdjustVolumeRestrictedPreferenceController extends
SliderPreferenceController {
private AccountRestrictionHelper mHelper;
public AdjustVolumeRestrictedPreferenceController(Context context, String key) {
this(context, new AccountRestrictionHelper(context), key);
}
@VisibleForTesting
AdjustVolumeRestrictedPreferenceController(Context context, AccountRestrictionHelper helper,
String key) {
super(context, key);
mHelper = helper;
}
@Override
public void updateState(Preference preference) {
if (!(preference instanceof RestrictedPreference)) {
return;
}
mHelper.enforceRestrictionOnPreference((RestrictedPreference) preference,
UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId());
}
@Override
public IntentFilter getIntentFilter() {
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
filter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);
filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
return filter;
}
}
VolumeSeekBarPreference
/** 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;
@VisibleForTesting
AudioManager mAudioManager;
public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.preference_volume_slider);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutResource(R.layout.preference_volume_slider);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
public VolumeSeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.preference_volume_slider);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
public VolumeSeekBarPreference(Context context) {
super(context);
setLayoutResource(R.layout.preference_volume_slider);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
public void setStream(int stream) {
mStream = stream;
setMax(mAudioManager.getStreamMaxVolume(mStream));
// Use getStreamMinVolumeInt for non-public stream type
// eg: AudioManager.STREAM_BLUETOOTH_SCO
setMin(mAudioManager.getStreamMinVolumeInt(mStream));
setProgress(mAudioManager.getStreamVolume(mStream));
}
public void setCallback(Callback callback) {
mCallback = callback;
}
public void onActivityResume() {
if (mStopped) {
init();
}
}
public void onActivityPause() {
mStopped = true;
if (mVolumizer != null) {
mVolumizer.stop();
mVolumizer = null;
}
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
mSuppressionTextView = (TextView) view.findViewById(R.id.suppression_text);
init();
}
protected void init() {
if (mSeekBar == null) return;
final SeekBarVolumizer.Callback sbvc = new SeekBarVolumizer.Callback() {
@Override
public void onSampleStarting(SeekBarVolumizer sbv) {
if (mCallback != null) {
mCallback.onSampleStarting(sbv);
}
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
if (mCallback != null) {
mCallback.onStreamValueChanged(mStream, progress);
}
}
@Override
public void onMuted(boolean muted, boolean zenMuted) {
if (mMuted == muted && mZenMuted == zenMuted) return;
mMuted = muted;
mZenMuted = zenMuted;
updateIconView();
}
@Override
public void onStartTrackingTouch(SeekBarVolumizer sbv) {
if (mCallback != null) {
mCallback.onStartTrackingTouch(sbv);
}
}
};
final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
if (mVolumizer == null) {
mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc);
}
mVolumizer.start();
mVolumizer.setSeekBar(mSeekBar);
updateIconView();
updateSuppressionText();
if (!isEnabled()) {
mSeekBar.setEnabled(false);
mVolumizer.stop();
}
}
protected void updateIconView() {
if (mIconView == null) return;
if (mIconResId != 0) {
mIconView.setImageResource(mIconResId);
} else if (mMuteIconResId != 0 && mMuted && !mZenMuted) {
mIconView.setImageResource(mMuteIconResId);
} else {
mIconView.setImageDrawable(getIcon());
}
}
public void showIcon(int resId) {
// Instead of using setIcon, which will trigger listeners, this just decorates the
// preference temporarily with a new icon.
if (mIconResId == resId) return;
mIconResId = resId;
updateIconView();
}
public void setMuteIcon(int resId) {
if (mMuteIconResId == resId) return;
mMuteIconResId = resId;
updateIconView();
}
private Uri getMediaVolumeUri() {
return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ getContext().getPackageName()
+ "/" + R.raw.media_volume);
}
public void setSuppressionText(String text) {
if (Objects.equals(text, mSuppressionText)) return;
mSuppressionText = text;
updateSuppressionText();
}
protected void updateSuppressionText() {
if (mSuppressionTextView != null && mSeekBar != null) {
mSuppressionTextView.setText(mSuppressionText);
final boolean showSuppression = !TextUtils.isEmpty(mSuppressionText);
mSuppressionTextView.setVisibility(showSuppression ? View.VISIBLE : View.GONE);
}
}
public interface Callback {
void onSampleStarting(SeekBarVolumizer sbv);
void onStreamValueChanged(int stream, int progress);
/**
* Callback reporting that the seek bar is start tracking.
*/
void onStartTrackingTouch(SeekBarVolumizer sbv);
}
}
核心代码,监听音量变化,将seekbar 传递到framework 层
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);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
updateSlider();
}
}
private void updateSlider() {
if (mSeekBar != null && mAudioManager != null) {
final int volume = mAudioManager.getStreamVolume(mStreamType);
final int lastAudibleVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
final boolean mute = mAudioManager.isStreamMute(mStreamType);
mUiHandler.postUpdateSlider(volume, lastAudibleVolume, mute);
}
}
public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) {
obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, new Boolean(mute)).sendToTarget();
}
@Override
public void handleMessage(Message msg) {
if (msg.what == UPDATE_SLIDER) {
if (mSeekBar != null) {
mLastProgress = msg.arg1;
mLastAudibleStreamVolume = msg.arg2;
final boolean muted = ((Boolean)msg.obj).booleanValue();
if (muted != mMuted) {
mMuted = muted;
if (mCallback != null) {
mCallback.onMuted(mMuted, isZenMuted());
}
}
updateSeekBar();
}
}
}
protected void updateSeekBar() {
final boolean zenMuted = isZenMuted();
mSeekBar.setEnabled(!zenMuted);
if (zenMuted) {
mSeekBar.setProgress(mLastAudibleStreamVolume, true);
} else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
mSeekBar.setProgress(0, true);
} else if (mMuted) {
mSeekBar.setProgress(0, true);
} else {
mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume, true);
}
}
设置音量控制同步进度条 核心代码 总结
类 | 作用 |
---|---|
SoundSettings | 设置->提示音 面板 |
sound_settings | 设置提示音面板布局 |
VolumeSeekBarPreference | 加载自己媒体音量的子布局 preference_volume_slider ;将自己传递给 framework 层,并接收音量变化回调 SeekBarVolumizer.Callback 动态更新UI |
preference_volume_slider | VolumeSeekBarPreference UI 类得布局,真正得媒体音量子布局 |
SeekBarPreference | 媒体音量UI自定义UI类得父类 就是支持基本的功能,seekBar 相关的基本功能。 setProgress 方法,原来是在父类中设置并更新UI的 |
MediaVolumePreferenceController | sound_settings布局中对应音量控制器 |
VolumeSeekBarPreferenceController | 处理音量控制器的基本父类 |
AdjustVolumeRestrictedPreferenceController | 处理音量控制器的基本父类,执行调节音量 |
SeekBarVolumizer | 把seek 传递到framework层,framework层实现seekbar 的控制,并回调到App层 |
分析 SystemUI 层
全局搜索 VOLUME_CHANGED_ACTION 思路分析
VolumeDialogControllerImpl
核心内容 注册了一个音量相关广播 并接收处理
Receiver
onVolumeChangedW
private final class Receiver extends BroadcastReceiver {
public void init() {
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
。。。。。。。。。。。。。。。。。。。。。
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
}
public void destroy() {
mBroadcastDispatcher.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
boolean changed = false;
if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
final int oldLevel = intent
.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
+ " level=" + level + " oldLevel=" + oldLevel);
changed = updateStreamLevelW(stream, level);
}
。。。。。。。。。。。。。。。。。。。。。。。。。
if (changed) {
mCallbacks.onStateChanged(mState);
}
}
}
boolean onVolumeChangedW(int stream, int flags) {
final boolean showUI = shouldShowUI(flags);
final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
boolean changed = false;
if (showUI) {
changed |= updateActiveStreamW(stream);
}
int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream);
changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);
changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
if (changed) {
mCallbacks.onStateChanged(mState);
}
if (showUI) {
onShowRequestedW(Events.SHOW_REASON_VOLUME_CHANGED);
}
if (showVibrateHint) {
mCallbacks.onShowVibrateHint();
}
if (showSilentHint) {
mCallbacks.onShowSilentHint();
}
if (changed && fromKey) {
Events.writeEvent(Events.EVENT_KEY, stream, lastAudibleStreamVolume);
}
return changed;
}
VolumeDialogImpl
通过回调方法 onStateChanged 找到这个类:
/**
* 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
@Override
public 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 rows
for (int i = 0; i < state.states.size(); i++) {
final int stream = state.states.keyAt(i);
final StreamState ss = state.states.valueAt(i);
if (!ss.dynamic) continue;
mDynamic.put(stream, true);
if (findRow(stream) == null) {
addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,
false, true);
}
}
if (mActiveStream != state.activeStream) {
mPrevActiveStream = mActiveStream;
mActiveStream = state.activeStream;
VolumeRow activeRow = getActiveRow();
updateRowsH(activeRow);
if (mShowing) rescheduleTimeoutH();
}
for (VolumeRow row : mRows) {
updateVolumeRowH(row);
}
updateRingerH();
mWindow.setTitle(composeWindowTitle());
}
这里看到的是处理UI的操作,SystemUI 从VOLUME广播监听 到 接收 到 传递到 VolumeDialogImpl 控制UI就已经形成了闭环了。
分析SystemUI 音量流程- 模拟流程分析
回到 VolumeDialogControllerImpl 类的广播监听回调方法 onReceive
回调的最终方法:
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() {
@Override
public void onShowRequested(int reason, boolean keyguardLocked, int lockTaskModeState) {
showH(reason, keyguardLocked, lockTaskModeState);
}
@Override
public void onDismissRequested(int reason) {
dismissH(reason);
}
@Override
public void onScreenOff() {
dismissH(Events.DISMISS_REASON_SCREEN_OFF);
}
@Override
public void onStateChanged(State state) {
onStateChangedH(state);
}
@Override
public void onLayoutDirectionChanged(int layoutDirection) {
mDialogView.setLayoutDirection(layoutDirection);
}
@Override
public void onConfigurationChanged() {
mDialog.dismiss();
mConfigChanged = true;
}
@Override
public void onShowVibrateHint() {
if (mSilentMode) {
mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
}
}
@Override
public void onShowSilentHint() {
if (mSilentMode) {
mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
}
}
@Override
public void onShowSafetyWarning(int flags) {
showSafetyWarningH(flags);
}
@Override
public void onAccessibilityModeChanged(Boolean showA11yStream) {
mShowA11yStream = showA11yStream == null ? false : showA11yStream;
VolumeRow activeRow = getActiveRow();
if (!mShowA11yStream && STREAM_ACCESSIBILITY == activeRow.stream) {
dismissH(Events.DISMISS_STREAM_GONE);
} else {
updateRowsH(activeRow);
}
}
@Override
public void onCaptionComponentStateChanged(
Boolean isComponentEnabled, Boolean fromTooltip) {
updateODICaptionsH(isComponentEnabled, fromTooltip);
}
};
分析到 VolumeDialogController.Callbacks mControllerCallbackH 就不用继续分析了。 这个回调 是写在类里面的。
分析 SystemUI 层 分析总结
- VOLUME_CHANGED_ACTION 监听回调
- VolumeDialogControllerImpl 注册回调addCallback
- VolumeDialogImpl 初始化 init 中 注册回调 addCallback
实现方案如下:
实现方案如下:
通过上面的 SystemUI 的音量回调流程分析和设置中音量回调流程,都是从音量监听的地方开始。但针对本需求并不合适在对应地方设置回调来实现 音量的监听。
实现思路
我们也直接监听VOLUME_CHANGED_ACTION 来实现需求
修改文件:
/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() {
@Override
public void run() {
if (mListening) {
return;
}
mListening = true;
mUserTracker.startTracking();
mUpdateSliderRunnable.run();
mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
}
};
private final Runnable mStopListeningRunnable = new Runnable() {
@Override
public void run() {
if (!mListening) {
return;
}
mListening = false;
mUserTracker.stopTracking();
mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
}
};
/**
* Fetch the Volume from the system
* background thread.
*/
private final Runnable mUpdateSliderRunnable = new Runnable() {
@Override
public void run() {
Log.d(TAG, "mUpdateSliderRunnable ");
int nowVoiceValue = SoundUtils.INSTANCE.get100CurrentVolume();
mHandler.obtainMessage(MSG_UPDATE_SLIDER, nowVoiceValue,
0).sendToTarget();
}
};
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mExternalChange = true;
try {
switch (msg.what) {
case MSG_UPDATE_SLIDER:
Log.d(TAG, "handleMessage MSG_UPDATE_SLIDER ");
updateSlider(msg.arg1, msg.arg2 != 0);
break;
case MSG_ATTACH_LISTENER:
Log.d(TAG, "handleMessage MSG_ATTACH_LISTENER ");
mControl.setOnChangedListener(VolumeController.this);
break;
case MSG_DETACH_LISTENER:
Log.d(TAG, "handleMessage MSG_DETACH_LISTENER ");
mControl.setOnChangedListener(null);
break;
default:
super.handleMessage(msg);
}
} finally {
mExternalChange = false;
}
}
};
public VolumeController(Context context, ToggleSlider control,
BroadcastDispatcher broadcastDispatcher) {
Log.d(TAG,"VolumeController:GAMMA_SPACE_MAX:"+GAMMA_SPACE_MAX);
mContext = context;
mControl = control;
mControl.setMax(100); //GAMMA_SPACE_MAX
mBackgroundHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
@Override
public void onUserSwitched(int newUserId) {
mBackgroundHandler.post(mUpdateSliderRunnable);
}
};
Log.d(TAG,"VolumeController ,post mUpdateSliderRunnable ");
mBackgroundHandler.post(mUpdateSliderRunnable);
//wangfangchen add
mWorker = new W((Looper) Dependency.get(Dependency.BG_LOOPER));
mBroadcastDispatcher = broadcastDispatcher;
mReceiver.init();
//wangfangchen end
}
public void registerCallbacks() {
mBackgroundHandler.post(mStartListeningRunnable);
}
/** Unregister all call backs, both to and from the controller */
public void unregisterCallbacks() {
mBackgroundHandler.post(mStopListeningRunnable);
mControlValueInitialized = false;
}
@Override
public void onChanged(boolean tracking, int value, boolean stopTracking) {
Log.d(TAG, "onChanged tracking:"+tracking+" value:"+value+" stopTracking:"+stopTracking);
if (mExternalChange) return;
if (mSliderAnimator != null) {
mSliderAnimator.cancel();
}
if (!tracking) {
AsyncTask.execute(new Runnable() {
public void run() {
//wangfangchen add
timeFlag = System.currentTimeMillis();
Log.d(TAG,"onChanged setVoice value:"+value+" timeFlag:"+timeFlag);
//wangfangchen end
SoundUtils.INSTANCE.setVoice(value);
}
});
}
}
public void checkRestrictionAndSetEnabled() {
Log.d(TAG, " checkRestrictionAndSetEnabled ");
mBackgroundHandler.post(new Runnable() {
@Override
public void run() {
mControl.setEnforcedAdmin(
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
UserManager.DISALLOW_CONFIG_BRIGHTNESS,
mUserTracker.getCurrentUserId()));
}
});
}
private void setBrightness(float brightness) {
Log.d(TAG, "setBrightness brightness:"+brightness);
}
private void updateSlider(int brightnessValue, boolean inVrMode) {
Log.d(TAG, "updateSlider brightnessValue:"+brightnessValue);
animateSliderTo(brightnessValue);
}
private void animateSliderTo(int target) {
Log.d(TAG,"animateSliderTo target:"+target);
if (!mControlValueInitialized) {
// Don't animate the first value since its default state isn't meaningful to users.
mControl.setValue(target);
mControlValueInitialized = true;
}
if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
mSliderAnimator.cancel();
}
mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target);
mSliderAnimator.addUpdateListener((ValueAnimator animation) -> {
mExternalChange = true;
mControl.setValue((int) animation.getAnimatedValue());
mExternalChange = false;
});
final long animationDuration = SLIDER_ANIMATION_DURATION * Math.abs(
//mControl.getValue() - target) / GAMMA_SPACE_MAX;
mControl.getValue() - target) / 100;
Log.d(TAG,"animateSliderTo animationDuration:"+animationDuration);
mSliderAnimator.setDuration(animationDuration);
mSliderAnimator.start();
}
/** Factory for creating a {@link VolumeController}. */
public static class Factory {
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
@Inject
public Factory(Context context, BroadcastDispatcher broadcastDispatcher) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
}
/** Create a {@link VolumeController} */
public VolumeController create(ToggleSlider toggleSlider) {
return new VolumeController(mContext, toggleSlider, mBroadcastDispatcher);
}
}
//wangfangchen add
private final class Receiver extends BroadcastReceiver {
public void init() {
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
}
public void destroy() {
mBroadcastDispatcher.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
boolean changed = false;
if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
final int oldLevel = intent
.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
+ " level=" + level + " oldLevel=" + oldLevel);
Log.d(TAG," mBackgroundHandler:"+mBackgroundHandler+" mUpdateSliderRunnable:"+mUpdateSliderRunnable);
long timeNow = System.currentTimeMillis();
long spereteTime=timeNow-timeFlag;
Log.d(TAG," spereteTime:"+spereteTime);
if(spereteTime<100){
Log.d(TAG," time is to short return ");
return ;
}
if(mBackgroundHandler!=null&&mUpdateSliderRunnable!=null){
mBackgroundHandler.post(mUpdateSliderRunnable);
}
}
}
}
boolean onVolumeChangedW(int stream, int flags) {
Log.d(TAG,"onVolumeChangedW stream:"+stream+" flags:"+flags);
return true;
}
private final class W extends Handler {
private static final int VOLUME_CHANGED = 1;
W(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
}
}
}
//wangfangchen end
}
主要实现 步骤分析说明
- 类的导入
//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);
}
});
}
}
- 音量变化监听,从新设置音量值
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
boolean changed = false;
if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
final int oldLevel = intent
.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
+ " level=" + level + " oldLevel=" + oldLevel);
Log.d(TAG," mBackgroundHandler:"+mBackgroundHandler+" mUpdateSliderRunnable:"+mUpdateSliderRunnable);
long timeNow = System.currentTimeMillis();
long spereteTime=timeNow-timeFlag;
Log.d(TAG," spereteTime:"+spereteTime);
if(spereteTime<100){
Log.d(TAG," time is to short return ");
return ;
}
if(mBackgroundHandler!=null&&mUpdateSliderRunnable!=null){
mBackgroundHandler.post(mUpdateSliderRunnable);
}
}
}