1 /*
2  * Copyright (C) 2015 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.audio;
18 
19 import android.car.Car;
20 import android.car.CarAppFocusManager;
21 import android.car.CarAppFocusManager.OnAppFocusChangedListener;
22 import android.car.CarAppFocusManager.OnAppFocusOwnershipCallback;
23 import android.car.media.CarAudioManager;
24 import android.content.Context;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageManager;
27 import android.media.AudioAttributes;
28 import android.media.AudioDeviceInfo;
29 import android.media.AudioFocusRequest;
30 import android.media.AudioManager;
31 import android.media.AudioManager.OnAudioFocusChangeListener;
32 import android.media.HwAudioSource;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.Looper;
37 import android.util.Log;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.widget.AdapterView;
42 import android.widget.ArrayAdapter;
43 import android.widget.Button;
44 import android.widget.LinearLayout;
45 import android.widget.RadioGroup;
46 import android.widget.Spinner;
47 import android.widget.TextView;
48 import android.widget.Toast;
49 import android.widget.ToggleButton;
50 
51 import androidx.fragment.app.Fragment;
52 
53 import com.google.android.car.kitchensink.CarEmulator;
54 import com.google.android.car.kitchensink.R;
55 
56 import java.util.ArrayList;
57 import java.util.List;
58 
59 import javax.annotation.concurrent.GuardedBy;
60 
61 public class AudioTestFragment extends Fragment {
62     private static final String TAG = "CAR.AUDIO.KS";
63     private static final boolean DBG = true;
64 
65     // Key for communicating to hall which audio zone has been selected to play
66     private static final String AAE_PARAMETER_KEY_FOR_SELECTED_ZONE =
67             "com.android.car.emulator.selected_zone";
68 
69     private AudioManager mAudioManager;
70     private FocusHandler mAudioFocusHandler;
71     private ToggleButton mEnableMocking;
72 
73     private AudioPlayer mMusicPlayer;
74     @GuardedBy("mLock")
75     private AudioPlayer mMusicPlayerWithDelayedFocus;
76     private AudioPlayer mMusicPlayerShort;
77     private AudioPlayer mNavGuidancePlayer;
78     private AudioPlayer mPhoneAudioPlayer;
79     private AudioPlayer mVrPlayer;
80     private AudioPlayer mSystemPlayer;
81     private AudioPlayer mWavPlayer;
82     private AudioPlayer mMusicPlayerForSelectedDeviceAddress;
83     private HwAudioSource mHwAudioSource;
84     private AudioPlayer[] mAllPlayers;
85 
86     private Handler mHandler;
87     private Context mContext;
88 
89     private Car mCar;
90     private CarAppFocusManager mAppFocusManager;
91     private AudioAttributes mMusicAudioAttrib;
92     private AudioAttributes mNavAudioAttrib;
93     private AudioAttributes mPhoneAudioAttrib;
94     private AudioAttributes mVrAudioAttrib;
95     private AudioAttributes mRadioAudioAttrib;
96     private AudioAttributes mSystemSoundAudioAttrib;
97     private AudioAttributes mMusicAudioAttribForDeviceAddress;
98     private CarEmulator mCarEmulator;
99     private CarAudioManager mCarAudioManager;
100     private Spinner mZoneSpinner;
101     ArrayAdapter<Integer> mZoneAdapter;
102     private Spinner mDeviceAddressSpinner;
103     ArrayAdapter<CarAudioZoneDeviceInfo> mDeviceAddressAdapter;
104     private LinearLayout mDeviceAddressLayout;
105 
106     private final Object mLock = new Object();
107 
108     @GuardedBy("mLock")
109     private AudioFocusRequest mDelayedFocusRequest;
110     private OnAudioFocusChangeListener mMediaWithDelayedFocusListener;
111     private TextView mDelayedStatusText;
112 
113     private final OnAudioFocusChangeListener mNavFocusListener = (focusChange) -> {
114         Log.i(TAG, "Nav focus change:" + focusChange);
115     };
116     private final OnAudioFocusChangeListener mVrFocusListener = (focusChange) -> {
117         Log.i(TAG, "VR focus change:" + focusChange);
118     };
119     private final OnAudioFocusChangeListener mRadioFocusListener = (focusChange) -> {
120         Log.i(TAG, "Radio focus change:" + focusChange);
121     };
122 
123     private final CarAppFocusManager.OnAppFocusOwnershipCallback mOwnershipCallbacks =
124             new OnAppFocusOwnershipCallback() {
125                 @Override
126                 public void onAppFocusOwnershipLost(int focus) {
127                 }
128                 @Override
129                 public void onAppFocusOwnershipGranted(int focus) {
130                 }
131     };
132 
connectCar()133     private void connectCar() {
134         mContext = getContext();
135         mHandler = new Handler(Looper.getMainLooper());
136         mCar = Car.createCar(mContext, /* handler= */ null,
137                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, (car, ready) -> {
138                     if (!ready) {
139                         return;
140                     }
141                     mAppFocusManager =
142                             (CarAppFocusManager) car.getCarManager(Car.APP_FOCUS_SERVICE);
143                     OnAppFocusChangedListener listener = new OnAppFocusChangedListener() {
144                         @Override
145                         public void onAppFocusChanged(int appType, boolean active) {
146                         }
147                     };
148                     mAppFocusManager.addFocusListener(listener,
149                             CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
150                     mAppFocusManager.addFocusListener(listener,
151                             CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND);
152 
153                     mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
154 
155                     handleSetUpZoneSelection();
156 
157                     setUpDeviceAddressPlayer();
158                 });
159     }
160 
161     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle)162     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
163         Log.i(TAG, "onCreateView");
164         View view = inflater.inflate(R.layout.audio, container, false);
165         //Zone Spinner
166         setUpZoneSpinnerView(view);
167 
168         // Device Address layout
169         setUpDeviceAddressLayoutView(view);
170 
171         connectCar();
172         initializePlayers();
173 
174         TextView currentZoneIdTextView = view.findViewById(R.id.activity_current_zone);
175         setActivityCurrentZoneId(currentZoneIdTextView);
176 
177         mAudioManager = (AudioManager) mContext.getSystemService(
178                 Context.AUDIO_SERVICE);
179         mAudioFocusHandler = new FocusHandler(
180                 view.findViewById(R.id.button_focus_request_selection),
181                 view.findViewById(R.id.button_audio_focus_request),
182                 view.findViewById(R.id.text_audio_focus_state));
183         view.findViewById(R.id.button_media_play_start).setOnClickListener(v -> {
184             boolean requestFocus = true;
185             boolean repeat = true;
186             mMusicPlayer.start(requestFocus, repeat, AudioManager.AUDIOFOCUS_GAIN);
187         });
188         view.findViewById(R.id.button_media_play_once).setOnClickListener(v -> {
189             mMusicPlayerShort.start(true, false, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
190             // play only for 1 sec and stop
191             mHandler.postDelayed(() -> mMusicPlayerShort.stop(), 1000);
192         });
193         view.findViewById(R.id.button_media_play_stop).setOnClickListener(v -> mMusicPlayer.stop());
194         view.findViewById(R.id.button_wav_play_start).setOnClickListener(
195                 v -> mWavPlayer.start(true, true, AudioManager.AUDIOFOCUS_GAIN));
196         view.findViewById(R.id.button_wav_play_stop).setOnClickListener(v -> mWavPlayer.stop());
197         view.findViewById(R.id.button_nav_play_once).setOnClickListener(v -> {
198             if (mAppFocusManager == null) {
199                 Log.e(TAG, "mAppFocusManager is null");
200                 return;
201             }
202             if (DBG) {
203                 Log.i(TAG, "Nav start");
204             }
205             mAppFocusManager.requestAppFocus(
206                     CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mOwnershipCallbacks);
207             if (!mNavGuidancePlayer.isPlaying()) {
208                 mNavGuidancePlayer.start(true, false,
209                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
210                         () -> mAppFocusManager.abandonAppFocus(mOwnershipCallbacks,
211                                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
212             }
213         });
214         view.findViewById(R.id.button_vr_play_once).setOnClickListener(v -> {
215             if (mAppFocusManager == null) {
216                 Log.e(TAG, "mAppFocusManager is null");
217                 return;
218             }
219             if (DBG) {
220                 Log.i(TAG, "VR start");
221             }
222             mAppFocusManager.requestAppFocus(
223                     CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, mOwnershipCallbacks);
224             if (!mVrPlayer.isPlaying()) {
225                 mVrPlayer.start(true, false,
226                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
227                         () -> mAppFocusManager.abandonAppFocus(mOwnershipCallbacks,
228                                 CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND));
229             }
230         });
231         view.findViewById(R.id.button_system_play_once).setOnClickListener(v -> {
232             if (DBG) {
233                 Log.i(TAG, "System start");
234             }
235             if (!mSystemPlayer.isPlaying()) {
236                 // system sound played without focus
237                 mSystemPlayer.start(false, false, 0);
238             }
239         });
240         view.findViewById(R.id.button_nav_start).setOnClickListener(v -> handleNavStart());
241         view.findViewById(R.id.button_nav_end).setOnClickListener(v -> handleNavEnd());
242         view.findViewById(R.id.button_vr_start).setOnClickListener(v -> handleVrStart());
243         view.findViewById(R.id.button_vr_end).setOnClickListener(v -> handleVrEnd());
244         view.findViewById(R.id.button_radio_start).setOnClickListener(v -> handleRadioStart());
245         view.findViewById(R.id.button_radio_end).setOnClickListener(v -> handleRadioEnd());
246         view.findViewById(R.id.button_speaker_phone_on).setOnClickListener(
247                 v -> mAudioManager.setSpeakerphoneOn(true));
248         view.findViewById(R.id.button_speaker_phone_off).setOnClickListener(
249                 v -> mAudioManager.setSpeakerphoneOn(false));
250         view.findViewById(R.id.button_microphone_on).setOnClickListener(
251                 v -> mAudioManager.setMicrophoneMute(false));
252         view.findViewById(R.id.button_microphone_off).setOnClickListener(
253                 v -> mAudioManager.setMicrophoneMute(true));
254         final View hwAudioSourceNotFound = view.findViewById(R.id.hw_audio_source_not_found);
255         final View hwAudioSourceStart = view.findViewById(R.id.hw_audio_source_start);
256         final View hwAudioSourceStop = view.findViewById(R.id.hw_audio_source_stop);
257         if (mHwAudioSource == null) {
258             hwAudioSourceNotFound.setVisibility(View.VISIBLE);
259             hwAudioSourceStart.setVisibility(View.GONE);
260             hwAudioSourceStop.setVisibility(View.GONE);
261         } else {
262             hwAudioSourceNotFound.setVisibility(View.GONE);
263             hwAudioSourceStart.setVisibility(View.VISIBLE);
264             hwAudioSourceStop.setVisibility(View.VISIBLE);
265             view.findViewById(R.id.hw_audio_source_start).setOnClickListener(
266                     v -> handleHwAudioSourceStart());
267             view.findViewById(R.id.hw_audio_source_stop).setOnClickListener(
268                     v -> handleHwAudioSourceStop());
269         }
270 
271         mEnableMocking = view.findViewById(R.id.button_mock_audio);
272         mEnableMocking.setOnCheckedChangeListener((buttonView, isChecked) -> {
273             if (mCarEmulator == null) {
274                 //TODO(pavelm): need to do a full switch between emulated and normal mode
275                 // all Car*Manager references should be invalidated.
276                 Toast.makeText(AudioTestFragment.this.getContext(),
277                         "Not supported yet :(", Toast.LENGTH_SHORT).show();
278                 return;
279             }
280             if (isChecked) {
281                 mCarEmulator.start();
282             } else {
283                 mCarEmulator.stop();
284                 mCarEmulator = null;
285             }
286         });
287 
288         // Manage buttons for audio player for device address
289         view.findViewById(R.id.button_device_media_play_start).setOnClickListener(v -> {
290             startDeviceAudio();
291         });
292         view.findViewById(R.id.button_device_media_play_once).setOnClickListener(v -> {
293             startDeviceAudio();
294             // play only for 1 sec and stop
295             mHandler.postDelayed(() -> mMusicPlayerForSelectedDeviceAddress.stop(), 1000);
296         });
297         view.findViewById(R.id.button_device_media_play_stop)
298                 .setOnClickListener(v -> mMusicPlayerForSelectedDeviceAddress.stop());
299 
300         view.findViewById(R.id.media_delayed_focus_start)
301                 .setOnClickListener(v -> handleDelayedMediaStart());
302         view.findViewById(R.id.media_delayed_focus_stop)
303                 .setOnClickListener(v -> handleDelayedMediaStop());
304 
305         view.findViewById(R.id.phone_audio_focus_start)
306                 .setOnClickListener(v -> mPhoneAudioPlayer.start(true, true,
307                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT));
308         view.findViewById(R.id.phone_audio_focus_stop)
309                 .setOnClickListener(v -> mPhoneAudioPlayer.stop());
310 
311         mDelayedStatusText = view.findViewById(R.id.media_delayed_player_status);
312 
313         return view;
314     }
315 
316     @Override
onDestroyView()317     public void onDestroyView() {
318         Log.i(TAG, "onDestroyView");
319         if (mCarEmulator != null) {
320             mCarEmulator.stop();
321         }
322         for (AudioPlayer p : mAllPlayers) {
323             p.stop();
324         }
325         handleHwAudioSourceStop();
326         if (mAudioFocusHandler != null) {
327             mAudioFocusHandler.release();
328             mAudioFocusHandler = null;
329         }
330         if (mAppFocusManager != null) {
331             mAppFocusManager.abandonAppFocus(mOwnershipCallbacks);
332         }
333         if (mCar != null && mCar.isConnected()) {
334             mCar.disconnect();
335             mCar = null;
336         }
337         super.onDestroyView();
338     }
339 
initializePlayers()340     private void initializePlayers() {
341         mMusicAudioAttrib = new AudioAttributes.Builder()
342                 .setUsage(AudioAttributes.USAGE_MEDIA)
343                 .build();
344         mNavAudioAttrib = new AudioAttributes.Builder()
345                 .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
346                 .build();
347         mPhoneAudioAttrib = new AudioAttributes.Builder()
348                 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
349                 .build();
350         mVrAudioAttrib = new AudioAttributes.Builder()
351                 .setUsage(AudioAttributes.USAGE_ASSISTANT)
352                 .build();
353         mRadioAudioAttrib = new AudioAttributes.Builder()
354                 .setUsage(AudioAttributes.USAGE_MEDIA)
355                 .build();
356         mSystemSoundAudioAttrib = new AudioAttributes.Builder()
357                 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
358                 .build();
359         // Create an audio device address audio attribute
360         mMusicAudioAttribForDeviceAddress = new AudioAttributes.Builder()
361                 .setUsage(AudioAttributes.USAGE_MEDIA)
362                 .build();
363 
364 
365         mMusicPlayerForSelectedDeviceAddress = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
366                 mMusicAudioAttribForDeviceAddress);
367         mMusicPlayer = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
368                 mMusicAudioAttrib);
369         mMusicPlayerWithDelayedFocus = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
370                 mMusicAudioAttrib);
371         mMusicPlayerShort = new AudioPlayer(mContext, R.raw.ring_classic_01,
372                 mMusicAudioAttrib);
373         mNavGuidancePlayer = new AudioPlayer(mContext, R.raw.turnright,
374                 mNavAudioAttrib);
375         mPhoneAudioPlayer = new AudioPlayer(mContext, R.raw.free_flight,
376                 mPhoneAudioAttrib);
377         mVrPlayer = new AudioPlayer(mContext, R.raw.one2six,
378                 mVrAudioAttrib);
379         mSystemPlayer = new AudioPlayer(mContext, R.raw.ring_classic_01,
380                 mSystemSoundAudioAttrib);
381         mWavPlayer = new AudioPlayer(mContext, R.raw.free_flight,
382                 mMusicAudioAttrib);
383         final AudioDeviceInfo tuner = findTunerDevice(mContext);
384         if (tuner != null) {
385             mHwAudioSource = new HwAudioSource.Builder()
386                     .setAudioAttributes(mMusicAudioAttrib)
387                     .setAudioDeviceInfo(findTunerDevice(mContext))
388                     .build();
389         }
390         mAllPlayers = new AudioPlayer[] {
391                 mMusicPlayer,
392                 mMusicPlayerShort,
393                 mNavGuidancePlayer,
394                 mVrPlayer,
395                 mSystemPlayer,
396                 mWavPlayer,
397                 mMusicPlayerWithDelayedFocus
398         };
399     }
400 
setActivityCurrentZoneId(TextView currentZoneIdTextView)401     private void setActivityCurrentZoneId(TextView currentZoneIdTextView) {
402         if (mCarAudioManager.isDynamicRoutingEnabled()) {
403             try {
404                 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
405                         mContext.getPackageName(), 0);
406                 int audioZoneId = mCarAudioManager.getZoneIdForUid(info.uid);
407                 currentZoneIdTextView.setText(Integer.toString(audioZoneId));
408             } catch (PackageManager.NameNotFoundException e) {
409                 Log.e(TAG, "setActivityCurrentZoneId Failed to find name: " , e);
410             }
411         }
412     }
413 
handleDelayedMediaStart()414     private void handleDelayedMediaStart() {
415         synchronized (mLock) {
416             if (mDelayedFocusRequest != null) {
417                 return;
418             }
419             mMediaWithDelayedFocusListener = new MediaWithDelayedFocusListener();
420             mDelayedFocusRequest = new AudioFocusRequest
421                     .Builder(AudioManager.AUDIOFOCUS_GAIN)
422                     .setAudioAttributes(mMusicAudioAttrib)
423                     .setOnAudioFocusChangeListener(mMediaWithDelayedFocusListener)
424                     .setForceDucking(false)
425                     .setWillPauseWhenDucked(false)
426                     .setAcceptsDelayedFocusGain(true)
427                     .build();
428             int delayedFocusRequestResults = mAudioManager.requestAudioFocus(mDelayedFocusRequest);
429             if (delayedFocusRequestResults == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
430                 startDelayedMediaPlayerLocked();
431                 return;
432             }
433             if (delayedFocusRequestResults == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
434                 if (DBG) Log.d(TAG, "Media With Delayed Focus delayed focus granted");
435                 mDelayedStatusText.setText(R.string.player_delayed);
436                 return;
437             }
438             mMediaWithDelayedFocusListener = null;
439             mDelayedFocusRequest = null;
440             mDelayedStatusText.setText(R.string.player_not_started);
441         }
442         if (DBG) Log.d(TAG, "Media With Delayed Focus focus rejected");
443     }
444 
startDelayedMediaPlayerLocked()445     private void startDelayedMediaPlayerLocked() {
446         if (!mMusicPlayerWithDelayedFocus.isPlaying()) {
447             if (DBG) Log.d(TAG, "Media With Delayed Focus starting player");
448             mMusicPlayerWithDelayedFocus.start(false, true,
449                     AudioManager.AUDIOFOCUS_GAIN);
450             mDelayedStatusText.setText(R.string.player_started);
451             return;
452         }
453         if (DBG) Log.d(TAG, "Media With Delayed Focus player already started");
454     }
455 
handleDelayedMediaStop()456     private void handleDelayedMediaStop() {
457         synchronized (mLock) {
458             if (mDelayedFocusRequest != null)  {
459                 int requestResults = mAudioManager.abandonAudioFocusRequest(mDelayedFocusRequest);
460                 if (DBG) {
461                     Log.d(TAG, "Media With Delayed Focus abandon focus " + requestResults);
462                 }
463                 mDelayedFocusRequest = null;
464                 mMediaWithDelayedFocusListener = null;
465                 stopDelayedMediaPlayerLocked();
466             }
467         }
468     }
469 
stopDelayedMediaPlayerLocked()470     private void stopDelayedMediaPlayerLocked() {
471         mDelayedStatusText.setText(R.string.player_not_started);
472         if (mMusicPlayerWithDelayedFocus.isPlaying()) {
473             if (DBG) Log.d(TAG, "Media With Delayed Focus stopping player");
474             mMusicPlayerWithDelayedFocus.stop();
475             return;
476         }
477         if (DBG) Log.d(TAG, "Media With Delayed Focus already stopped");
478     }
479 
pauseDelayedMediaPlayerLocked()480     private void pauseDelayedMediaPlayerLocked() {
481         mDelayedStatusText.setText(R.string.player_paused);
482         if (mMusicPlayerWithDelayedFocus.isPlaying()) {
483             if (DBG) Log.d(TAG, "Media With Delayed Focus pausing player");
484             mMusicPlayerWithDelayedFocus.stop();
485             return;
486         }
487         if (DBG) Log.d(TAG, "Media With Delayed Focus already stopped");
488     }
489 
setUpDeviceAddressLayoutView(View view)490     private void setUpDeviceAddressLayoutView(View view) {
491         mDeviceAddressLayout = view.findViewById(R.id.audio_select_device_address_layout);
492 
493         mDeviceAddressSpinner = view.findViewById(R.id.device_address_spinner);
494         mDeviceAddressSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
495             @Override
496             public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
497                 handleDeviceAddressSelection();
498             }
499 
500             @Override
501             public void onNothingSelected(AdapterView<?> parent) {
502             }
503         });
504     }
505 
setUpZoneSpinnerView(View view)506     private void setUpZoneSpinnerView(View view) {
507         mZoneSpinner = view.findViewById(R.id.zone_spinner);
508         mZoneSpinner.setEnabled(false);
509         mZoneSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
510             @Override
511             public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
512                 handleZoneSelection();
513             }
514 
515             @Override
516             public void onNothingSelected(AdapterView<?> parent) {
517             }
518         });
519     }
520 
handleZoneSelection()521     public void handleZoneSelection() {
522         int position = mZoneSpinner.getSelectedItemPosition();
523         int zone = mZoneAdapter.getItem(position);
524         Log.d(TAG, "Zone Selected: " + zone);
525         if (Build.IS_EMULATOR && zone != CarAudioManager.PRIMARY_AUDIO_ZONE) {
526             setZoneToPlayOnSpeaker(zone);
527         }
528     }
529 
handleSetUpZoneSelection()530     private void handleSetUpZoneSelection() {
531         if (!Build.IS_EMULATOR || !mCarAudioManager.isDynamicRoutingEnabled()) {
532             return;
533         }
534         //take care of zone selection
535         List<Integer> zoneList = mCarAudioManager.getAudioZoneIds();
536         Integer[] zoneArray = zoneList.stream()
537                 .filter(i -> i != CarAudioManager.PRIMARY_AUDIO_ZONE).toArray(Integer[]::new);
538         mZoneAdapter = new ArrayAdapter<>(mContext,
539                 android.R.layout.simple_spinner_item, zoneArray);
540         mZoneAdapter.setDropDownViewResource(
541                 android.R.layout.simple_spinner_dropdown_item);
542         mZoneSpinner.setAdapter(mZoneAdapter);
543         mZoneSpinner.setEnabled(true);
544     }
545 
handleNavStart()546     private void handleNavStart() {
547         if (mAppFocusManager == null) {
548             Log.e(TAG, "mAppFocusManager is null");
549             return;
550         }
551         if (DBG) {
552             Log.i(TAG, "Nav start");
553         }
554         mAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
555                 mOwnershipCallbacks);
556         mAudioManager.requestAudioFocus(mNavFocusListener, mNavAudioAttrib,
557                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
558     }
559 
handleNavEnd()560     private void handleNavEnd() {
561         if (mAppFocusManager == null) {
562             Log.e(TAG, "mAppFocusManager is null");
563             return;
564         }
565         if (DBG) {
566             Log.i(TAG, "Nav end");
567         }
568         mAudioManager.abandonAudioFocus(mNavFocusListener, mNavAudioAttrib);
569         mAppFocusManager.abandonAppFocus(mOwnershipCallbacks,
570                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
571     }
572 
findTunerDevice(Context context)573     private AudioDeviceInfo findTunerDevice(Context context) {
574         AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
575         AudioDeviceInfo[] devices = am.getDevices(AudioManager.GET_DEVICES_INPUTS);
576         for (AudioDeviceInfo device : devices) {
577             if (device.getType() == AudioDeviceInfo.TYPE_FM_TUNER) {
578                 return device;
579             }
580         }
581         return null;
582     }
583 
handleHwAudioSourceStart()584     private void handleHwAudioSourceStart() {
585         if (mHwAudioSource != null) {
586             mHwAudioSource.start();
587         }
588     }
589 
handleHwAudioSourceStop()590     private void handleHwAudioSourceStop() {
591         if (mHwAudioSource != null) {
592             mHwAudioSource.stop();
593         }
594     }
595 
handleVrStart()596     private void handleVrStart() {
597         if (mAppFocusManager == null) {
598             Log.e(TAG, "mAppFocusManager is null");
599             return;
600         }
601         if (DBG) {
602             Log.i(TAG, "VR start");
603         }
604         mAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND,
605                 mOwnershipCallbacks);
606         mAudioManager.requestAudioFocus(mVrFocusListener, mVrAudioAttrib,
607                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
608     }
609 
handleVrEnd()610     private void handleVrEnd() {
611         if (mAppFocusManager == null) {
612             Log.e(TAG, "mAppFocusManager is null");
613             return;
614         }
615         if (DBG) {
616             Log.i(TAG, "VR end");
617         }
618         mAudioManager.abandonAudioFocus(mVrFocusListener, mVrAudioAttrib);
619         mAppFocusManager.abandonAppFocus(mOwnershipCallbacks,
620                 CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND);
621     }
622 
handleRadioStart()623     private void handleRadioStart() {
624         if (DBG) {
625             Log.i(TAG, "Radio start");
626         }
627         mAudioManager.requestAudioFocus(mRadioFocusListener, mRadioAudioAttrib,
628                 AudioManager.AUDIOFOCUS_GAIN, 0);
629     }
630 
handleRadioEnd()631     private void handleRadioEnd() {
632         if (DBG) {
633             Log.i(TAG, "Radio end");
634         }
635         mAudioManager.abandonAudioFocus(mRadioFocusListener, mRadioAudioAttrib);
636     }
637 
setUpDeviceAddressPlayer()638     private void setUpDeviceAddressPlayer() {
639         if (!mCarAudioManager.isDynamicRoutingEnabled()) {
640             mDeviceAddressLayout.setVisibility(View.GONE);
641             return;
642         }
643         mDeviceAddressLayout.setVisibility(View.VISIBLE);
644         List<CarAudioZoneDeviceInfo> deviceList = new ArrayList<>();
645         for (int audioZoneId: mCarAudioManager.getAudioZoneIds()) {
646             AudioDeviceInfo deviceInfo = mCarAudioManager
647                     .getOutputDeviceForUsage(audioZoneId, AudioAttributes.USAGE_MEDIA);
648             CarAudioZoneDeviceInfo carAudioZoneDeviceInfo = new CarAudioZoneDeviceInfo();
649             carAudioZoneDeviceInfo.mDeviceInfo = deviceInfo;
650             carAudioZoneDeviceInfo.mAudioZoneId = audioZoneId;
651             deviceList.add(carAudioZoneDeviceInfo);
652             if (DBG) {
653                 Log.d(TAG, "Found device address"
654                         + carAudioZoneDeviceInfo.mDeviceInfo.getAddress()
655                         + " for audio zone id " + audioZoneId);
656             }
657 
658         }
659 
660         CarAudioZoneDeviceInfo[] deviceArray =
661                 deviceList.stream().toArray(CarAudioZoneDeviceInfo[]::new);
662         mDeviceAddressAdapter = new ArrayAdapter<>(mContext,
663                 android.R.layout.simple_spinner_item, deviceArray);
664         mDeviceAddressAdapter.setDropDownViewResource(
665                 android.R.layout.simple_spinner_dropdown_item);
666         mDeviceAddressSpinner.setAdapter(mDeviceAddressAdapter);
667         createDeviceAddressAudioPlayer();
668     }
669 
createDeviceAddressAudioPlayer()670     private void createDeviceAddressAudioPlayer() {
671         CarAudioZoneDeviceInfo carAudioZoneDeviceInfo = mDeviceAddressAdapter.getItem(
672                 mDeviceAddressSpinner.getSelectedItemPosition());
673         Log.d(TAG, "Setting Bundle to zone " + carAudioZoneDeviceInfo.mAudioZoneId);
674         Bundle bundle = new Bundle();
675         bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,
676                 carAudioZoneDeviceInfo.mAudioZoneId);
677         mMusicAudioAttribForDeviceAddress = new AudioAttributes.Builder()
678                 .setUsage(AudioAttributes.USAGE_MEDIA)
679                 .addBundle(bundle)
680                 .build();
681 
682         mMusicPlayerForSelectedDeviceAddress = new AudioPlayer(mContext,
683                 R.raw.well_worth_the_wait,
684                 mMusicAudioAttribForDeviceAddress,
685                 carAudioZoneDeviceInfo.mDeviceInfo);
686     }
687 
startDeviceAudio()688     private void startDeviceAudio() {
689         Log.d(TAG, "Starting device address audio");
690         mMusicPlayerForSelectedDeviceAddress.start(true, false,
691                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
692     }
693 
handleDeviceAddressSelection()694     public void handleDeviceAddressSelection() {
695         if (mMusicPlayerForSelectedDeviceAddress != null
696                 && mMusicPlayerForSelectedDeviceAddress.isPlaying()) {
697             mMusicPlayerForSelectedDeviceAddress.stop();
698         }
699         createDeviceAddressAudioPlayer();
700     }
701 
702     /**
703      * Sets the left speaker to output sound from zoneId
704      * @param zoneId zone id to set left speakers output
705      * @Note this should only be used with emulator where the zones are separated into right
706      * and left speaker, other platforms would have real devices where audio is routed.
707      */
setZoneToPlayOnSpeaker(int zoneId)708     private void setZoneToPlayOnSpeaker(int zoneId) {
709         String selectedZoneKeyValueString = AAE_PARAMETER_KEY_FOR_SELECTED_ZONE + "=" + zoneId;
710         // send key value  parameter list to audio HAL
711         mAudioManager.setParameters(selectedZoneKeyValueString);
712         Log.d(TAG, "setZoneToPlayOnSpeaker : " + zoneId);
713     }
714 
715 
716     private class FocusHandler {
717         private static final String AUDIO_FOCUS_STATE_GAIN = "gain";
718         private static final String AUDIO_FOCUS_STATE_RELEASED_UNKNOWN = "released / unknown";
719 
720         private final RadioGroup mRequestSelection;
721         private final TextView mText;
722         private final AudioFocusListener mFocusListener;
723         private AudioFocusRequest mFocusRequest;
724 
FocusHandler(RadioGroup radioGroup, Button requestButton, TextView text)725         public FocusHandler(RadioGroup radioGroup, Button requestButton, TextView text) {
726             mText = text;
727             mRequestSelection = radioGroup;
728             mRequestSelection.check(R.id.focus_gain);
729             setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
730             mFocusListener = new AudioFocusListener();
731             requestButton.setOnClickListener(v -> {
732                 int selectedButtonId = mRequestSelection.getCheckedRadioButtonId();
733                 int focusRequest;
734                 switch (selectedButtonId) {
735                     case R.id.focus_gain:
736                         focusRequest = AudioManager.AUDIOFOCUS_GAIN;
737                         break;
738                     case R.id.focus_gain_transient:
739                         focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
740                         break;
741                     case R.id.focus_gain_transient_duck:
742                         focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
743                         break;
744                     case R.id.focus_gain_transient_exclusive:
745                         focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
746                         break;
747                     case R.id.focus_release:
748                     default:
749                         abandonAudioFocus();
750                         return;
751                 }
752                 mFocusRequest = new AudioFocusRequest.Builder(focusRequest)
753                         .setAudioAttributes(new AudioAttributes.Builder()
754                                 .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
755                                 .build())
756                         .setOnAudioFocusChangeListener(mFocusListener)
757                         .build();
758                 int ret = mAudioManager.requestAudioFocus(mFocusRequest);
759                 Log.i(TAG, "requestAudioFocus returned " + ret);
760                 if (ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
761                     setFocusText(AUDIO_FOCUS_STATE_GAIN);
762                 }
763             });
764         }
765 
release()766         public void release() {
767             abandonAudioFocus();
768         }
769 
abandonAudioFocus()770         private void abandonAudioFocus() {
771             if (DBG) {
772                 Log.i(TAG, "abandonAudioFocus");
773             }
774             if (mFocusRequest != null) {
775                 mAudioManager.abandonAudioFocusRequest(mFocusRequest);
776                 mFocusRequest = null;
777             } else {
778                 Log.i(TAG, "mFocusRequest is already null");
779             }
780             setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
781         }
782 
setFocusText(String msg)783         private void setFocusText(String msg) {
784             mText.setText("focus state:" + msg);
785         }
786 
787         private class AudioFocusListener implements OnAudioFocusChangeListener {
788             @Override
onAudioFocusChange(int focusChange)789             public void onAudioFocusChange(int focusChange) {
790                 Log.i(TAG, "onAudioFocusChange " + focusChange);
791                 if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
792                     setFocusText(AUDIO_FOCUS_STATE_GAIN);
793                 } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
794                     setFocusText("loss");
795                 } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
796                     setFocusText("loss,transient");
797                 } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
798                     setFocusText("loss,transient,duck");
799                 }
800             }
801         }
802     }
803 
804     private final class MediaWithDelayedFocusListener implements OnAudioFocusChangeListener {
805         @Override
onAudioFocusChange(int focusChange)806         public void onAudioFocusChange(int focusChange) {
807             if (DBG) Log.d(TAG, "Media With Delayed Focus focus change:" + focusChange);
808             synchronized (mLock) {
809                 switch (focusChange) {
810                     case AudioManager.AUDIOFOCUS_GAIN:
811                     case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
812                     case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
813                     case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
814                         startDelayedMediaPlayerLocked();
815                         break;
816                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
817                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
818                         pauseDelayedMediaPlayerLocked();
819                         break;
820                     case AudioManager.AUDIOFOCUS_LOSS:
821                     default:
822                         stopDelayedMediaPlayerLocked();
823                         mDelayedFocusRequest = null;
824                         mMediaWithDelayedFocusListener = null;
825                         break;
826                 }
827             }
828         }
829     }
830 
831     private class CarAudioZoneDeviceInfo {
832         AudioDeviceInfo mDeviceInfo;
833         int mAudioZoneId;
834 
835         @Override
toString()836         public String toString() {
837             StringBuilder builder = new StringBuilder();
838             builder.append("Device Address : ");
839             builder.append(mDeviceInfo.getAddress());
840             builder.append(", Audio Zone Id: ");
841             builder.append(mAudioZoneId);
842             return builder.toString();
843         }
844     }
845 }
846