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