1.AudioService::adjustStreamVolume
private void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
......
//finally update notify systemui to update
int index = mStreamStates[streamType].getIndex(device);
sendVolumeUpdate(streamType, oldIndex, index, flags);
2.flags一定要是奇数才能显示UI,对应Audiomanager 中public static final int FLAG_SHOW_UI = 1 << 0;
// UI update and Broadcast Intent
protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
streamType = mStreamVolumeAlias[streamType];
if (streamType == AudioSystem.STREAM_MUSIC) {
flags = updateFlagsForSystemAudio(flags);
}
mVolumeController.postVolumeChanged(streamType, flags);
}
3.mVolumeController是AudioService的内部类VolumeController,封装了VolumeDialogControllerImpl,也就是mController,这是SystemUI启动的时候通过AudioManager的setVolumeController设置的。
public void postVolumeChanged(int streamType, int flags) {
if (mController == null)
return;
try {
mController.volumeChanged(streamType, flags);
} catch (RemoteException e) {
Log.w(TAG, "Error calling volumeChanged", e);
}
}
4.SystemUI VolumeDialogControllerImpl内部类的VC
private final class VC extends IVolumeController.Stub {
...
@Override
public void volumeChanged(int streamType, int flags) throws RemoteException {
if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType)
+ " " + Util.audioManagerFlagsToString(flags));
if (mDestroyed) return;
mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
}
5.业务处理也是VolumeDialogControllerImpl内部类Handler继承类W做的
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
6.showUI是音量条开关,值是1,fromKey就是key事件启动的,值是4096。updateActiveStreamw是更新mState中的active stream,lastAudibleStreamVolume是从AudioService去取对应stream的音量值,updateStreamLevelW是将这个值保存下来。关键是mCallbacks.onStateChanged和mCallbacks.onShowRequested。对于盒子而言要关注的Callback是VolumeDialogImpl,onStateChanged会更新音量条要显示那些stream的条及对应大小值,onShowRequested则会请求显示音量条或者更新dismiss的时间。
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) {
mCallbacks.onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
}
if (showVibrateHint) {
mCallbacks.onShowVibrateHint();
}
if (showSilentHint) {
mCallbacks.onShowSilentHint();
}
if (changed && fromKey) {
Events.writeEvent(mContext, Events.EVENT_KEY, stream, lastAudibleStreamVolume);
}
return changed;
}
7.VolumeDialogImpl,主要是对各个stream进行UI的更新。
private void onStateChangedH(State state) {
final boolean animating = mMotion.isAnimating();
if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating);
mState = state;
if (animating) {
mPendingStateChanged = true;
return;
}
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,
true);
}
}
if (mActiveStream != state.activeStream) {
mPrevActiveStream = mActiveStream;
mActiveStream = state.activeStream;
updateRowsH(getActiveRow());
rescheduleTimeoutH();
}
for (VolumeRow row : mRows) {
updateVolumeRowH(row);
}
updateFooterH();
}
8.Volume dialog显示这块逻辑的日志和events打印很足,非常方便debug,写的真好。每次都是先移除之前的SHOW和DISMISS message,然后定时dismiss。真要显示会调用mMotion.startShow,还会通知AudioService。
private void showH(int reason) {
if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
mHandler.removeMessages(H.SHOW);
mHandler.removeMessages(H.DISMISS);
rescheduleTimeoutH();
if (mShowing) return;
mShowing = true;
mMotion.startShow();
Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
mController.notifyVisible(true);
}
9.定时dismiss,mController.userActivity最终会调到PMS的userActivity来更新下一次休眠时间。
protected void rescheduleTimeoutH() {
mHandler.removeMessages(H.DISMISS);
final int timeout = computeTimeoutH();
mHandler.sendMessageDelayed(mHandler
.obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
mController.userActivity();
}
10.VolumeDialogMotion的show和dismiss,主要是涉及到animation的处理,要考虑到正在显示showing或者dismissing animation的情况。startShow如果正在mDismissing的状态会取消掉dismissing anmiation,但这里有个bug,大家一看便知,会导致dismiss最终还是会发生,并且是在dialog show之后,引起音量条不显示。
public void startShow() {
if (D.BUG) Log.d(TAG, "startShow");
if (mShowing) return;
setShowing(true);
if (mDismissing) {
mDialogView.animate().cancel();
setDismissing(false);
startShowAnimation();
return;
}
if (D.BUG) Log.d(TAG, "mDialog.show()");
mDialog.show();
}
public void startDismiss(final Runnable onComplete) {
if (D.BUG) Log.d(TAG, "startDismiss");
if (mDismissing) return;
setDismissing(true);
if (mShowing) {
mDialogView.animate().cancel();
if (mContentsPositionAnimator != null) {
mContentsPositionAnimator.cancel();
}
mContents.animate().cancel();
if (mChevronPositionAnimator != null) {
mChevronPositionAnimator.cancel();
}
mChevron.animate().cancel();
setShowing(false);
}
mDialogView.animate()
.translationY(-mDialogView.getHeight())
.setDuration(scaledDuration(250))
.setInterpolator(new LogAccelerateInterpolator())
.setUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mContents.setTranslationY(-mDialogView.getTranslationY());
final int posY = chevronPosY();
mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
}
})
.setListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
public void onAnimationEnd(Animator animation) {
if (mCancelled) return;
if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
mDialog.dismiss();
onComplete.run();
setDismissing(false);
}
}, PRE_DISMISS_DELAY);
}
@Override
public void onAnimationCancel(Animator animation) {
if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
mCancelled = true;
}
}).start();
}