1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tv.audio; 17 18 import android.app.Activity; 19 import android.content.Context; 20 import android.media.AudioAttributes; 21 import android.media.AudioFocusRequest; 22 import android.media.AudioManager; 23 import android.os.Build; 24 import android.support.annotation.Nullable; 25 import com.android.tv.features.TvFeatures; 26 import com.android.tv.ui.api.TunableTvViewPlayingApi; 27 28 /** A helper class to help {@code Activities} to handle audio-related stuffs. */ 29 public class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener { 30 private static final float AUDIO_MAX_VOLUME = 1.0f; 31 private static final float AUDIO_MIN_VOLUME = 0.0f; 32 private static final float AUDIO_DUCKING_VOLUME = 0.3f; 33 34 private final Activity mActivity; 35 private final TunableTvViewPlayingApi mTvView; 36 private final AudioManager mAudioManager; 37 @Nullable private final AudioFocusRequest mFocusRequest; 38 39 private int mAudioFocusStatus = AudioManager.AUDIOFOCUS_NONE; 40 AudioManagerHelper(Activity activity, TunableTvViewPlayingApi tvView)41 public AudioManagerHelper(Activity activity, TunableTvViewPlayingApi tvView) { 42 mActivity = activity; 43 mTvView = tvView; 44 mAudioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); 45 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 46 mFocusRequest = 47 new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) 48 .setAudioAttributes( 49 new AudioAttributes.Builder() 50 .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) 51 .setUsage(AudioAttributes.USAGE_MEDIA) 52 .build()) 53 .setOnAudioFocusChangeListener(this) 54 // Auto ducking from the system does not mute the TV Input Service. 55 // Using will pause when ducked allows us to set the stream volume 56 // even when we are not pausing. 57 .setWillPauseWhenDucked(true) 58 .build(); 59 } else { 60 mFocusRequest = null; 61 } 62 } 63 64 /** 65 * Sets suitable volume to {@link TunableTvViewPlayingApi} according to the current audio focus. 66 * 67 * <p>If the focus status is {@link AudioManager#AUDIOFOCUS_LOSS} or {@link 68 * AudioManager#AUDIOFOCUS_NONE} and the activity is under PIP mode, this method will finish the 69 * activity. Sets suitable volume to {@link TunableTvViewPlayingApi} according to the current 70 * audio focus. If the focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is 71 * under PIP mode, this method will finish the activity. 72 */ setVolumeByAudioFocusStatus()73 public void setVolumeByAudioFocusStatus() { 74 if (mTvView.isPlaying()) { 75 switch (mAudioFocusStatus) { 76 case AudioManager.AUDIOFOCUS_GAIN: 77 if (mTvView.isTimeShiftAvailable()) { 78 mTvView.timeShiftPlay(); 79 } else { 80 mTvView.setStreamVolume(AUDIO_MAX_VOLUME); 81 } 82 break; 83 case AudioManager.AUDIOFOCUS_NONE: 84 case AudioManager.AUDIOFOCUS_LOSS: 85 if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(mActivity) 86 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N 87 && mActivity.isInPictureInPictureMode()) { 88 mActivity.finish(); 89 break; 90 } 91 // fall through 92 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 93 if (mTvView.isTimeShiftAvailable()) { 94 mTvView.timeShiftPause(); 95 } else { 96 mTvView.setStreamVolume(AUDIO_MIN_VOLUME); 97 } 98 break; 99 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 100 if (mTvView.isTimeShiftAvailable()) { 101 mTvView.timeShiftPause(); 102 } else { 103 mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME); 104 } 105 break; 106 } 107 } 108 } 109 110 /** 111 * Tries to request audio focus from {@link AudioManager} and set volume according to the 112 * returned result. 113 */ requestAudioFocus()114 public void requestAudioFocus() { 115 int result; 116 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 117 result = mAudioManager.requestAudioFocus(mFocusRequest); 118 } else { 119 result = 120 mAudioManager.requestAudioFocus( 121 this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 122 } 123 mAudioFocusStatus = 124 (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) 125 ? AudioManager.AUDIOFOCUS_GAIN 126 : AudioManager.AUDIOFOCUS_LOSS; 127 setVolumeByAudioFocusStatus(); 128 } 129 130 /** Abandons audio focus. */ abandonAudioFocus()131 public void abandonAudioFocus() { 132 mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; 133 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 134 mAudioManager.abandonAudioFocusRequest(mFocusRequest); 135 } else { 136 mAudioManager.abandonAudioFocus(this); 137 } 138 } 139 140 @Override onAudioFocusChange(int focusChange)141 public void onAudioFocusChange(int focusChange) { 142 mAudioFocusStatus = focusChange; 143 setVolumeByAudioFocusStatus(); 144 } 145 } 146