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.hardware.display.DisplayManager;
28 import android.media.AudioAttributes;
29 import android.media.AudioDeviceInfo;
30 import android.media.AudioFocusRequest;
31 import android.media.AudioManager;
32 import android.media.HwAudioSource;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.util.Log;
37 import android.view.Display;
38 import android.view.DisplayAddress;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.widget.AdapterView;
43 import android.widget.ArrayAdapter;
44 import android.widget.Button;
45 import android.widget.LinearLayout;
46 import android.widget.RadioGroup;
47 import android.widget.Spinner;
48 import android.widget.TextView;
49 import android.widget.Toast;
50 import android.widget.ToggleButton;
51 
52 import androidx.fragment.app.Fragment;
53 
54 import com.google.android.car.kitchensink.CarEmulator;
55 import com.google.android.car.kitchensink.R;
56 
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.List;
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     private AudioManager mAudioManager;
66     private FocusHandler mAudioFocusHandler;
67     private ToggleButton mEnableMocking;
68 
69     private AudioPlayer mMusicPlayer;
70     private AudioPlayer mMusicPlayerShort;
71     private AudioPlayer mNavGuidancePlayer;
72     private AudioPlayer mVrPlayer;
73     private AudioPlayer mSystemPlayer;
74     private AudioPlayer mWavPlayer;
75     private AudioPlayer mMusicPlayerForSelectedDisplay;
76     private HwAudioSource mHwAudioSource;
77     private AudioPlayer[] mAllPlayers;
78 
79     private Handler mHandler;
80     private Context mContext;
81 
82     private Car mCar;
83     private CarAppFocusManager mAppFocusManager;
84     private AudioAttributes mMusicAudioAttrib;
85     private AudioAttributes mNavAudioAttrib;
86     private AudioAttributes mVrAudioAttrib;
87     private AudioAttributes mRadioAudioAttrib;
88     private AudioAttributes mSystemSoundAudioAttrib;
89     private AudioAttributes mMusicAudioAttribForDisplay;
90     private CarEmulator mCarEmulator;
91     private CarAudioManager mCarAudioManager;
92     private Spinner mZoneSpinner;
93     ArrayAdapter<Integer> mZoneAdapter;
94     private Spinner mDisplaySpinner;
95     ArrayAdapter<Integer> mDisplayAdapter;
96     private LinearLayout mDisplayLayout;
97     private int mOldZonePosition;
98 
99     private static int sDefaultExtraTestScreenPortId = 1;
100 
101     private final AudioManager.OnAudioFocusChangeListener mNavFocusListener = (focusChange) -> {
102         Log.i(TAG, "Nav focus change:" + focusChange);
103     };
104     private final AudioManager.OnAudioFocusChangeListener mVrFocusListener = (focusChange) -> {
105         Log.i(TAG, "VR focus change:" + focusChange);
106     };
107     private final AudioManager.OnAudioFocusChangeListener mRadioFocusListener = (focusChange) -> {
108         Log.i(TAG, "Radio focus change:" + focusChange);
109     };
110 
111     private final CarAppFocusManager.OnAppFocusOwnershipCallback mOwnershipCallbacks =
112             new OnAppFocusOwnershipCallback() {
113                 @Override
114                 public void onAppFocusOwnershipLost(int focus) {
115                 }
116                 @Override
117                 public void onAppFocusOwnershipGranted(int focus) {
118                 }
119     };
120 
connectCar()121     private void connectCar() {
122         mContext = getContext();
123         mHandler = new Handler(Looper.getMainLooper());
124         mCar = Car.createCar(mContext, /* handler= */ null,
125                 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, (car, ready) -> {
126                     if (!ready) {
127                         return;
128                     }
129                     mAppFocusManager =
130                             (CarAppFocusManager) car.getCarManager(Car.APP_FOCUS_SERVICE);
131                     OnAppFocusChangedListener listener = new OnAppFocusChangedListener() {
132                         @Override
133                         public void onAppFocusChanged(int appType, boolean active) {
134                         }
135                     };
136                     mAppFocusManager.addFocusListener(listener,
137                             CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
138                     mAppFocusManager.addFocusListener(listener,
139                             CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND);
140 
141                     mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
142 
143                     //take care of zone selection
144                     int[] zoneList = mCarAudioManager.getAudioZoneIds();
145                     Integer[] zoneArray = Arrays.stream(zoneList).boxed().toArray(Integer[]::new);
146                     mZoneAdapter = new ArrayAdapter<>(mContext,
147                             android.R.layout.simple_spinner_item, zoneArray);
148                     mZoneAdapter.setDropDownViewResource(
149                             android.R.layout.simple_spinner_dropdown_item);
150                     mZoneSpinner.setAdapter(mZoneAdapter);
151                     mZoneSpinner.setEnabled(true);
152 
153                     if (mCarAudioManager.isDynamicRoutingEnabled()) {
154                         setUpDisplayPlayer();
155                     }
156                 });
157     }
158 
initializePlayers()159     private void initializePlayers() {
160         mMusicAudioAttrib = new AudioAttributes.Builder()
161             .setUsage(AudioAttributes.USAGE_MEDIA)
162             .build();
163         mNavAudioAttrib = new AudioAttributes.Builder()
164             .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
165             .build();
166         mVrAudioAttrib = new AudioAttributes.Builder()
167             .setUsage(AudioAttributes.USAGE_ASSISTANT)
168             .build();
169         mRadioAudioAttrib = new AudioAttributes.Builder()
170             .setUsage(AudioAttributes.USAGE_MEDIA)
171             .build();
172         mSystemSoundAudioAttrib = new AudioAttributes.Builder()
173             .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
174             .build();
175         // Create a display audio attribute
176         mMusicAudioAttribForDisplay = new AudioAttributes.Builder()
177                 .setUsage(AudioAttributes.USAGE_MEDIA)
178                 .build();
179 
180 
181         mMusicPlayerForSelectedDisplay = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
182                 mMusicAudioAttribForDisplay);
183         mMusicPlayer = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
184             mMusicAudioAttrib);
185         mMusicPlayerShort = new AudioPlayer(mContext, R.raw.ring_classic_01,
186             mMusicAudioAttrib);
187         mNavGuidancePlayer = new AudioPlayer(mContext, R.raw.turnright,
188             mNavAudioAttrib);
189         mVrPlayer = new AudioPlayer(mContext, R.raw.one2six,
190             mVrAudioAttrib);
191         mSystemPlayer = new AudioPlayer(mContext, R.raw.ring_classic_01,
192             mSystemSoundAudioAttrib);
193         mWavPlayer = new AudioPlayer(mContext, R.raw.free_flight,
194             mMusicAudioAttrib);
195         final AudioDeviceInfo tuner = findTunerDevice(mContext);
196         if (tuner != null) {
197             mHwAudioSource = new HwAudioSource.Builder()
198                 .setAudioAttributes(mMusicAudioAttrib)
199                 .setAudioDeviceInfo(findTunerDevice(mContext))
200                 .build();
201         }
202         mAllPlayers = new AudioPlayer[] {
203             mMusicPlayer,
204             mMusicPlayerShort,
205             mNavGuidancePlayer,
206             mVrPlayer,
207             mSystemPlayer,
208             mWavPlayer
209         };
210     }
211 
212     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle)213     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
214         Log.i(TAG, "onCreateView");
215         View view = inflater.inflate(R.layout.audio, container, false);
216         //Zone Spinner
217         setUpZoneSpinnerView(view);
218 
219         //Display layout
220         setUpDisplayLayoutView(view);
221 
222         connectCar();
223         initializePlayers();
224 
225         mAudioManager = (AudioManager) mContext.getSystemService(
226                 Context.AUDIO_SERVICE);
227         mAudioFocusHandler = new FocusHandler(
228                 view.findViewById(R.id.button_focus_request_selection),
229                 view.findViewById(R.id.button_audio_focus_request),
230                 view.findViewById(R.id.text_audio_focus_state));
231         view.findViewById(R.id.button_media_play_start).setOnClickListener(v -> {
232             boolean requestFocus = true;
233             boolean repeat = true;
234             mMusicPlayer.start(requestFocus, repeat, AudioManager.AUDIOFOCUS_GAIN);
235         });
236         view.findViewById(R.id.button_media_play_once).setOnClickListener(v -> {
237             mMusicPlayerShort.start(true, false, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
238             // play only for 1 sec and stop
239             mHandler.postDelayed(() -> mMusicPlayerShort.stop(), 1000);
240         });
241         view.findViewById(R.id.button_media_play_stop).setOnClickListener(v -> mMusicPlayer.stop());
242         view.findViewById(R.id.button_wav_play_start).setOnClickListener(
243                 v -> mWavPlayer.start(true, true, AudioManager.AUDIOFOCUS_GAIN));
244         view.findViewById(R.id.button_wav_play_stop).setOnClickListener(v -> mWavPlayer.stop());
245         view.findViewById(R.id.button_nav_play_once).setOnClickListener(v -> {
246             if (mAppFocusManager == null) {
247                 Log.e(TAG, "mAppFocusManager is null");
248                 return;
249             }
250             if (DBG) {
251                 Log.i(TAG, "Nav start");
252             }
253             mAppFocusManager.requestAppFocus(
254                     CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, mOwnershipCallbacks);
255             if (!mNavGuidancePlayer.isPlaying()) {
256                 mNavGuidancePlayer.start(true, false,
257                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
258                         () -> mAppFocusManager.abandonAppFocus(mOwnershipCallbacks,
259                                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION));
260             }
261         });
262         view.findViewById(R.id.button_vr_play_once).setOnClickListener(v -> {
263             if (mAppFocusManager == null) {
264                 Log.e(TAG, "mAppFocusManager is null");
265                 return;
266             }
267             if (DBG) {
268                 Log.i(TAG, "VR start");
269             }
270             mAppFocusManager.requestAppFocus(
271                     CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, mOwnershipCallbacks);
272             if (!mVrPlayer.isPlaying()) {
273                 mVrPlayer.start(true, false,
274                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
275                         () -> mAppFocusManager.abandonAppFocus(mOwnershipCallbacks,
276                                 CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND));
277             }
278         });
279         view.findViewById(R.id.button_system_play_once).setOnClickListener(v -> {
280             if (DBG) {
281                 Log.i(TAG, "System start");
282             }
283             if (!mSystemPlayer.isPlaying()) {
284                 // system sound played without focus
285                 mSystemPlayer.start(false, false, 0);
286             }
287         });
288         view.findViewById(R.id.button_nav_start).setOnClickListener(v -> handleNavStart());
289         view.findViewById(R.id.button_nav_end).setOnClickListener(v -> handleNavEnd());
290         view.findViewById(R.id.button_vr_start).setOnClickListener(v -> handleVrStart());
291         view.findViewById(R.id.button_vr_end).setOnClickListener(v -> handleVrEnd());
292         view.findViewById(R.id.button_radio_start).setOnClickListener(v -> handleRadioStart());
293         view.findViewById(R.id.button_radio_end).setOnClickListener(v -> handleRadioEnd());
294         view.findViewById(R.id.button_speaker_phone_on).setOnClickListener(
295                 v -> mAudioManager.setSpeakerphoneOn(true));
296         view.findViewById(R.id.button_speaker_phone_off).setOnClickListener(
297                 v -> mAudioManager.setSpeakerphoneOn(false));
298         view.findViewById(R.id.button_microphone_on).setOnClickListener(
299                 v -> mAudioManager.setMicrophoneMute(false));
300         view.findViewById(R.id.button_microphone_off).setOnClickListener(
301                 v -> mAudioManager.setMicrophoneMute(true));
302         final View hwAudioSourceNotFound = view.findViewById(R.id.hw_audio_source_not_found);
303         final View hwAudioSourceStart = view.findViewById(R.id.hw_audio_source_start);
304         final View hwAudioSourceStop = view.findViewById(R.id.hw_audio_source_stop);
305         if (mHwAudioSource == null) {
306             hwAudioSourceNotFound.setVisibility(View.VISIBLE);
307             hwAudioSourceStart.setVisibility(View.GONE);
308             hwAudioSourceStop.setVisibility(View.GONE);
309         } else {
310             hwAudioSourceNotFound.setVisibility(View.GONE);
311             hwAudioSourceStart.setVisibility(View.VISIBLE);
312             hwAudioSourceStop.setVisibility(View.VISIBLE);
313             view.findViewById(R.id.hw_audio_source_start).setOnClickListener(
314                     v -> handleHwAudioSourceStart());
315             view.findViewById(R.id.hw_audio_source_stop).setOnClickListener(
316                     v -> handleHwAudioSourceStop());
317         }
318 
319         mEnableMocking = view.findViewById(R.id.button_mock_audio);
320         mEnableMocking.setOnCheckedChangeListener((buttonView, isChecked) -> {
321             if (mCarEmulator == null) {
322                 //TODO(pavelm): need to do a full switch between emulated and normal mode
323                 // all Car*Manager references should be invalidated.
324                 Toast.makeText(AudioTestFragment.this.getContext(),
325                         "Not supported yet :(", Toast.LENGTH_SHORT).show();
326                 return;
327             }
328             if (isChecked) {
329                 mCarEmulator.start();
330             } else {
331                 mCarEmulator.stop();
332                 mCarEmulator = null;
333             }
334         });
335 
336         // Manage buttons for audio player for displays
337         view.findViewById(R.id.button_display_media_play_start).setOnClickListener(v -> {
338             startDisplayAudio();
339         });
340         view.findViewById(R.id.button_display_media_play_once).setOnClickListener(v -> {
341             startDisplayAudio();
342             // play only for 1 sec and stop
343             mHandler.postDelayed(() -> mMusicPlayerForSelectedDisplay.stop(), 1000);
344         });
345         view.findViewById(R.id.button_display_media_play_stop)
346                 .setOnClickListener(v -> mMusicPlayerForSelectedDisplay.stop());
347 
348         return view;
349     }
350 
setUpDisplayLayoutView(View view)351     private void setUpDisplayLayoutView(View view) {
352         mDisplayLayout = view.findViewById(R.id.audio_display_layout);
353 
354         mDisplaySpinner = view.findViewById(R.id.display_spinner);
355         mDisplaySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
356             @Override
357             public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
358                 handleDisplaySelection();
359             }
360 
361             @Override
362             public void onNothingSelected(AdapterView<?> parent) {
363             }
364         });
365     }
366 
setUpZoneSpinnerView(View view)367     private void setUpZoneSpinnerView(View view) {
368         mZoneSpinner = view.findViewById(R.id.zone_spinner);
369         mZoneSpinner.setEnabled(false);
370         mZoneSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
371             @Override
372             public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
373                 handleZoneSelection();
374             }
375 
376             @Override
377             public void onNothingSelected(AdapterView<?> parent) {
378             }
379         });
380     }
381 
handleZoneSelection()382     public void handleZoneSelection() {
383         int position = mZoneSpinner.getSelectedItemPosition();
384         int zone = mZoneAdapter.getItem(position);
385         Log.d(TAG, "Zone Selected: " + zone);
386         try {
387             ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
388                     mContext.getPackageName(), 0);
389             int uid = info.uid;
390             Log.d(TAG, "handleZoneSelection App uid: " + uid);
391             if (mCarAudioManager.setZoneIdForUid(zone, uid)) {
392                 Log.d(TAG, "Changed uid " + uid + " sound to zone " + zone);
393                 mOldZonePosition = position;
394             } else {
395                 Log.d(TAG, "Filed to changed uid " + uid + " sound to zone " + zone);
396                 mZoneSpinner.setSelection(mOldZonePosition);
397             }
398 
399         } catch (PackageManager.NameNotFoundException e) {
400             Log.e(TAG, "handleZoneSelection Failed to find name: " , e);
401         }
402     }
403 
404     @Override
onDestroyView()405     public void onDestroyView() {
406         super.onDestroyView();
407         Log.i(TAG, "onDestroyView");
408         if (mCarEmulator != null) {
409             mCarEmulator.stop();
410         }
411         for (AudioPlayer p : mAllPlayers) {
412             p.stop();
413         }
414         handleHwAudioSourceStop();
415         if (mAudioFocusHandler != null) {
416             mAudioFocusHandler.release();
417             mAudioFocusHandler = null;
418         }
419         if (mAppFocusManager != null) {
420             mAppFocusManager.abandonAppFocus(mOwnershipCallbacks);
421         }
422     }
423 
handleNavStart()424     private void handleNavStart() {
425         if (mAppFocusManager == null) {
426             Log.e(TAG, "mAppFocusManager is null");
427             return;
428         }
429         if (DBG) {
430             Log.i(TAG, "Nav start");
431         }
432         mAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
433                 mOwnershipCallbacks);
434         mAudioManager.requestAudioFocus(mNavFocusListener, mNavAudioAttrib,
435                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
436     }
437 
handleNavEnd()438     private void handleNavEnd() {
439         if (mAppFocusManager == null) {
440             Log.e(TAG, "mAppFocusManager is null");
441             return;
442         }
443         if (DBG) {
444             Log.i(TAG, "Nav end");
445         }
446         mAudioManager.abandonAudioFocus(mNavFocusListener, mNavAudioAttrib);
447         mAppFocusManager.abandonAppFocus(mOwnershipCallbacks,
448                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
449     }
450 
findTunerDevice(Context context)451     private AudioDeviceInfo findTunerDevice(Context context) {
452         AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
453         AudioDeviceInfo[] devices = am.getDevices(AudioManager.GET_DEVICES_INPUTS);
454         for (AudioDeviceInfo device : devices) {
455             if (device.getType() == AudioDeviceInfo.TYPE_FM_TUNER) {
456                 return device;
457             }
458         }
459         return null;
460     }
461 
handleHwAudioSourceStart()462     private void handleHwAudioSourceStart() {
463         if (mHwAudioSource != null) {
464             mHwAudioSource.start();
465         }
466     }
467 
handleHwAudioSourceStop()468     private void handleHwAudioSourceStop() {
469         if (mHwAudioSource != null) {
470             mHwAudioSource.stop();
471         }
472     }
473 
handleVrStart()474     private void handleVrStart() {
475         if (mAppFocusManager == null) {
476             Log.e(TAG, "mAppFocusManager is null");
477             return;
478         }
479         if (DBG) {
480             Log.i(TAG, "VR start");
481         }
482         mAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND,
483                 mOwnershipCallbacks);
484         mAudioManager.requestAudioFocus(mVrFocusListener, mVrAudioAttrib,
485                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
486     }
487 
handleVrEnd()488     private void handleVrEnd() {
489         if (mAppFocusManager == null) {
490             Log.e(TAG, "mAppFocusManager is null");
491             return;
492         }
493         if (DBG) {
494             Log.i(TAG, "VR end");
495         }
496         mAudioManager.abandonAudioFocus(mVrFocusListener, mVrAudioAttrib);
497         mAppFocusManager.abandonAppFocus(mOwnershipCallbacks,
498                 CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND);
499     }
500 
handleRadioStart()501     private void handleRadioStart() {
502         if (DBG) {
503             Log.i(TAG, "Radio start");
504         }
505         mAudioManager.requestAudioFocus(mRadioFocusListener, mRadioAudioAttrib,
506                 AudioManager.AUDIOFOCUS_GAIN, 0);
507     }
508 
handleRadioEnd()509     private void handleRadioEnd() {
510         if (DBG) {
511             Log.i(TAG, "Radio end");
512         }
513         mAudioManager.abandonAudioFocus(mRadioFocusListener, mRadioAudioAttrib);
514     }
515 
setUpDisplayPlayer()516     private void setUpDisplayPlayer() {
517         DisplayManager displayManager =  (DisplayManager) mContext.getSystemService(
518                 Context.DISPLAY_SERVICE);
519         Display[] displays = displayManager.getDisplays();
520         List<Integer> displayList = new ArrayList<>();
521         for (Display display : displays) {
522             DisplayAddress.Physical physical = (DisplayAddress.Physical) display.getAddress();
523             if (physical != null) {
524                 displayList.add((int) physical.getPort());
525                 Log.d(TAG, "Found Display Port " + physical.getPort());
526             } else {
527                 Log.d(TAG, "Found Display with no physical " + display.getDisplayId());
528             }
529         }
530         // If only one display is available add another display for testing
531         if (displayList.size() == 1) {
532             displayList.add(sDefaultExtraTestScreenPortId);
533         }
534 
535         //take care of display selection
536         Integer[] displayArray = displayList.stream().toArray(Integer[]::new);
537         mDisplayAdapter = new ArrayAdapter<>(mContext,
538                 android.R.layout.simple_spinner_item, displayArray);
539         mDisplayAdapter.setDropDownViewResource(
540                 android.R.layout.simple_spinner_dropdown_item);
541         mDisplaySpinner.setAdapter(mDisplayAdapter);
542         createDisplayAudioPlayer();
543     }
544 
createDisplayAudioPlayer()545     private void createDisplayAudioPlayer() {
546         byte selectedDisplayPortId = mDisplayAdapter.getItem(
547                 mDisplaySpinner.getSelectedItemPosition()).byteValue();
548         int zoneIdForDisplayId = mCarAudioManager.getZoneIdForDisplayPortId(selectedDisplayPortId);
549         Log.d(TAG, "Setting Bundle to zone " + zoneIdForDisplayId);
550         Bundle bundle = new Bundle();
551         bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,
552                 zoneIdForDisplayId);
553         mMusicAudioAttribForDisplay = new AudioAttributes.Builder()
554                 .setUsage(AudioAttributes.USAGE_MEDIA)
555                 .addBundle(bundle)
556                 .build();
557 
558         mMusicPlayerForSelectedDisplay = new AudioPlayer(mContext,
559                 R.raw.well_worth_the_wait,
560                 mMusicAudioAttribForDisplay);
561 
562         mDisplayLayout.findViewById(R.id.audio_display_layout)
563                 .setVisibility(View.VISIBLE);
564     }
565 
startDisplayAudio()566     private void startDisplayAudio() {
567         byte selectedDisplayPortId = mDisplayAdapter.getItem(
568                 mDisplaySpinner.getSelectedItemPosition()).byteValue();
569         int zoneIdForDisplayId = mCarAudioManager.getZoneIdForDisplayPortId(selectedDisplayPortId);
570         Log.d(TAG, "Starting display audio in zone " + zoneIdForDisplayId);
571         // Direct audio to the correct source
572         // TODO: Figure out a way to facilitate this for the user
573         // Currently there is no way of distinguishing apps from the same package to different zones
574         // One suggested way would be to create a unique id for each focus requester that is also
575         // share with the audio router
576         if (zoneIdForDisplayId == CarAudioManager.PRIMARY_AUDIO_ZONE) {
577             mMusicPlayerForSelectedDisplay.start(true, false,
578                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
579         } else {
580             // Route everything else to rear seat
581             mMusicPlayerForSelectedDisplay.start(true, false,
582                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, "bus100_rear_seat");
583         }
584     }
585 
handleDisplaySelection()586     public void handleDisplaySelection() {
587         if (mMusicPlayerForSelectedDisplay != null && mMusicPlayerForSelectedDisplay.isPlaying()) {
588             mMusicPlayerForSelectedDisplay.stop();
589         }
590         createDisplayAudioPlayer();
591     }
592 
593 
594     private class FocusHandler {
595         private static final String AUDIO_FOCUS_STATE_GAIN = "gain";
596         private static final String AUDIO_FOCUS_STATE_RELEASED_UNKNOWN = "released / unknown";
597 
598         private final RadioGroup mRequestSelection;
599         private final TextView mText;
600         private final AudioFocusListener mFocusListener;
601         private AudioFocusRequest mFocusRequest;
602 
FocusHandler(RadioGroup radioGroup, Button requestButton, TextView text)603         public FocusHandler(RadioGroup radioGroup, Button requestButton, TextView text) {
604             mText = text;
605             mRequestSelection = radioGroup;
606             mRequestSelection.check(R.id.focus_gain);
607             setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
608             mFocusListener = new AudioFocusListener();
609             requestButton.setOnClickListener(v -> {
610                 int selectedButtonId = mRequestSelection.getCheckedRadioButtonId();
611                 int focusRequest;
612                 switch (selectedButtonId) {
613                     case R.id.focus_gain:
614                         focusRequest = AudioManager.AUDIOFOCUS_GAIN;
615                         break;
616                     case R.id.focus_gain_transient:
617                         focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
618                         break;
619                     case R.id.focus_gain_transient_duck:
620                         focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
621                         break;
622                     case R.id.focus_gain_transient_exclusive:
623                         focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
624                         break;
625                     case R.id.focus_release:
626                     default:
627                         abandonAudioFocus();
628                         return;
629                 }
630                 mFocusRequest = new AudioFocusRequest.Builder(focusRequest)
631                         .setAudioAttributes(new AudioAttributes.Builder()
632                                 .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
633                                 .build())
634                         .setOnAudioFocusChangeListener(mFocusListener)
635                         .build();
636                 int ret = mAudioManager.requestAudioFocus(mFocusRequest);
637                 Log.i(TAG, "requestAudioFocus returned " + ret);
638                 if (ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
639                     setFocusText(AUDIO_FOCUS_STATE_GAIN);
640                 }
641             });
642         }
643 
release()644         public void release() {
645             abandonAudioFocus();
646         }
647 
abandonAudioFocus()648         private void abandonAudioFocus() {
649             if (DBG) {
650                 Log.i(TAG, "abandonAudioFocus");
651             }
652             mAudioManager.abandonAudioFocusRequest(mFocusRequest);
653             mFocusRequest = null;
654             setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
655         }
656 
setFocusText(String msg)657         private void setFocusText(String msg) {
658             mText.setText("focus state:" + msg);
659         }
660 
661         private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
662             @Override
onAudioFocusChange(int focusChange)663             public void onAudioFocusChange(int focusChange) {
664                 Log.i(TAG, "onAudioFocusChange " + focusChange);
665                 if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
666                     setFocusText(AUDIO_FOCUS_STATE_GAIN);
667                 } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
668                     setFocusText("loss");
669                 } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
670                     setFocusText("loss,transient");
671                 } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
672                     setFocusText("loss,transient,duck");
673                 }
674             }
675         }
676     }
677 }
678