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