1 /* 2 * Copyright (C) 2010-2011 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.android.musicfx; 18 19 import android.app.ActionBar; 20 import android.app.Activity; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.media.AudioDeviceCallback; 24 import android.media.AudioDeviceInfo; 25 import android.media.AudioFormat; 26 import android.media.AudioManager; 27 import android.media.audiofx.AudioEffect; 28 import android.media.audiofx.AudioEffect.Descriptor; 29 import android.media.audiofx.Virtualizer; 30 import android.os.Bundle; 31 import android.util.Log; 32 import android.view.Gravity; 33 import android.view.MotionEvent; 34 import android.view.View; 35 import android.view.View.OnTouchListener; 36 import android.view.ViewGroup; 37 import android.widget.AdapterView; 38 import android.widget.AdapterView.OnItemSelectedListener; 39 import android.widget.ArrayAdapter; 40 import android.widget.CompoundButton; 41 import android.widget.CompoundButton.OnCheckedChangeListener; 42 import android.widget.SeekBar; 43 import android.widget.SeekBar.OnSeekBarChangeListener; 44 import android.widget.Spinner; 45 import android.widget.Switch; 46 import android.widget.TextView; 47 import android.widget.Toast; 48 49 import androidx.core.graphics.Insets; 50 import androidx.core.view.ViewCompat; 51 import androidx.core.view.WindowCompat; 52 import androidx.core.view.WindowInsetsCompat; 53 54 import com.android.audiofx.OpenSLESConstants; 55 56 import java.util.Formatter; 57 import java.util.HashSet; 58 import java.util.Locale; 59 import java.util.Map; 60 import java.util.Set; 61 62 /** 63 * 64 */ 65 public class ActivityMusic extends Activity implements OnSeekBarChangeListener { 66 private final static String TAG = "MusicFXActivityMusic"; 67 68 /** 69 * Max number of EQ bands supported 70 */ 71 private final static int EQUALIZER_MAX_BANDS = 32; 72 73 /** 74 * Max levels per EQ band in millibels (1 dB = 100 mB) 75 */ 76 private final static int EQUALIZER_MAX_LEVEL = 1000; 77 78 /** 79 * Min levels per EQ band in millibels (1 dB = 100 mB) 80 */ 81 private final static int EQUALIZER_MIN_LEVEL = -1000; 82 83 /** 84 * Indicates if Virtualizer effect is supported. 85 */ 86 private boolean mVirtualizerSupported = false; 87 private boolean mVirtualizerIsHeadphoneOnly = false; 88 /** 89 * Indicates if BassBoost effect is supported. 90 */ 91 private boolean mBassBoostSupported = false; 92 /** 93 * Indicates if Equalizer effect is supported. 94 */ 95 private boolean mEqualizerSupported = false; 96 /** 97 * Indicates if Preset Reverb effect is supported. 98 */ 99 private boolean mPresetReverbSupported = false; 100 101 // Equalizer fields 102 private final SeekBar[] mEqualizerSeekBar = new SeekBar[EQUALIZER_MAX_BANDS]; 103 private int mNumberEqualizerBands; 104 private int mEqualizerMinBandLevel; 105 private int mEQPresetUserPos = 1; 106 private int mEQPreset; 107 private int mEQPresetPrevious; 108 private int[] mEQPresetUserBandLevelsPrev; 109 private String[] mEQPresetNames; 110 111 private int mPRPreset; 112 private int mPRPresetPrevious; 113 114 private boolean mIsHeadsetOn = false; 115 private CompoundButton mToggleSwitch; 116 117 private StringBuilder mFormatBuilder = new StringBuilder(); 118 private Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault()); 119 120 /** 121 * Mapping for the EQ widget ids per band 122 */ 123 private static final int[][] EQViewElementIds = { 124 { R.id.EQBand0TextView, R.id.EQBand0SeekBar }, 125 { R.id.EQBand1TextView, R.id.EQBand1SeekBar }, 126 { R.id.EQBand2TextView, R.id.EQBand2SeekBar }, 127 { R.id.EQBand3TextView, R.id.EQBand3SeekBar }, 128 { R.id.EQBand4TextView, R.id.EQBand4SeekBar }, 129 { R.id.EQBand5TextView, R.id.EQBand5SeekBar }, 130 { R.id.EQBand6TextView, R.id.EQBand6SeekBar }, 131 { R.id.EQBand7TextView, R.id.EQBand7SeekBar }, 132 { R.id.EQBand8TextView, R.id.EQBand8SeekBar }, 133 { R.id.EQBand9TextView, R.id.EQBand9SeekBar }, 134 { R.id.EQBand10TextView, R.id.EQBand10SeekBar }, 135 { R.id.EQBand11TextView, R.id.EQBand11SeekBar }, 136 { R.id.EQBand12TextView, R.id.EQBand12SeekBar }, 137 { R.id.EQBand13TextView, R.id.EQBand13SeekBar }, 138 { R.id.EQBand14TextView, R.id.EQBand14SeekBar }, 139 { R.id.EQBand15TextView, R.id.EQBand15SeekBar }, 140 { R.id.EQBand16TextView, R.id.EQBand16SeekBar }, 141 { R.id.EQBand17TextView, R.id.EQBand17SeekBar }, 142 { R.id.EQBand18TextView, R.id.EQBand18SeekBar }, 143 { R.id.EQBand19TextView, R.id.EQBand19SeekBar }, 144 { R.id.EQBand20TextView, R.id.EQBand20SeekBar }, 145 { R.id.EQBand21TextView, R.id.EQBand21SeekBar }, 146 { R.id.EQBand22TextView, R.id.EQBand22SeekBar }, 147 { R.id.EQBand23TextView, R.id.EQBand23SeekBar }, 148 { R.id.EQBand24TextView, R.id.EQBand24SeekBar }, 149 { R.id.EQBand25TextView, R.id.EQBand25SeekBar }, 150 { R.id.EQBand26TextView, R.id.EQBand26SeekBar }, 151 { R.id.EQBand27TextView, R.id.EQBand27SeekBar }, 152 { R.id.EQBand28TextView, R.id.EQBand28SeekBar }, 153 { R.id.EQBand29TextView, R.id.EQBand29SeekBar }, 154 { R.id.EQBand30TextView, R.id.EQBand30SeekBar }, 155 { R.id.EQBand31TextView, R.id.EQBand31SeekBar } }; 156 157 // Preset Reverb fields 158 /** 159 * Array containing the PR preset names. 160 */ 161 private static final String[] PRESETREVERBPRESETSTRINGS = { "None", "SmallRoom", "MediumRoom", 162 "LargeRoom", "MediumHall", "LargeHall", "Plate" }; 163 164 /** 165 * Default localized equalizer preset names. Keep the same as EffectBundle::gEqualizerPresets. 166 */ 167 private static final Map<String, Integer> LOCALIZED_EQUALIZER_PRESET_NAMES = Map.of( 168 "Normal", R.string.normal, 169 "Classical", R.string.classical, 170 "Dance", R.string.dance, 171 "Flat", R.string.flat, 172 "Folk", R.string.folk, 173 "Heavy Metal", R.string.heavy_metal, 174 "Hip Hop", R.string.hip_hop, 175 "Jazz", R.string.jazz, 176 "Pop", R.string.pop, 177 "Rock", R.string.rock); 178 179 /** 180 * Context field 181 */ 182 private Context mContext; 183 184 /** 185 * Calling package name field 186 */ 187 private String mCallingPackageName = "empty"; 188 189 // Listen to AudioDeviceCallback to determine if a headset is connected 190 private final AudioDeviceCallback mMyAudioDeviceCallback = 191 new AudioDeviceCallback() { 192 193 /** 194 * Called by the {@link AudioManager} to indicate that one or more audio devices 195 * have been connected. 196 * 197 * @see AudioDeviceCallback. 198 */ 199 @Override 200 public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 201 // do nothing if mIsHeadsetOn is true and new devices added 202 if (mIsHeadsetOn) { 203 return; 204 } 205 final boolean isHeadsetOn = isHeadSetDeviceConnected(); 206 if (isHeadsetOn) { 207 Log.v(TAG, " HeadSet connected"); 208 mIsHeadsetOn = true; 209 updateUIHeadset(); 210 } 211 } 212 213 /** 214 * Called by the {@link AudioManager} to indicate that one or more audio devices 215 * have been disconnected. 216 * 217 * @param removedDevices An array of {@link AudioDeviceInfo} objects corresponding 218 * to any newly removed audio devices. 219 */ 220 @Override 221 public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { 222 // do nothing if mIsHeadsetOn is false and some devices removed 223 if (!mIsHeadsetOn) { 224 return; 225 } 226 final boolean isHeadsetOn = isHeadSetDeviceConnected(); 227 if (!isHeadsetOn) { 228 Log.v(TAG, " HeadSet disconnected"); 229 mIsHeadsetOn = false; 230 updateUIHeadset(); 231 } 232 } 233 }; 234 235 public static final Set<Integer> HEADSET_DEVICE_TYPES = new HashSet<>(); 236 static { 237 HEADSET_DEVICE_TYPES.add(AudioDeviceInfo.TYPE_WIRED_HEADSET); 238 HEADSET_DEVICE_TYPES.add(AudioDeviceInfo.TYPE_WIRED_HEADPHONES); 239 HEADSET_DEVICE_TYPES.add(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP); 240 HEADSET_DEVICE_TYPES.add(AudioDeviceInfo.TYPE_BLUETOOTH_SCO); 241 HEADSET_DEVICE_TYPES.add(AudioDeviceInfo.TYPE_USB_HEADSET); 242 HEADSET_DEVICE_TYPES.add(AudioDeviceInfo.TYPE_HEARING_AID); 243 HEADSET_DEVICE_TYPES.add(AudioDeviceInfo.TYPE_BLE_HEADSET); 244 } 245 isHeadSetDeviceConnected()246 private boolean isHeadSetDeviceConnected() { 247 final AudioManager audioManager = getSystemService(AudioManager.class); 248 final AudioDeviceInfo[] deviceInfos = 249 audioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); 250 for (AudioDeviceInfo deviceInfo : deviceInfos) { 251 if (deviceInfo == null) { 252 continue; 253 } 254 final int type = deviceInfo.getType(); 255 if (HEADSET_DEVICE_TYPES.contains(type)) { 256 Log.v(TAG, " at least a HeadSet device type " + type + " connected"); 257 return true; 258 } 259 } 260 261 Log.v(TAG, " no HeadSet device type connected"); 262 return false; 263 } 264 265 /* 266 * Declares and initializes all objects and widgets in the layouts and the CheckBox and SeekBar 267 * onchange methods on creation. 268 * 269 * (non-Javadoc) 270 * 271 * @see android.app.ActivityGroup#onCreate(android.os.Bundle) 272 */ 273 @Override onCreate(final Bundle savedInstanceState)274 public void onCreate(final Bundle savedInstanceState) { 275 super.onCreate(savedInstanceState); 276 277 // Init context to be used in listeners 278 mContext = this; 279 280 // Receive intent 281 // get calling intent 282 final Intent intent = getIntent(); 283 final int audioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION, 284 ControlPanelEffect.AUDIO_SESSION_ID_UNSPECIFIED); 285 Log.v(TAG, "audio session: " + audioSession); 286 287 mCallingPackageName = getCallingPackage(); 288 289 // check for errors 290 if (mCallingPackageName == null) { 291 Log.e(TAG, "Package name is null"); 292 setResult(RESULT_CANCELED); 293 finish(); 294 return; 295 } 296 setResult(RESULT_OK); 297 298 Log.v(TAG, mCallingPackageName + " (" + audioSession + ")"); 299 300 ControlPanelEffect.initEffectsPreferences(mContext, mCallingPackageName, audioSession); 301 302 // Make sure the package name and the audio session are saved, since MusicFX might be killed 303 // after receiving the AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION intent. 304 ControlPanelEffect.openSession(mContext, mCallingPackageName, audioSession); 305 306 // query available effects 307 final Descriptor[] effects = AudioEffect.queryEffects(); 308 309 // Determine available/supported effects 310 Log.v(TAG, "Available effects:"); 311 for (final Descriptor effect : effects) { 312 Log.v(TAG, effect.name.toString() + ", type: " + effect.type.toString()); 313 314 if (effect.type.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) { 315 mVirtualizerSupported = true; 316 mVirtualizerIsHeadphoneOnly = !isVirtualizerTransauralSupported(); 317 } else if (effect.type.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)) { 318 mBassBoostSupported = true; 319 } else if (effect.type.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)) { 320 mEqualizerSupported = true; 321 } else if (effect.type.equals(AudioEffect.EFFECT_TYPE_PRESET_REVERB)) { 322 mPresetReverbSupported = true; 323 } 324 } 325 326 setContentView(R.layout.music_main); 327 final ViewGroup viewGroup = (ViewGroup) findViewById(R.id.contentSoundEffects); 328 329 // Set accessibility label for bass boost and virtualizer strength seekbars. 330 findViewById(R.id.bBStrengthText).setLabelFor(R.id.bBStrengthSeekBar); 331 findViewById(R.id.vIStrengthText).setLabelFor(R.id.vIStrengthSeekBar); 332 333 // Fill array with presets from AudioEffects call. 334 // allocate a space for 2 extra strings (CI Extreme & User) 335 final int numPresets = ControlPanelEffect.getParameterInt(mContext, mCallingPackageName, 336 ControlPanelEffect.Key.eq_num_presets); 337 mEQPresetNames = new String[numPresets + 2]; 338 for (short i = 0; i < numPresets; i++) { 339 mEQPresetNames[i] = ControlPanelEffect.getParameterString(mContext, 340 mCallingPackageName, ControlPanelEffect.Key.eq_preset_name, i); 341 Integer localizedNameId = LOCALIZED_EQUALIZER_PRESET_NAMES.get(mEQPresetNames[i]); 342 if (localizedNameId != null) { 343 mEQPresetNames[i] = getString(localizedNameId); 344 } 345 } 346 mEQPresetNames[numPresets] = getString(R.string.ci_extreme); 347 mEQPresetNames[numPresets + 1] = getString(R.string.user); 348 mEQPresetUserPos = numPresets + 1; 349 350 // Watch for button clicks and initialization. 351 if ((mVirtualizerSupported) || (mBassBoostSupported) || (mEqualizerSupported) 352 || (mPresetReverbSupported)) { 353 // Set the listener for the main enhancements toggle button. 354 // Depending on the state enable the supported effects if they were 355 // checked in the setup tab. 356 mToggleSwitch = new Switch(this); 357 mToggleSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() { 358 @Override 359 public void onCheckedChanged(final CompoundButton buttonView, 360 final boolean isChecked) { 361 362 // set parameter and state 363 ControlPanelEffect.setParameterBoolean(mContext, mCallingPackageName, 364 ControlPanelEffect.Key.global_enabled, isChecked); 365 // Enable Linear layout (in scroll layout) view with all 366 // effect contents depending on checked state 367 setEnabledAllChildren(viewGroup, isChecked); 368 // update UI according to headset state 369 updateUIHeadset(); 370 } 371 }); 372 373 // Initialize the Virtualizer elements. 374 // Set the SeekBar listener. 375 if (mVirtualizerSupported) { 376 // Show msg when disabled slider (layout) is touched 377 findViewById(R.id.vILayout).setOnTouchListener(new OnTouchListener() { 378 379 @Override 380 public boolean onTouch(final View v, final MotionEvent event) { 381 if (event.getAction() == MotionEvent.ACTION_UP) { 382 showHeadsetMsg(); 383 } 384 return false; 385 } 386 }); 387 388 final SeekBar seekbar = (SeekBar) findViewById(R.id.vIStrengthSeekBar); 389 seekbar.setMax(OpenSLESConstants.VIRTUALIZER_MAX_STRENGTH 390 - OpenSLESConstants.VIRTUALIZER_MIN_STRENGTH); 391 392 seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { 393 // Update the parameters while SeekBar changes and set the 394 // effect parameter. 395 396 @Override 397 public void onProgressChanged(final SeekBar seekBar, final int progress, 398 final boolean fromUser) { 399 // set parameter and state 400 ControlPanelEffect.setParameterInt(mContext, mCallingPackageName, 401 ControlPanelEffect.Key.virt_strength, progress); 402 } 403 404 // If slider pos was 0 when starting re-enable effect 405 @Override 406 public void onStartTrackingTouch(final SeekBar seekBar) { 407 if (seekBar.getProgress() == 0) { 408 ControlPanelEffect.setParameterBoolean(mContext, mCallingPackageName, 409 ControlPanelEffect.Key.virt_enabled, true); 410 } 411 } 412 413 // If slider pos = 0 when stopping disable effect 414 @Override 415 public void onStopTrackingTouch(final SeekBar seekBar) { 416 if (seekBar.getProgress() == 0) { 417 // disable 418 ControlPanelEffect.setParameterBoolean(mContext, mCallingPackageName, 419 ControlPanelEffect.Key.virt_enabled, false); 420 } 421 } 422 }); 423 424 final Switch sw = (Switch) findViewById(R.id.vIStrengthToggle); 425 sw.setOnCheckedChangeListener(new OnCheckedChangeListener() { 426 @Override 427 public void onCheckedChanged(final CompoundButton buttonView, 428 final boolean isChecked) { 429 ControlPanelEffect.setParameterBoolean(mContext, mCallingPackageName, 430 ControlPanelEffect.Key.virt_enabled, isChecked); 431 } 432 }); 433 } else { 434 findViewById(R.id.vILayout).setVisibility(View.GONE); 435 } 436 437 // Initialize the Bass Boost elements. 438 // Set the SeekBar listener. 439 if (mBassBoostSupported) { 440 // Show msg when disabled slider (layout) is touched 441 findViewById(R.id.bBLayout).setOnTouchListener(new OnTouchListener() { 442 443 @Override 444 public boolean onTouch(final View v, final MotionEvent event) { 445 if (event.getAction() == MotionEvent.ACTION_UP) { 446 showHeadsetMsg(); 447 } 448 return false; 449 } 450 }); 451 452 final SeekBar seekbar = (SeekBar) findViewById(R.id.bBStrengthSeekBar); 453 seekbar.setMax(OpenSLESConstants.BASSBOOST_MAX_STRENGTH 454 - OpenSLESConstants.BASSBOOST_MIN_STRENGTH); 455 456 seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { 457 // Update the parameters while SeekBar changes and set the 458 // effect parameter. 459 460 @Override 461 public void onProgressChanged(final SeekBar seekBar, final int progress, 462 final boolean fromUser) { 463 // set parameter and state 464 ControlPanelEffect.setParameterInt(mContext, mCallingPackageName, 465 ControlPanelEffect.Key.bb_strength, progress); 466 } 467 468 // If slider pos was 0 when starting re-enable effect 469 @Override 470 public void onStartTrackingTouch(final SeekBar seekBar) { 471 if (seekBar.getProgress() == 0) { 472 ControlPanelEffect.setParameterBoolean(mContext, mCallingPackageName, 473 ControlPanelEffect.Key.bb_enabled, true); 474 } 475 } 476 477 // If slider pos = 0 when stopping disable effect 478 @Override 479 public void onStopTrackingTouch(final SeekBar seekBar) { 480 if (seekBar.getProgress() == 0) { 481 // disable 482 ControlPanelEffect.setParameterBoolean(mContext, mCallingPackageName, 483 ControlPanelEffect.Key.bb_enabled, false); 484 } 485 } 486 }); 487 } else { 488 findViewById(R.id.bBLayout).setVisibility(View.GONE); 489 } 490 491 // Initialize the Equalizer elements. 492 if (mEqualizerSupported) { 493 mEQPreset = ControlPanelEffect.getParameterInt(mContext, mCallingPackageName, 494 ControlPanelEffect.Key.eq_current_preset); 495 if (mEQPreset >= mEQPresetNames.length) { 496 mEQPreset = 0; 497 } 498 mEQPresetPrevious = mEQPreset; 499 equalizerSpinnerInit((Spinner)findViewById(R.id.eqSpinner)); 500 equalizerBandsInit(findViewById(R.id.eqcontainer)); 501 } else { 502 findViewById(R.id.eqSpinner).setVisibility(View.GONE); 503 } 504 505 // Initialize the Preset Reverb elements. 506 // Set Spinner listeners. 507 if (mPresetReverbSupported) { 508 mPRPreset = ControlPanelEffect.getParameterInt(mContext, mCallingPackageName, 509 ControlPanelEffect.Key.pr_current_preset); 510 mPRPresetPrevious = mPRPreset; 511 reverbSpinnerInit((Spinner)findViewById(R.id.prSpinner)); 512 } else { 513 findViewById(R.id.prSpinner).setVisibility(View.GONE); 514 } 515 516 ActionBar ab = getActionBar(); 517 final int padding = getResources().getDimensionPixelSize( 518 R.dimen.action_bar_switch_padding); 519 mToggleSwitch.setPadding(0,0, padding, 0); 520 ab.setCustomView(mToggleSwitch, new ActionBar.LayoutParams( 521 ActionBar.LayoutParams.WRAP_CONTENT, 522 ActionBar.LayoutParams.WRAP_CONTENT, 523 Gravity.CENTER_VERTICAL | Gravity.RIGHT)); 524 ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM); 525 } else { 526 viewGroup.setVisibility(View.GONE); 527 ((TextView) findViewById(R.id.noEffectsTextView)).setVisibility(View.VISIBLE); 528 } 529 530 if (com.android.media.audio.Flags.musicFxEdgeToEdge()) { 531 setupEdgeToEdge(); 532 } 533 } 534 535 /* 536 * (non-Javadoc) 537 * 538 * @see android.app.Activity#onResume() 539 */ 540 @Override onResume()541 protected void onResume() { 542 super.onResume(); 543 if ((mVirtualizerSupported) || (mBassBoostSupported) || (mEqualizerSupported) 544 || (mPresetReverbSupported)) { 545 // Monitor AudioDeviceCallback for device change 546 final AudioManager audioManager = getSystemService(AudioManager.class); 547 audioManager.registerAudioDeviceCallback(mMyAudioDeviceCallback, null); 548 mIsHeadsetOn = isHeadSetDeviceConnected(); 549 Log.v(TAG, "onResume: mIsHeadsetOn : " + mIsHeadsetOn); 550 // Update UI 551 updateUI(); 552 } 553 } 554 555 /* 556 * (non-Javadoc) 557 * 558 * @see android.app.Activity#onPause() 559 */ 560 @Override onPause()561 protected void onPause() { 562 super.onPause(); 563 564 // Stop monitoring AudioDeviceCallback, (these affect the visible UI, so we only care about 565 // them while we're in the foreground). 566 if ((mVirtualizerSupported) 567 || (mBassBoostSupported) 568 || (mEqualizerSupported) 569 || (mPresetReverbSupported)) { 570 final AudioManager audioManager = getSystemService(AudioManager.class); 571 audioManager.unregisterAudioDeviceCallback(mMyAudioDeviceCallback); 572 } 573 } 574 reverbSpinnerInit(Spinner spinner)575 private void reverbSpinnerInit(Spinner spinner) { 576 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 577 android.R.layout.simple_spinner_item, PRESETREVERBPRESETSTRINGS); 578 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 579 spinner.setAdapter(adapter); 580 spinner.setOnItemSelectedListener(new OnItemSelectedListener() { 581 582 @Override 583 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 584 if (position != mPRPresetPrevious) { 585 presetReverbSetPreset(position); 586 } 587 mPRPresetPrevious = position; 588 } 589 590 @Override 591 public void onNothingSelected(AdapterView<?> parent) { 592 } 593 }); 594 spinner.setSelection(mPRPreset); 595 } 596 equalizerSpinnerInit(Spinner spinner)597 private void equalizerSpinnerInit(Spinner spinner) { 598 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 599 android.R.layout.simple_spinner_item, mEQPresetNames); 600 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 601 spinner.setAdapter(adapter); 602 spinner.setOnItemSelectedListener(new OnItemSelectedListener() { 603 604 @Override 605 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 606 if (position != mEQPresetPrevious) { 607 equalizerSetPreset(position); 608 } 609 mEQPresetPrevious = position; 610 } 611 612 @Override 613 public void onNothingSelected(AdapterView<?> parent) { 614 } 615 }); 616 spinner.setSelection(mEQPreset); 617 } 618 619 620 /** 621 * En/disables all children for a given view. For linear and relative layout children do this 622 * recursively 623 * 624 * @param viewGroup 625 * @param enabled 626 */ setEnabledAllChildren(final ViewGroup viewGroup, final boolean enabled)627 private void setEnabledAllChildren(final ViewGroup viewGroup, final boolean enabled) { 628 final int count = viewGroup.getChildCount(); 629 for (int i = 0; i < count; i++) { 630 final View view = viewGroup.getChildAt(i); 631 if ((view instanceof ViewGroup)) { 632 final ViewGroup vg = (ViewGroup) view; 633 setEnabledAllChildren(vg, enabled); 634 } 635 view.setEnabled(enabled); 636 } 637 } 638 639 /** 640 * Updates UI (checkbox, seekbars, enabled states) according to the current stored preferences. 641 */ updateUI()642 private void updateUI() { 643 final boolean isEnabled = ControlPanelEffect.getParameterBoolean(mContext, 644 mCallingPackageName, ControlPanelEffect.Key.global_enabled); 645 mToggleSwitch.setChecked(isEnabled); 646 setEnabledAllChildren((ViewGroup) findViewById(R.id.contentSoundEffects), isEnabled); 647 updateUIHeadset(); 648 649 if (mVirtualizerSupported) { 650 SeekBar bar = (SeekBar) findViewById(R.id.vIStrengthSeekBar); 651 Switch sw = (Switch) findViewById(R.id.vIStrengthToggle); 652 int strength = ControlPanelEffect 653 .getParameterInt(mContext, mCallingPackageName, 654 ControlPanelEffect.Key.virt_strength); 655 bar.setProgress(strength); 656 boolean hasStrength = ControlPanelEffect.getParameterBoolean(mContext, 657 mCallingPackageName, ControlPanelEffect.Key.virt_strength_supported); 658 if (hasStrength) { 659 sw.setVisibility(View.GONE); 660 } else { 661 bar.setVisibility(View.GONE); 662 sw.setChecked(sw.isEnabled() && strength != 0); 663 } 664 } 665 if (mBassBoostSupported) { 666 ((SeekBar) findViewById(R.id.bBStrengthSeekBar)).setProgress(ControlPanelEffect 667 .getParameterInt(mContext, mCallingPackageName, 668 ControlPanelEffect.Key.bb_strength)); 669 } 670 if (mEqualizerSupported) { 671 equalizerUpdateDisplay(); 672 } 673 if (mPresetReverbSupported) { 674 int reverb = ControlPanelEffect.getParameterInt( 675 mContext, mCallingPackageName, 676 ControlPanelEffect.Key.pr_current_preset); 677 ((Spinner)findViewById(R.id.prSpinner)).setSelection(reverb); 678 } 679 } 680 681 /** 682 * Updates UI for headset mode. En/disable VI and BB controls depending on headset state 683 * (on/off) if effects are on. Do the inverse for their layouts so they can take over 684 * control/events. 685 */ updateUIHeadset()686 private void updateUIHeadset() { 687 if (mToggleSwitch.isChecked()) { 688 if (mVirtualizerSupported) { 689 // virtualizer is on if headset is on or transaural virtualizer supported 690 final boolean isVirtualizerOn = mIsHeadsetOn || !mVirtualizerIsHeadphoneOnly; 691 ControlPanelEffect.setParameterBoolean( 692 mContext, 693 mCallingPackageName, 694 ControlPanelEffect.Key.virt_enabled, 695 isVirtualizerOn); 696 ((TextView) findViewById(R.id.vIStrengthText)).setEnabled(isVirtualizerOn); 697 ((SeekBar) findViewById(R.id.vIStrengthSeekBar)).setEnabled(isVirtualizerOn); 698 findViewById(R.id.vILayout).setEnabled(!isVirtualizerOn); 699 } 700 if (mBassBoostSupported) { 701 ControlPanelEffect.setParameterBoolean( 702 mContext, 703 mCallingPackageName, 704 ControlPanelEffect.Key.bb_enabled, 705 mIsHeadsetOn); 706 ((TextView) findViewById(R.id.bBStrengthText)).setEnabled(mIsHeadsetOn); 707 ((SeekBar) findViewById(R.id.bBStrengthSeekBar)).setEnabled(mIsHeadsetOn); 708 findViewById(R.id.bBLayout).setEnabled(!mIsHeadsetOn); 709 } 710 } 711 } 712 713 /** 714 * Initializes the equalizer elements. Set the SeekBars and Spinner listeners. 715 */ equalizerBandsInit(View eqcontainer)716 private void equalizerBandsInit(View eqcontainer) { 717 // Initialize the N-Band Equalizer elements. 718 mNumberEqualizerBands = ControlPanelEffect.getParameterInt(mContext, mCallingPackageName, 719 ControlPanelEffect.Key.eq_num_bands); 720 mEQPresetUserBandLevelsPrev = ControlPanelEffect.getParameterIntArray(mContext, 721 mCallingPackageName, ControlPanelEffect.Key.eq_preset_user_band_level); 722 final int[] centerFreqs = ControlPanelEffect.getParameterIntArray(mContext, 723 mCallingPackageName, ControlPanelEffect.Key.eq_center_freq); 724 final int[] bandLevelRange = ControlPanelEffect.getParameterIntArray(mContext, 725 mCallingPackageName, ControlPanelEffect.Key.eq_level_range); 726 mEqualizerMinBandLevel = (int) Math.max(EQUALIZER_MIN_LEVEL, bandLevelRange[0]); 727 final int mEqualizerMaxBandLevel = (int) Math.min(EQUALIZER_MAX_LEVEL, bandLevelRange[1]); 728 729 for (int band = 0; band < mNumberEqualizerBands; band++) { 730 // Unit conversion from mHz to Hz and use k prefix if necessary to display 731 final int centerFreq = centerFreqs[band] / 1000; 732 float centerFreqHz = centerFreq; 733 String unitPrefix = ""; 734 if (centerFreqHz >= 1000) { 735 centerFreqHz = centerFreqHz / 1000; 736 unitPrefix = "k"; 737 } 738 ((TextView) eqcontainer.findViewById(EQViewElementIds[band][0])).setText( 739 format("%.0f ", centerFreqHz) + unitPrefix + "Hz"); 740 mEqualizerSeekBar[band] = (SeekBar) eqcontainer 741 .findViewById(EQViewElementIds[band][1]); 742 eqcontainer.findViewById(EQViewElementIds[band][0]) 743 .setLabelFor(EQViewElementIds[band][1]); 744 mEqualizerSeekBar[band].setMax(mEqualizerMaxBandLevel - mEqualizerMinBandLevel); 745 mEqualizerSeekBar[band].setOnSeekBarChangeListener(this); 746 } 747 748 // Hide the inactive Equalizer bands. 749 for (int band = mNumberEqualizerBands; band < EQUALIZER_MAX_BANDS; band++) { 750 // CenterFreq text 751 eqcontainer.findViewById(EQViewElementIds[band][0]).setVisibility(View.GONE); 752 // SeekBar 753 eqcontainer.findViewById(EQViewElementIds[band][1]).setVisibility(View.GONE); 754 } 755 756 TextView tv = (TextView) findViewById(R.id.maxLevelText); 757 tv.setText(String.format("+%d dB", (int) Math.ceil(mEqualizerMaxBandLevel / 100))); 758 tv = (TextView) findViewById(R.id.centerLevelText); 759 tv.setText("0 dB"); 760 tv = (TextView) findViewById(R.id.minLevelText); 761 tv.setText(String.format("%d dB", (int) Math.floor(mEqualizerMinBandLevel / 100))); 762 equalizerUpdateDisplay(); 763 } 764 format(String format, Object... args)765 private String format(String format, Object... args) { 766 mFormatBuilder.setLength(0); 767 mFormatter.format(format, args); 768 return mFormatBuilder.toString(); 769 } 770 771 /* 772 * For the EQ Band SeekBars 773 * 774 * (non-Javadoc) 775 * 776 * @see android.widget.SeekBar.OnSeekBarChangeListener#onProgressChanged(android 777 * .widget.SeekBar, int, boolean) 778 */ 779 780 @Override onProgressChanged(final SeekBar seekbar, final int progress, final boolean fromUser)781 public void onProgressChanged(final SeekBar seekbar, final int progress, final boolean fromUser) { 782 final int id = seekbar.getId(); 783 784 for (short band = 0; band < mNumberEqualizerBands; band++) { 785 if (id == EQViewElementIds[band][1]) { 786 final short level = (short) (progress + mEqualizerMinBandLevel); 787 if (fromUser) { 788 equalizerBandUpdate(band, level); 789 } 790 break; 791 } 792 } 793 } 794 795 /* 796 * (non-Javadoc) 797 * 798 * @see android.widget.SeekBar.OnSeekBarChangeListener#onStartTrackingTouch(android 799 * .widget.SeekBar) 800 */ 801 802 @Override onStartTrackingTouch(final SeekBar seekbar)803 public void onStartTrackingTouch(final SeekBar seekbar) { 804 // get current levels 805 final int[] bandLevels = ControlPanelEffect.getParameterIntArray(mContext, 806 mCallingPackageName, ControlPanelEffect.Key.eq_band_level); 807 // copy current levels to user preset 808 for (short band = 0; band < mNumberEqualizerBands; band++) { 809 equalizerBandUpdate(band, bandLevels[band]); 810 } 811 equalizerSetPreset(mEQPresetUserPos); 812 ((Spinner)findViewById(R.id.eqSpinner)).setSelection(mEQPresetUserPos); 813 } 814 815 /* 816 * Updates the EQ display when the user stops changing. 817 * 818 * (non-Javadoc) 819 * 820 * @see android.widget.SeekBar.OnSeekBarChangeListener#onStopTrackingTouch(android 821 * .widget.SeekBar) 822 */ 823 824 @Override onStopTrackingTouch(final SeekBar seekbar)825 public void onStopTrackingTouch(final SeekBar seekbar) { 826 equalizerUpdateDisplay(); 827 } 828 829 /** 830 * Updates the EQ by getting the parameters. 831 */ equalizerUpdateDisplay()832 private void equalizerUpdateDisplay() { 833 // Update and show the active N-Band Equalizer bands. 834 final int[] bandLevels = ControlPanelEffect.getParameterIntArray(mContext, 835 mCallingPackageName, ControlPanelEffect.Key.eq_band_level); 836 for (short band = 0; band < mNumberEqualizerBands; band++) { 837 final int level = bandLevels[band]; 838 final int progress = level - mEqualizerMinBandLevel; 839 mEqualizerSeekBar[band].setProgress(progress); 840 } 841 } 842 843 /** 844 * Updates/sets a given EQ band level. 845 * 846 * @param band 847 * Band id 848 * @param level 849 * EQ band level 850 */ equalizerBandUpdate(final int band, final int level)851 private void equalizerBandUpdate(final int band, final int level) { 852 ControlPanelEffect.setParameterInt(mContext, mCallingPackageName, 853 ControlPanelEffect.Key.eq_band_level, level, band); 854 } 855 856 /** 857 * Sets the given EQ preset. 858 * 859 * @param preset 860 * EQ preset id. 861 */ equalizerSetPreset(final int preset)862 private void equalizerSetPreset(final int preset) { 863 ControlPanelEffect.setParameterInt(mContext, mCallingPackageName, 864 ControlPanelEffect.Key.eq_current_preset, preset); 865 equalizerUpdateDisplay(); 866 } 867 868 /** 869 * Sets the given PR preset. 870 * 871 * @param preset 872 * PR preset id. 873 */ presetReverbSetPreset(final int preset)874 private void presetReverbSetPreset(final int preset) { 875 ControlPanelEffect.setParameterInt(mContext, mCallingPackageName, 876 ControlPanelEffect.Key.pr_current_preset, preset); 877 } 878 879 /** 880 * Show msg that headset needs to be plugged. 881 */ showHeadsetMsg()882 private void showHeadsetMsg() { 883 final Context context = getApplicationContext(); 884 final int duration = Toast.LENGTH_SHORT; 885 886 final Toast toast = Toast.makeText(context, getString(R.string.headset_plug), duration); 887 toast.setGravity(Gravity.CENTER, toast.getXOffset() / 2, toast.getYOffset() / 2); 888 toast.show(); 889 } 890 isVirtualizerTransauralSupported()891 private static boolean isVirtualizerTransauralSupported() { 892 Virtualizer virt = null; 893 boolean transauralSupported = false; 894 try { 895 virt = new Virtualizer(0, android.media.AudioSystem.newAudioSessionId()); 896 transauralSupported = virt.canVirtualize(AudioFormat.CHANNEL_OUT_STEREO, 897 Virtualizer.VIRTUALIZATION_MODE_TRANSAURAL); 898 } catch (Exception e) { 899 } finally { 900 if (virt != null) { 901 virt.release(); 902 } 903 } 904 return transauralSupported; 905 } 906 setupEdgeToEdge()907 private void setupEdgeToEdge() { 908 WindowCompat.setDecorFitsSystemWindows(getWindow(), false /* decorFitsSystemWindows */); 909 ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.contentSoundEffects), 910 (v, windowInsets) -> { 911 Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); 912 // Apply the insets paddings to the view. 913 v.setPadding(insets.left, insets.top, insets.right, insets.bottom); 914 915 // Return CONSUMED if you don't want the window insets to keep being 916 // passed down to descendant views. 917 return WindowInsetsCompat.CONSUMED; 918 }); 919 } 920 } 921