frameworks/base/media/java/android/media/AudioService.java //direction 一般取1(音量+),0, -1(音量-) public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage) { adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, Binder.getCallingUid()); } private void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage, int uid) { if (mUseFixedVolume) { return; } if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream="+streamType+", dir="+direction + ", flags="+flags); ensureValidDirection(direction); ensureValidStreamType(streamType); // use stream type alias here so that streams with same alias have the same behavior, // including with regard to silent mode control (e.g the use of STREAM_RING below and in // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION) int streamTypeAlias = mStreamVolumeAlias[streamType]; VolumeStreamState streamState = mStreamStates[streamTypeAlias]; final int device = getDeviceForStream(streamTypeAlias); int aliasIndex = streamState.getIndex(device); boolean adjustVolume = true; int step; // skip a2dp absolute volume control request when the device // is not an a2dp device if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { return; } if (mAppOps.noteOp(STEAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } // reset any pending volume command synchronized (mSafeMediaVolumeState) { mPendingVolumeCommand = null; } flags &= ~AudioManager.FLAG_FIXED_VOLUME; if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && ((device & mFixedVolumeDevices) != 0)) { flags |= AudioManager.FLAG_FIXED_VOLUME; // Always toggle between max safe volume and 0 for fixed volume devices where safe // volume is enforced, and max and 0 for the others. // This is simulated by stepping by the full allowed volume range if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && (device & mSafeMediaVolumeDevices) != 0) { step = mSafeMediaVolumeIndex; } else { step = streamState.getMaxIndex(); } if (aliasIndex != 0) { aliasIndex = step; } } else { // convert one UI step (+/-1) into a number of internal units on the stream alias step = rescaleIndex(10, streamType, streamTypeAlias); } // If either the client forces allowing ringer modes for this adjustment, // or the stream type is one that is affected by ringer modes if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (streamTypeAlias == getMasterStreamType())) { int ringerMode = getRingerMode(); // do not vibrate if already in vibrate mode if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { flags &= ~AudioManager.FLAG_VIBRATE; } // Check if the ringer mode changes with this volume adjustment. If // it does, it will handle adjusting the volume, so we won't below final int result = checkForRingerModeChange(aliasIndex, direction, step); adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0; // If suppressing a volume adjustment in silent mode, display the UI hint if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) { flags |= AudioManager.FLAG_SHOW_SILENT_HINT; } } int oldIndex = mStreamStates[streamType].getIndex(device); if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) { // Check if volume update should be send to AVRCP if (streamTypeAlias == AudioSystem.STREAM_MUSIC && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { synchronized (mA2dpAvrcpLock) { if (mA2dp != null && mAvrcpAbsVolSupported) { mA2dp.adjustAvrcpAbsoluteVolume(direction); } } } if ((direction == AudioManager.ADJUST_RAISE) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { //這里是看是否需要針對特定設備顯示音量警告界面 Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex); mVolumeController.postDisplaySafeVolumeWarning(flags); } else if (streamState.adjustIndex(direction * step, device)) { //這里會存儲音量,並且向底層設置音量 // Post message to set system volume (it in turn will post a message // to persist). Do not change volume if stream is muted. sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, device, 0, streamState, 0); } // Check if volume update should be send to Hdmi system audio. int newIndex = mStreamStates[streamType].getIndex(device); if (mHdmiManager != null) { synchronized (mHdmiManager) { if (mHdmiTvClient != null && streamTypeAlias == AudioSystem.STREAM_MUSIC && (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 && oldIndex != newIndex) { int maxIndex = getStreamMaxVolume(streamType); synchronized (mHdmiTvClient) { if (mHdmiSystemAudioSupported) { mHdmiTvClient.setSystemAudioVolume( (oldIndex + 5) / 10, (newIndex + 5) / 10, maxIndex); } } } // mHdmiCecSink true => mHdmiPlaybackClient != null if (mHdmiCecSink && streamTypeAlias == AudioSystem.STREAM_MUSIC && oldIndex != newIndex) { synchronized (mHdmiPlaybackClient) { int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN : KeyEvent.KEYCODE_VOLUME_UP; mHdmiPlaybackClient.sendKeyEvent(keyCode, true); mHdmiPlaybackClient.sendKeyEvent(keyCode, false); } } } } } int index = mStreamStates[streamType].getIndex(device); sendVolumeUpdate(streamType, oldIndex, index, flags); } //mSafeMediaVolumeState並非一直是SAFE_MEDIA_VOLUME_ACTIVE, 警告界面設置后狀態為SAFE_MEDIA_VOLUME_INACTIVE //會針對音量大於一定值時計時,當時間超過20個小時時,會修改狀態為SAFE_MEDIA_VOLUME_ACTIVE,當再次調節音量, //該音量超過警告值時,會再次彈出警告界面 private boolean checkSafeMediaVolume(int streamType, int index, int device) { synchronized (mSafeMediaVolumeState) { if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && ((device & mSafeMediaVolumeDevices) != 0) && (index > mSafeMediaVolumeIndex)) { return false; } return true; } } // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS. private int mMusicActiveMs; private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval private void onCheckMusicActive() { synchronized (mSafeMediaVolumeState) { if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) { int device = getDeviceForStream(AudioSystem.STREAM_MUSIC); if ((device & mSafeMediaVolumeDevices) != 0) { //循環消息,不停的統計音量超值時間,1分鍾統計一次 sendMsg(mAudioHandler, MSG_CHECK_MUSIC_ACTIVE, SENDMSG_REPLACE, 0, 0, null, MUSIC_ACTIVE_POLL_PERIOD_MS); int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device); if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && (index > mSafeMediaVolumeIndex)) { // Approximate cumulative active music time mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS; if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) { setSafeMediaVolumeEnabled(true); mMusicActiveMs = 0; } saveMusicActiveMs(); } } } } }
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
/** {@inheritDoc} */
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
if (!mSystemBooted) {
// If we have not yet booted, don't let key events do anything.
return 0;
}
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
final int keyCode = event.getKeyCode();
if(isAlarmBoot() && keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN){
return 0;
}
final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;
// If screen is off then we treat the case where the keyguard is open but hidden
// the same as if it were open and in front.
// This will prevent any keys other than the power button from waking the screen
// when the keyguard is hidden by another activity.
final boolean keyguardActive = (mKeyguardDelegate == null ? false :
(interactive ?
mKeyguardDelegate.isShowingAndNotOccluded() :
mKeyguardDelegate.isShowing()));
if (DEBUG_INPUT) {
Log.d(TAG, "interceptKeyTq keycode=" + keyCode
+ " interactive=" + interactive + " keyguardActive=" + keyguardActive
+ " policyFlags=" + Integer.toHexString(policyFlags));
}
// Basic policy based on interactive state.
int result;
boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
|| event.isWakeKey();
if (interactive || (isInjected && !isWakeKey)) {
// When the device is interactive or the key is injected pass the
// key to the application.
result = ACTION_PASS_TO_USER;
isWakeKey = false;
} else if (!interactive && shouldDispatchInputWhenNonInteractive()) {
// If we're currently dozing with the screen on and the keyguard showing, pass the key
// to the application but preserve its wake key status to make sure we still move
// from dozing to fully interactive if we would normally go from off to fully
// interactive.
result = ACTION_PASS_TO_USER;
} else {
// When the screen is off and the key is not injected, determine whether
// to wake the device but don't pass the key to the application.
result = 0;
if (isWakeKey && (!down || !isWakeKeyWhenScreenOff(keyCode))) {
isWakeKey = false;
}
}
// If the key would be handled globally, just return the result, don't worry about special
// key processing.
if (mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) {
if (isWakeKey) {
mPowerManager.wakeUp(event.getEventTime());
}
return result;
}
boolean useHapticFeedback = down
&& (policyFlags & WindowManagerPolicy.FLAG_VIRTUAL) != 0
&& event.getRepeatCount() == 0;
// Handle special keys.
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
if (interactive && !mVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mVolumeDownKeyTriggered = true;
mVolumeDownKeyTime = event.getDownTime();
mVolumeDownKeyConsumedByScreenshotChord = false;
cancelPendingPowerKeyAction();
interceptScreenshotChord();
}
} else {
mVolumeDownKeyTriggered = false;
cancelPendingScreenshotChordAction();
}
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
if (down) {
if (interactive && !mVolumeUpKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mVolumeUpKeyTriggered = true;
cancelPendingPowerKeyAction();
cancelPendingScreenshotChordAction();
}
} else {
mVolumeUpKeyTriggered = false;
cancelPendingScreenshotChordAction();
}
}
if (down) {
TelecomManager telecomManager = getTelecommService();
if (telecomManager != null) {
if (telecomManager.isRinging()) {
// If an incoming call is ringing, either VOLUME key means
// "silence ringer". We handle these keys here, rather than
// in the InCallScreen, to make sure we'll respond to them
// even if the InCallScreen hasn't come to the foreground yet.
// Look for the DOWN event here, to agree with the "fallback"
// behavior in the InCallScreen.
Log.i(TAG, "interceptKeyBeforeQueueing:"
+ " VOLUME key-down while ringing: Silence ringer!");
// Silence the ringer. (It's safe to call this
// even if the ringer has already been silenced.)
telecomManager.silenceRinger();
// And *don't* pass this key thru to the current activity
// (which is probably the InCallScreen.)
result &= ~ACTION_PASS_TO_USER;
break;
}
if (telecomManager.isInCall()
&& (result & ACTION_PASS_TO_USER) == 0) {
// If we are in call but we decided not to pass the key to
// the application, just pass it to the session service.
MediaSessionLegacyHelper.getHelper(mContext)
.sendVolumeKeyEvent(event, false);
break;
}
}
if ((result & ACTION_PASS_TO_USER) == 0) {
// If we aren't passing to the user and no one else
// handled it send it to the session manager to figure
// out.
MediaSessionLegacyHelper.getHelper(mContext)
.sendVolumeKeyEvent(event, true);
break;
}
}
break;
}
case KeyEvent.KEYCODE_ENDCALL:
case KeyEvent.KEYCODE_POWER:
case KeyEvent.KEYCODE_SLEEP:
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
case KeyEvent.KEYCODE_CALL:
case KeyEvent.KEYCODE_BACK:
return result;
}
frameworks/base/media/java/android/media/session/MediaSessionLegacyHelper.java public void sendVolumeKeyEvent(KeyEvent keyEvent, boolean musicOnly) { if (keyEvent == null) { Log.w(TAG, "Tried to send a null key event. Ignoring."); return; } boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN; boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP; int direction = 0; switch (keyEvent.getKeyCode()) { case KeyEvent.KEYCODE_VOLUME_UP: direction = AudioManager.ADJUST_RAISE; break; case KeyEvent.KEYCODE_VOLUME_DOWN: direction = AudioManager.ADJUST_LOWER; break; case KeyEvent.KEYCODE_VOLUME_MUTE: // TODO break; } if ((down || up) && direction != 0) { int flags; // If this is action up we want to send a beep for non-music events if (up) { direction = 0; } if (musicOnly) { // This flag is used when the screen is off to only affect // active media flags = AudioManager.FLAG_ACTIVE_MEDIA_ONLY; } else { // These flags are consistent with the home screen if (up) { flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE; } else { flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE; } } mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags); } } frameworks/base/media/java/android/media/session/MediaSessionManager.java /** * Dispatch an adjust volume request to the system. It will be sent to the * most relevant audio stream or media session. The direction must be one of * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, * {@link AudioManager#ADJUST_SAME}. * * @param suggestedStream The stream to fall back to if there isn't a * relevant stream * @param direction The direction to adjust volume in. * @param flags Any flags to include with the volume change. * @hide */ public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) { try { mService.dispatchAdjustVolume(suggestedStream, direction, flags); } catch (RemoteException e) { Log.e(TAG, "Failed to send adjust volume.", e); } } frameworks/base/services/core/java/com/android/server/media/MediaSessionService.java @Override public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) throws RemoteException { final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { MediaSessionRecord session = mPriorityStack .getDefaultVolumeSession(mCurrentUserId); dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session); } } finally { Binder.restoreCallingIdentity(token); } } private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags, MediaSessionRecord session) { if (DEBUG) { String description = session == null ? null : session.toString(); Log.d(TAG, "Adjusting session " + description + " by " + direction + ". flags=" + flags + ", suggestedStream=" + suggestedStream); } if (session == null) { if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) { if (DEBUG) { Log.d(TAG, "No active session to adjust, skipping media only volume event"); } return; } try { mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags, getContext().getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error adjusting default volume.", e); } } else { session.adjustVolume(direction, flags, getContext().getPackageName(), UserHandle.myUserId(), true); if (session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE && mRvc != null) { try { mRvc.remoteVolumeChanged(session.getControllerBinder(), flags); } catch (Exception e) { Log.wtf(TAG, "Error sending volume change to system UI.", e); } } } }