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