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