1 /*
2  * Copyright (C) 2021 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 
17 package com.google.android.car.kitchensink;
18 
19 import static android.car.Car.APP_FOCUS_SERVICE;
20 import static android.car.Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER;
21 import static android.car.Car.createCar;
22 import static android.car.CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION;
23 import static android.car.CarAppFocusManager.OnAppFocusChangedListener;
24 import static android.car.media.CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING;
25 import static android.media.AudioManager.AUDIOFOCUS_GAIN;
26 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
27 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
28 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
29 import static android.media.AudioManager.AUDIOFOCUS_LOSS;
30 import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
31 import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
32 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
33 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED;
34 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
35 import static android.util.Log.DEBUG;
36 
37 import android.app.Activity;
38 import android.car.Car;
39 import android.car.CarAppFocusManager;
40 import android.car.media.CarAudioManager;
41 import android.content.pm.ApplicationInfo;
42 import android.content.pm.PackageManager;
43 import android.content.res.AssetFileDescriptor;
44 import android.media.AudioAttributes;
45 import android.media.AudioFocusRequest;
46 import android.media.AudioManager;
47 import android.media.AudioManager.OnAudioFocusChangeListener;
48 import android.media.MediaPlayer;
49 import android.os.Bundle;
50 import android.os.Handler;
51 import android.os.Looper;
52 import android.util.Log;
53 import android.widget.TextView;
54 
55 import java.io.IOException;
56 
57 import javax.annotation.concurrent.GuardedBy;
58 
59 public final class AudioAutoStartActivity extends Activity {
60 
61     private static final String TAG = "CAR.AUDIO.KS.AUTO_START";
62     private static final AudioAttributes MUSIC_AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
63             .setUsage(AudioAttributes.USAGE_MEDIA)
64             .build();
65 
66     private Handler mHandler;
67     private Car mCar;
68     private CarAppFocusManager mAppFocusManager;
69     private CarAudioManager mCarAudioManager;
70     private AudioManager mAudioManager;
71 
72     private final Object mLock = new Object();
73     @GuardedBy("mLock")
74     private AudioFocusRequest mFocusRequest;
75     @GuardedBy("mLock")
76     private OnAudioFocusChangeListener mMediaFocusListener;
77     @GuardedBy("mLock")
78     private MediaPlayer mPlayer;
79     private TextView mPlayerStatusText;
80 
81     @Override
onCreate(Bundle savedInstanceState)82     protected void onCreate(Bundle savedInstanceState) {
83         super.onCreate(savedInstanceState);
84         connectCar();
85         setContentView(R.layout.audio_auto_start_activity);
86 
87         mAudioManager = getSystemService(AudioManager.class);
88 
89         TextView currentZoneIdTextView = findViewById(R.id.activity_current_zone);
90         setActivityCurrentZoneId(currentZoneIdTextView);
91 
92         mPlayerStatusText = findViewById(R.id.player_status);
93 
94         requestAudioFocus();
95     }
96 
connectCar()97     private void connectCar() {
98         mHandler = new Handler(Looper.getMainLooper(), null, true);
99         mCar = createCar(this, null,
100                 CAR_WAIT_TIMEOUT_WAIT_FOREVER, (car, ready) -> {
101                     if (!ready) {
102                         return;
103                     }
104                     mAppFocusManager =
105                             (CarAppFocusManager) car.getCarManager(APP_FOCUS_SERVICE);
106                     mAppFocusManager.addFocusListener(
107                             (OnAppFocusChangedListener) (appType, active) -> {
108 
109                             },
110                             APP_FOCUS_TYPE_NAVIGATION);
111 
112                     mCarAudioManager = (CarAudioManager) car.getCarManager(AUDIO_SERVICE);
113                 });
114     }
115 
setActivityCurrentZoneId(TextView currentZoneIdTextView)116     private void setActivityCurrentZoneId(TextView currentZoneIdTextView) {
117         if (mCarAudioManager.isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)) {
118             try {
119                 ApplicationInfo info = getPackageManager().getApplicationInfo(
120                         getPackageName(), 0);
121                 int audioZoneId = mCarAudioManager.getZoneIdForUid(info.uid);
122                 currentZoneIdTextView.setText(Integer.toString(audioZoneId));
123             } catch (PackageManager.NameNotFoundException e) {
124                 Log.e(TAG, "setActivityCurrentZoneId Failed to find package name "
125                         + getPackageName(), e);
126             }
127         }
128     }
129 
requestAudioFocus()130     private void requestAudioFocus() {
131         int delayedFocusRequestResults;
132         synchronized (mLock) {
133             if (mFocusRequest != null) {
134                 return;
135             }
136             mMediaFocusListener = new AutoStartFocusListener();
137             mFocusRequest = new AudioFocusRequest
138                     .Builder(AUDIOFOCUS_GAIN)
139                     .setAudioAttributes(MUSIC_AUDIO_ATTRIBUTES)
140                     .setOnAudioFocusChangeListener(mMediaFocusListener)
141                     .setForceDucking(false)
142                     .setWillPauseWhenDucked(false)
143                     .setAcceptsDelayedFocusGain(true)
144                     .build();
145             delayedFocusRequestResults = mAudioManager.requestAudioFocus(mFocusRequest);
146         }
147 
148         switch (delayedFocusRequestResults) {
149             case AUDIOFOCUS_REQUEST_GRANTED:
150                 if (Log.isLoggable(TAG, DEBUG)) {
151                     Log.d(TAG, "Audio Auto Start Player granted");
152                 }
153                 mHandler.post(() -> startMediaPlayer());
154                 break;
155             case AUDIOFOCUS_REQUEST_DELAYED:
156                 if (Log.isLoggable(TAG, DEBUG)) {
157                     Log.d(TAG, "Media With Delayed Focus delayed");
158                 }
159                 mHandler.post(() -> delayMediaPlayer());
160                 break;
161             case AUDIOFOCUS_REQUEST_FAILED:
162             default:
163                 if (Log.isLoggable(TAG, DEBUG)) {
164                     Log.d(TAG, "Audio Auto Start Player rejected");
165                 }
166                 mHandler.post(()  -> stopMediaPlayer());
167                 synchronized (mLock) {
168                     mMediaFocusListener = null;
169                     mFocusRequest = null;
170                 }
171         }
172     }
173 
createPlayer()174     private MediaPlayer createPlayer() throws IOException {
175         MediaPlayer mediaPlayer = new MediaPlayer();
176         AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.testmp3_2);
177         mediaPlayer.setAudioAttributes(MUSIC_AUDIO_ATTRIBUTES);
178         mediaPlayer.setDataSource(afd);
179         mediaPlayer.setLooping(true);
180         mediaPlayer.setVolume(1.0f, 1.0f);
181         mediaPlayer.prepare();
182         return mediaPlayer;
183     }
184 
stopMediaPlayer()185     private void stopMediaPlayer() {
186         synchronized (mLock) {
187             mPlayerStatusText.setText(R.string.player_stopped);
188             if (mPlayer == null) {
189                 Log.i(TAG, "Can not stop non existent player");
190                 return;
191             }
192             mPlayer.stop();
193             mPlayer.release();
194             mPlayer = null;
195         }
196     }
197 
pauseMediaPlayer()198     private void pauseMediaPlayer() {
199         synchronized (mLock) {
200             mPlayerStatusText.setText(R.string.player_paused);
201             if (mPlayer == null) {
202                 Log.i(TAG, "Can not pause non existent player");
203                 return;
204             }
205             mPlayer.pause();
206         }
207     }
208 
startMediaPlayer()209     private void startMediaPlayer() {
210         synchronized (mLock) {
211             if (mPlayer == null) {
212                 try {
213                     mPlayer = createPlayer();
214                 } catch (IOException e) {
215                     Log.e(TAG, "Can not media start player", e);
216                     return;
217                 }
218             }
219             mPlayer.start();
220             mPlayerStatusText.setText(R.string.player_started);
221         }
222     }
223 
delayMediaPlayer()224     private void delayMediaPlayer() {
225         synchronized (mLock) {
226             mPlayerStatusText.setText(R.string.player_delayed);
227         }
228     }
229 
230     private class AutoStartFocusListener implements OnAudioFocusChangeListener {
231 
232         @Override
onAudioFocusChange(int focusChange)233         public void onAudioFocusChange(int focusChange) {
234             if (Log.isLoggable(TAG, DEBUG)) {
235                 Log.d(TAG, "Audio Auto Start Player Focus change:" + focusChange);
236             }
237             switch (focusChange) {
238                 case AUDIOFOCUS_GAIN:
239                 case AUDIOFOCUS_GAIN_TRANSIENT:
240                 case AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
241                 case AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
242                     mHandler.post(() -> startMediaPlayer());
243                     return;
244                 case AUDIOFOCUS_LOSS_TRANSIENT:
245                 case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
246                     mHandler.post(() -> pauseMediaPlayer());
247                     return;
248                 case AUDIOFOCUS_LOSS:
249                 default:
250                     mHandler.post(() -> stopMediaPlayer());
251                     synchronized (mLock) {
252                         mFocusRequest = null;
253                         mMediaFocusListener = null;
254                     }
255                     return;
256             }
257         }
258     }
259 }
260