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.AudioFormat;
34 import android.media.AudioManager;
35 import android.media.audiofx.AudioEffect;
36 import android.media.audiofx.AudioEffect.Descriptor;
37 import android.media.audiofx.Virtualizer;
38 import android.os.Bundle;
39 import android.util.Log;
40 import android.view.Gravity;
41 import android.view.LayoutInflater;
42 import android.view.MotionEvent;
43 import android.view.View;
44 import android.view.View.OnClickListener;
45 import android.view.View.OnTouchListener;
46 import android.view.ViewGroup;
47 import android.widget.AdapterView;
48 import android.widget.AdapterView.OnItemSelectedListener;
49 import android.widget.ArrayAdapter;
50 import android.widget.CompoundButton;
51 import android.widget.CompoundButton.OnCheckedChangeListener;
52 import android.widget.LinearLayout;
53 import android.widget.ListView;
54 import android.widget.RelativeLayout;
55 import android.widget.SeekBar;
56 import android.widget.SeekBar.OnSeekBarChangeListener;
57 import android.widget.Spinner;
58 import android.widget.Switch;
59 import android.widget.TextView;
60 import android.widget.Toast;
61 
62 import java.util.Formatter;
63 import java.util.Locale;
64 import java.util.UUID;
65 
66 /**
67  *
68  */
69 public 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
onCreate(final Bundle savedInstanceState)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
onResume()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
onPause()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 
reverbSpinnerInit(Spinner spinner)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 
equalizerSpinnerInit(Spinner spinner)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      */
setEnabledAllChildren(final ViewGroup viewGroup, final boolean enabled)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      */
updateUI()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      */
updateUIHeadset()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      */
equalizerBandsInit(View eqcontainer)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 
format(String format, Object... args)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
onProgressChanged(final SeekBar seekbar, final int progress, final boolean fromUser)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
onStartTrackingTouch(final SeekBar seekbar)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
onStopTrackingTouch(final SeekBar seekbar)753     public void onStopTrackingTouch(final SeekBar seekbar) {
754         equalizerUpdateDisplay();
755     }
756 
757     /**
758      * Updates the EQ by getting the parameters.
759      */
equalizerUpdateDisplay()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      */
equalizerBandUpdate(final int band, final int level)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      */
equalizerSetPreset(final int preset)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      */
presetReverbSetPreset(final int preset)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      */
showHeadsetMsg()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 
isVirtualizerTransauralSupported()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