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