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