blob: 90445cd1913deabf8c8ddeba7997ce10136c6eba [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chromecast.shell;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.chromecast.base.Controller;
import org.chromium.chromecast.base.Observable;
import org.chromium.chromecast.base.Unit;
/**
* Wrapper for Cast code to use a single AudioManager instance.
* Muting and unmuting streams must be invoke on the same AudioManager instance.
*/
public class CastAudioManager {
private static final String TAG = "CastAudioManager";
// TODO(sanfin): This class should encapsulate SDK-dependent implementation details of
// android.media.AudioManager.
private static CastAudioManager sInstance = null;
public static CastAudioManager getAudioManager(Context context) {
if (sInstance == null) {
sInstance = new CastAudioManager(
(AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
}
return sInstance;
}
private final AudioManager mAudioManager;
@VisibleForTesting
CastAudioManager(AudioManager audioManager) {
mAudioManager = audioManager;
}
/**
* Requests audio focus whenever the given Observable is activated.
*
* Returns an Observable that is activated whenever the audio focus is granted.
*
* TODO(sanfin): Distinguish between transient, ducking, and full audio focus losses.
*/
public Observable<Unit> requestAudioFocusWhen(
Observable<?> event, int streamType, int durationHint) {
Controller<Unit> audioFocusState = new Controller<>();
event.watch(() -> {
AudioManager.OnAudioFocusChangeListener listener = (int focusChange) -> {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
audioFocusState.set(Unit.unit());
return;
default:
audioFocusState.reset();
return;
}
};
// Request audio focus when the source event is activated.
if (requestAudioFocus(listener, streamType, durationHint)
!= AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.e(TAG, "Failed to get audio focus");
}
// Abandon audio focus when the source event is deactivated.
return () -> {
if (abandonAudioFocus(listener) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.e(TAG, "Failed to abandon audio focus");
}
audioFocusState.reset();
};
});
return audioFocusState;
}
// Only called on Lollipop and below, in an Activity's onPause() event.
// On Lollipop and below, setStreamMute() calls are cumulative and per-application, and if
// Activities don't unmute the streams that they mute, the stream remains muted to other
// applications, which are unable to unmute the stream themselves. Therefore, when an Activity
// is paused, it must unmute any streams it had muted.
// More context in b/19964892 and b/22204758.
@SuppressWarnings("deprecation")
public void releaseStreamMuteIfNecessary(int streamType) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
// On L, if we try to unmute a stream that is not muted, a warning Toast appears.
// Check the stream mute state to determine whether to unmute.
boolean isMuted = false;
try {
// isStreamMute() was only made public in M, but it can be accessed through
// reflection in L.
isMuted = (Boolean) mAudioManager.getClass()
.getMethod("isStreamMute", int.class)
.invoke(mAudioManager, streamType);
} catch (Exception e) {
Log.e(TAG, "Can not call AudioManager.isStreamMute().", e);
}
if (isMuted) {
// Note: this is a no-op on fixed-volume devices.
mAudioManager.setStreamMute(streamType, false);
}
}
}
// TODO(sanfin): Use the AudioFocusRequest version on O and above.
@SuppressWarnings("deprecation")
public int requestAudioFocus(
AudioManager.OnAudioFocusChangeListener l, int streamType, int durationHint) {
return mAudioManager.requestAudioFocus(l, streamType, durationHint);
}
// TODO(sanfin): Use the AudioFocusRequest version on O and above.
@SuppressWarnings("deprecation")
public int abandonAudioFocus(AudioManager.OnAudioFocusChangeListener l) {
return mAudioManager.abandonAudioFocus(l);
}
public int getStreamMaxVolume(int streamType) {
return mAudioManager.getStreamMaxVolume(streamType);
}
// TODO(sanfin): Do not expose this. All needed AudioManager methods can be adapted with
// CastAudioManager.
public AudioManager getInternal() {
return mAudioManager;
}
}