package me.barrasso.android.volume.popup;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.media.audiofx.AudioEffect;
import android.media.audiofx.BassBoost;
import android.media.audiofx.Equalizer;
import android.media.audiofx.Virtualizer;
import android.provider.Settings;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import com.android.audiofx.OpenSLESConstants;
import java.util.Arrays;
import java.util.UUID;
import me.barrasso.android.volume.LogUtils;
import me.barrasso.android.volume.R;
import me.barrasso.android.volume.media.StreamResources;
import me.barrasso.android.volume.media.VolumePanelInfo;
import me.barrasso.android.volume.ui.Expandable;
import me.barrasso.android.volume.ui.VerticalSeekBar;
import me.barrasso.android.volume.ui.transition.TransitionCompat;
import static me.barrasso.android.volume.LogUtils.LOGI;
@SuppressWarnings("unused")
public class EqualizerVolumePanel extends VolumePanel
implements Equalizer.OnParameterChangeListener, Spinner.OnItemSelectedListener, Expandable {
public static final String TAG = EqualizerVolumePanel.class.getSimpleName();
private static final int PRIORITY = 20;
private static final int GLOBAL_AUDIO_OUTPUT = 0;
public static final VolumePanelInfo<EqualizerVolumePanel> VOLUME_PANEL_INFO =
new VolumePanelInfo<EqualizerVolumePanel>(EqualizerVolumePanel.class);
protected AudioEffect.Descriptor[] descriptors;
protected Equalizer equalizer;
protected Virtualizer virtualizer;
protected BassBoost bassBoost;
protected short preset;
protected short[] range = new short[2];
protected short[] levels;
protected int[] frequencies;
protected SparseArray<String> presets;
/**
* @see com.android.audiofx.OpenSLESConstants#BASSBOOST_MAX_STRENGTH
* @see com.android.audiofx.OpenSLESConstants#BASSBOOST_MIN_STRENGTH
**/
protected short bassStrength;
/**
* @see com.android.audiofx.OpenSLESConstants#VIRTUALIZER_MAX_STRENGTH
* @see com.android.audiofx.OpenSLESConstants#VIRTUALIZER_MIN_STRENGTH
**/
protected short virtualizerStrength;
public EqualizerVolumePanel(PopupWindowManager pWindowManager) { super(pWindowManager); }
protected void startEqualizer() {
equalizer = getEqualizer();
bassBoost = getBassBoost();
virtualizer = getVirtualizer();
descriptors = AudioEffect.queryEffects();
// Create everything we can now if we've got an equalizer.
if (null != equalizer) {
equalizer.setEnabled(true);
short bands = equalizer.getNumberOfBands();
levels = new short[bands];
range = equalizer.getBandLevelRange();
frequencies = new int[bands];
// Collect the levels and frequencies of each band.
for (short band = 0; band < bands; ++band) {
levels[band] = equalizer.getBandLevel(band);
frequencies[band] = equalizer.getCenterFreq(band) / 1000; // Hz
}
// Collect information about the pre-packaged presets.
short numOfPresets = equalizer.getNumberOfPresets();
preset = equalizer.getCurrentPreset();
presets = new SparseArray<String>(numOfPresets);
for (short preset = 0; preset < numOfPresets; ++preset) {
presets.put(preset, equalizer.getPresetName(preset));
}
}
if (null != virtualizer) {
virtualizer.setEnabled(true);
virtualizerStrength = virtualizer.getRoundedStrength();
}
if (null != bassBoost) {
bassBoost.setEnabled(true);
bassStrength = bassBoost.getRoundedStrength();
}
LOGI(TAG, dump());
}
// Equalizer
private String dump() {
String info = getClass().getSimpleName() + "@{\n";
info += "\tbass_boost=" + isBassBostSupported() + ", \n";
info += "\tvirtualizer=" + isVirtualizerSupported() + ", \n";
info += "\tpresets=[" + presets.toString() + "], \n";
info += "\tlevels=[" + Arrays.toString(levels) + "], \n";
info += "\tfrequencies=[" + Arrays.toString(frequencies) + "], \n";
info += "\trange=[" + Arrays.toString(range) + "], \n";
info += "\tdescriptors=[" + Arrays.toString(getDescriptorNames()) + ']';
info += "\n}";
return info;
}
private void updateEqualizerState() {
updateEqualizerState(equalizer);
}
private void updateEqualizerState(Equalizer eq) {
if (null == eq) return;
preset = eq.getCurrentPreset();
for (short band = 0; band < levels.length; ++band) {
levels[band] = eq.getBandLevel(band);
}
}
/** @see android.media.audiofx.Equalizer.OnParameterChangeListener */
protected void setParameterListener(Equalizer.OnParameterChangeListener listener) {
if (null != equalizer) {
equalizer.setParameterListener(listener);
}
}
/** @see android.media.audiofx.Equalizer#usePreset(short) */
protected void usePreset(final short newPreset) {
if (null != equalizer && presets.indexOfKey(newPreset) >= 0) {
equalizer.usePreset(newPreset);
preset = newPreset;
}
}
/** @return True if an Equalizer is supported on this device. */
protected boolean isEqualizerSupported() {
return (null != equalizer);
}
/** @return True if an Virtualizer is supported on this device. */
protected boolean isVirtualizerSupported() {
return (null != virtualizer && virtualizer.getStrengthSupported());
}
/** @return True if an BassBost is supported on this device. */
protected boolean isBassBostSupported() {
return (null != bassBoost && bassBoost.getStrengthSupported());
}
protected Equalizer getEqualizer() {
try {
// NOTE: this is because Equalization isn't supported on all devices.
return new Equalizer(PRIORITY, GLOBAL_AUDIO_OUTPUT);
} catch (IllegalArgumentException e) {
LogUtils.LOGE("AudioFX", "Equalizer effect not supported", e);
} catch (IllegalStateException e) {
LogUtils.LOGE("AudioFX", "Equalizer cannot get strength supported", e);
} catch (UnsupportedOperationException e) {
LogUtils.LOGE("AudioFX", "Equalizer library not loaded", e);
} catch (RuntimeException e) {
LogUtils.LOGE("AudioFX", "Equalizer effect not found", e);
}
return null;
}
protected BassBoost getBassBoost() {
try {
// NOTE: this is because BassBoost isn't supported on all devices.
return new BassBoost(PRIORITY, GLOBAL_AUDIO_OUTPUT);
} catch (IllegalArgumentException e) {
LogUtils.LOGE("AudioFX", "BassBoost effect not supported", e);
} catch (IllegalStateException e) {
LogUtils.LOGE("AudioFX", "BassBoost cannot get strength supported", e);
} catch (UnsupportedOperationException e) {
LogUtils.LOGE("AudioFX", "BassBoost library not loaded", e);
} catch (RuntimeException e) {
LogUtils.LOGE("AudioFX", "BassBoost effect not found", e);
}
return null;
}
protected Virtualizer getVirtualizer() {
try {
// NOTE: this is because Virtualizer isn't supported on all devices.
return new Virtualizer(PRIORITY, GLOBAL_AUDIO_OUTPUT);
} catch (IllegalArgumentException e) {
LogUtils.LOGE("AudioFX", "Virtualizer effect not supported", e);
} catch (IllegalStateException e) {
LogUtils.LOGE("AudioFX", "Virtualizer cannot get strength supported", e);
} catch (UnsupportedOperationException e) {
LogUtils.LOGE("AudioFX", "Virtualizer library not loaded", e);
} catch (RuntimeException e) {
LogUtils.LOGE("AudioFX", "Virtualizer effect not found", e);
}
return null;
}
/** @see android.media.audiofx.AudioEffect.Descriptor */
protected boolean hasFeature(AudioEffect.Descriptor descriptor) {
return (Arrays.binarySearch(descriptors, descriptor) >= 0);
}
protected UUID[] uuids;
/** @see android.media.audiofx.AudioEffect.Descriptor#uuid */
protected boolean hasFeature(UUID uuid) {
// Build of array of UUIDs to search through.
if (null == uuids || uuids.length == 0) {
uuids = new UUID[descriptors.length];
int i = -1;
for (AudioEffect.Descriptor descriptor : descriptors)
uuids[++i] = descriptor.uuid;
}
return (Arrays.binarySearch(uuids, uuid) >= 0);
}
/** @see android.media.audiofx.AudioEffect.Descriptor#name */
protected String[] getDescriptorNames() {
// Build of array of UUIDs to search through.
String[] names = new String[descriptors.length];
int i = -1;
for (AudioEffect.Descriptor descriptor : descriptors)
names[++i] = descriptor.name;
return names;
}
private void release(AudioEffect effect) {
if (null != effect) {
effect.setControlStatusListener(null);
effect.setEnableStatusListener(null);
if (effect instanceof Equalizer) {
((Equalizer) effect).setParameterListener(null);
} else if (effect instanceof BassBoost) {
((BassBoost) effect).setParameterListener(null);
} else if (effect instanceof Virtualizer) {
((Virtualizer) effect).setParameterListener(null);
}
effect.release();
}
}
/** @return An array of all preset names, in order. */
protected String[] getPresets() {
int key = 0;
String[] names = new String[presets.size()];
for(int i = 0; i < names.length; ++i) {
key = presets.keyAt(i);
names[i] = presets.get(key);
}
return names;
}
public static int calcPercentage(int value, int range) {
return (int) 100.0F * (value / range);
}
// End Equalizer
@Override
public void onParameterChange(Equalizer effect, int status, int id, int qualifier, int value) {
LOGI(TAG, "onParameterChange(" + effect.getDescriptor().name +
", " + status + ", " + id + ", " + qualifier + ", " + value + ')');
updateEqualizerState();
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
LOGI(TAG, "onItemSelected(" + pos + ", " + id + ')');
usePreset((short) pos);
}
@Override public void onNothingSelected(AdapterView<?> parent) { }
SeekBar.OnSeekBarChangeListener levelListener = new SeekBar.OnSeekBarChangeListener() {
@Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (!fromUser) return;
if (isVirtualizerSupported() && seekBar.getId() == R.id.virtualizer) {
virtualizer.setStrength((short) progress);
LOGI(virtualizer.getClass().getSimpleName(), "setStrength(" + progress + ')');
} else if (isBassBostSupported() && seekBar.getId() == R.id.bass_boost) {
bassBoost.setStrength((short) progress);
LOGI(bassBoost.getClass().getSimpleName(), "setStrength(" + progress + ')');
} else if (null != equalizer) {
Object tag = seekBar.getTag();
if (tag instanceof Short) {
short band = (Short) tag;
equalizer.setBandLevel(band, (short) (progress + range[0]));
LOGI(equalizer.getClass().getSimpleName(), "setBandLevel(" + (progress + range[0]) + ')');
}
}
onUserInteraction();
}
@Override public void onStartTrackingTouch(SeekBar seekBar) {}
@Override public void onStopTrackingTouch(SeekBar seekBar) {}
};
protected ViewGroup root, eqRoot;
protected ImageView volumeIcon;
protected LinearLayout levelsRoot;
protected ImageButton expandButton;
protected SeekBar volumeSeekBar;
protected Spinner presetSpinner;
protected SeekBar bassSeekBar, virtualizerSeekBar;
protected TextView bassText, virtualizerText;
@Override
public void onCreate() {
super.onCreate();
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
setParameterListener(this);
root = (ViewGroup) inflater.inflate(R.layout.eq_volume_adjust, null);
eqRoot = (ViewGroup) root.findViewById(R.id.music_panel);
levelsRoot = (LinearLayout) root.findViewById(R.id.slider_group);
volumeSeekBar = (SeekBar) root.findViewById(android.R.id.progress);
volumeIcon = (ImageView) root.findViewById(R.id.stream_icon);
expandButton = (ImageButton) root.findViewById(R.id.expand_button);
volumeSeekBar.setOnSeekBarChangeListener(this);
expandButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) { expand(); }
});
volumeIcon.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) { launchSoundSettings(); }
});
presetSpinner = (Spinner) root.findViewById(R.id.eq_presets);
virtualizerSeekBar = (SeekBar) root.findViewById(R.id.virtualizer);
bassSeekBar = (SeekBar) root.findViewById(R.id.bass_boost);
virtualizerText = (TextView) root.findViewById(R.id.virtualizer_text);
bassText = (TextView) root.findViewById(R.id.bass_boost_text);
startEqualizer();
if (isBassBostSupported()) {
bassSeekBar.setMax(OpenSLESConstants.BASSBOOST_MAX_STRENGTH);
bassSeekBar.setOnSeekBarChangeListener(levelListener);
} else {
bassSeekBar.setVisibility(View.GONE);
bassText.setVisibility(View.GONE);
}
if (isVirtualizerSupported()) {
virtualizerSeekBar.setMax(OpenSLESConstants.VIRTUALIZER_MAX_STRENGTH);
virtualizerSeekBar.setOnSeekBarChangeListener(levelListener);
} else {
virtualizerSeekBar.setVisibility(View.GONE);
virtualizerText.setVisibility(View.GONE);
}
// Create our Spinner for Equalizer presets.
if (presets != null && presets.size() > 0) {
presetSpinner.setSelection(preset);
ArrayAdapter<String> presetAdapter = new ArrayAdapter<String>(context,
android.R.layout.simple_spinner_item, getPresets());
presetAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
presetSpinner.setAdapter(presetAdapter);
presetSpinner.setOnItemSelectedListener(this);
presetSpinner.setOnTouchListener(new View.OnTouchListener() {
@Override public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP)
onUserInteraction();
return view.onTouchEvent(motionEvent);
}
});
} else {
// No Presets? Guess we won't display that!
root.findViewById(R.id.eq_presets_container).setVisibility(View.GONE);
}
// KitKat+ add support for default transitions.
TransitionCompat transition = TransitionCompat.get();
transition.beginDelayedTransition((ViewGroup) root.findViewById(R.id.slider_group));
initEqualizer();
mLayout = root;
}
protected SeekBar[] levelSeekBars;
protected void initEqualizer() {
if (levels == null || levels.length == 0) {
levelsRoot.setVisibility(View.GONE);
return;
}
Context context = getContext();
levelSeekBars = new SeekBar[levels.length];
levelsRoot.setWeightSum(levels.length);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
(int) dpToPx(10), ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.weight = 1;
layoutParams.gravity = (Gravity.CENTER_HORIZONTAL);
layoutParams.setMargins(0, 10, 0, 10);
for (short band = 0; band < levels.length; ++band) {
levelSeekBars[band] = new VerticalSeekBar(context);
levelSeekBars[band].setMax(range[1] - range[0]); // max - min
levelSeekBars[band].setProgress(levels[band]);
levelSeekBars[band].setLayoutParams(layoutParams);
levelSeekBars[band].setTag(band);
levelSeekBars[band].setEnabled(true);
levelSeekBars[band].setOnSeekBarChangeListener(levelListener);
levelsRoot.addView(levelSeekBars[band]);
}
}
protected float dpToPx(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dp, getResources().getDisplayMetrics());
}
protected void launchSoundSettings() {
hide();
startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
}
// Expansion
@Override public void hide() {
super.hide();
if (isExpanded()) collapse();
}
@Override
public boolean isExpanded() {
return (null != expandButton && expandButton.getVisibility() != View.VISIBLE);
}
@Override
public void expand() {
LOGI(TAG, "expand()");
expandButton.setVisibility(View.GONE);
eqRoot.setVisibility(View.VISIBLE);
onUserInteraction();
}
@Override
public void collapse() {
LOGI(TAG, "collapse()");
expandButton.setVisibility(View.VISIBLE);
eqRoot.setVisibility(View.GONE);
}
// End Expansion
@Override
public void onStreamVolumeChange(int streamType, int volume, int max) {
LOGI(TAG, "onStreamVolumeChange(" + streamType + ", " + volume + ", " + max + ")");
StreamResources resources = StreamResources.resourceForStreamType(streamType);
resources.setVolume(volume);
int iconRes = ((volume <= 0) ? resources.getIconMuteRes() : resources.getIconRes());
Drawable drawable = getResources().getDrawable(iconRes);
drawable.mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
LayerDrawable layer = (LayerDrawable) volumeSeekBar.getProgressDrawable();
layer.findDrawableByLayerId(android.R.id.progress).mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
volumeIcon.setImageDrawable(drawable);
volumeSeekBar.setMax(max);
volumeSeekBar.setProgress(volume);
volumeSeekBar.setTag(resources);
show();
}
@Override public boolean isInteractive() { return true; }
@Override public WindowManager.LayoutParams getWindowLayoutParams() {
int flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED );
WindowManager.LayoutParams WPARAMS = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, 0,
getResources().getDimensionPixelSize(R.dimen.volume_panel_top),
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, flags, PixelFormat.TRANSLUCENT);
WPARAMS.windowAnimations = android.R.style.Animation_Dialog;
WPARAMS.packageName = getContext().getPackageName();
WPARAMS.setTitle(TAG);
WPARAMS.gravity = (Gravity.FILL_HORIZONTAL | Gravity.TOP);
WPARAMS.screenBrightness = WPARAMS.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
return WPARAMS;
}
protected void removeOnSeekBarChangeListeners(SeekBar... seekBars) {
for (SeekBar seekBar : seekBars)
seekBar.setOnSeekBarChangeListener(null);
}
@Override
public void onDestroy() {
super.onDestroy();
uuids = null;
descriptors = null;
removeOnSeekBarChangeListeners(bassSeekBar, virtualizerSeekBar, volumeSeekBar);
removeOnSeekBarChangeListeners(levelSeekBars);
presetSpinner.setOnItemSelectedListener(null);
volumeIcon.setOnClickListener(null);
expandButton.setOnClickListener(null);
release(equalizer);
release(bassBoost);
release(virtualizer);
bassBoost = null;
virtualizer = null;
equalizer = null;
}
}