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