package com.simplecity.amp_library.ui.activities;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.media.audiofx.AudioEffect;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.CompoundButton;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import com.readystatesoftware.systembartint.SystemBarTintManager;
import com.simplecity.amp_library.R;
import com.simplecity.amp_library.constants.OpenSLESConstants;
import com.simplecity.amp_library.services.EqualizerService;
import com.simplecity.amp_library.ui.adapters.RobotoSpinnerAdapter;
import com.simplecity.amp_library.ui.views.CustomSwitch;
import com.simplecity.amp_library.ui.views.SizableSeekBar;
import com.simplecity.amp_library.utils.ColorUtils;
import com.simplecity.amp_library.utils.MusicUtils;
import com.simplecity.amp_library.utils.SettingsManager;
import com.simplecity.amp_library.utils.ShuttleUtils;
import com.simplecity.amp_library.utils.ThemeUtils;
import java.lang.ref.WeakReference;
import java.util.Formatter;
import java.util.Locale;
import java.util.UUID;
public class EqualizerActivity extends BaseActivity {
private static final String TAG = "EqualizerActivity";
private SystemBarTintManager mTintManager;
private static final String EFFECT_TYPE_EQUALIZER = "47382d60-ddd8-11db-bf3a-0002a5d5c51b";
private static final String EFFECT_TYPE_BASS_BOOST = "0634f220-ddd4-11db-a0fc-0002a5d5c51b";
private static final String EFFECT_TYPE_VIRTUALIZER = "37cc2c00-dddd-11db-8577-0002a5d5c51b";
SharedPreferences mPrefs;
/**
* Max number of EQ bands supported
*/
private final static int EQUALIZER_MAX_BANDS = 6;
/**
* Indicates if Equalizer effect is supported.
*/
private boolean mEqualizerSupported;
/**
* Indicates if BassBoost effect is supported.
*/
private boolean mBassBoostSupported;
/**
* Indicates if Virtualizer effect is supported.
*/
private boolean mVirtualizerSupported;
private ServiceConnection mServiceConnection;
public EqualizerService service;
// Equalizer fields
private int mNumberEqualizerBands;
int mEqCustomPresetPosition = 1;
int mEqPreset;
private String[] mEqPresetNames;
private final SizableSeekBar[] mEqualizerSeekBar = new SizableSeekBar[EQUALIZER_MAX_BANDS];
Spinner mSpinner;
RobotoSpinnerAdapter mSpinnerAdapter;
private CustomSwitch mInternalEqSwitch;
private StringBuilder mFormatBuilder = new StringBuilder();
private Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
/**
* Mapping for the EQ widget ids per band
*/
static final int[][] eqViewElementIds = {
{R.id.EqBand0TopTextView, R.id.EqBand0SeekBar},
{R.id.EqBand1TopTextView, R.id.EqBand1SeekBar},
{R.id.EqBand2TopTextView, R.id.EqBand2SeekBar},
{R.id.EqBand3TopTextView, R.id.EqBand3SeekBar},
{R.id.EqBand4TopTextView, R.id.EqBand4SeekBar},
{R.id.EqBand5TopTextView, R.id.EqBand5SeekBar}
};
/**
* Mapping for the EQ widget ids per band
*/
private static final int[][] eqViewTextElementIds = {
{R.id.EqBand0LeftTextView, R.id.EqBand0RightTextView},
{R.id.EqBand1LeftTextView, R.id.EqBand1RightTextView},
{R.id.EqBand2LeftTextView, R.id.EqBand2RightTextView},
{R.id.EqBand3LeftTextView, R.id.EqBand3RightTextView},
{R.id.EqBand4LeftTextView, R.id.EqBand4RightTextView},
{R.id.EqBand5LeftTextView, R.id.EqBand5RightTextView}
};
@Override
public void themeColorChanged() {
}
@Override
protected String screenName() {
return TAG;
}
private static class UpdateHandler extends Handler {
private final WeakReference<EqualizerActivity> mActivity;
public UpdateHandler(EqualizerActivity equalizerActivity) {
mActivity = new WeakReference<>(equalizerActivity);
}
@Override
public void handleMessage(Message msg) {
final EqualizerActivity activity = mActivity.get();
if (activity == null) {
Log.w(TAG, "Active null");
return;
}
super.handleMessage(msg);
switch (msg.what) {
case MSG_UPDATE_SERVICE:
if (activity.service != null) {
activity.service.update();
} else {
Log.w(TAG, "Service null");
}
break;
}
}
}
UpdateHandler mHandler = new UpdateHandler(this);
@SuppressLint("InlinedApi")
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
@Override
public void onCreate(Bundle savedInstanceState) {
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
ThemeUtils.setTheme(this);
if (!ShuttleUtils.hasLollipop() && ShuttleUtils.hasKitKat()) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
mTintManager = new SystemBarTintManager(this);
}
if (!ShuttleUtils.hasKitKat()) {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
}
if (SettingsManager.getInstance().canTintNavBar()) {
getWindow().setNavigationBarColor(ColorUtils.getPrimaryColorDark(this));
}
super.onCreate(savedInstanceState);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
setContentView(R.layout.activity_equalizer);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
ThemeUtils.themeActionBar(this);
ThemeUtils.themeStatusBar(this, mTintManager);
ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(getResources().getString(R.string.equalizer));
try {
//Query available effects
final AudioEffect.Descriptor[] effects = AudioEffect.queryEffects();
//Determine available/supported effects
if (effects != null && effects.length != 0) {
for (final AudioEffect.Descriptor effect : effects) {
//Equalizer
if (effect.type.equals(UUID.fromString(EFFECT_TYPE_EQUALIZER))) {
mEqualizerSupported = true;
} else if (effect.type.equals(UUID.fromString(EFFECT_TYPE_BASS_BOOST))) {
mBassBoostSupported = true;
} else if (effect.type.equals(UUID.fromString(EFFECT_TYPE_VIRTUALIZER))) {
mVirtualizerSupported = true;
}
}
}
} catch (NoClassDefFoundError ignored) {
//The user doesn't have the AudioEffect/AudioEffect.Descriptor class. How sad.
}
mSpinner = (Spinner) findViewById(R.id.eqSpinner);
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mEqPreset = position;
equalizerSetPreset(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
setupPresets();
if (mSpinnerAdapter != null && mSpinnerAdapter.getCount() > mEqPreset) {
mSpinner.setSelection(mEqPreset);
}
//Initialize the equalizer elements
View eqContainer = findViewById(R.id.eqContainer);
mNumberEqualizerBands = Integer.parseInt(mPrefs.getString("equalizer.number_of_bands", "5"));
final int[] centerFreqs = getCenterFreqs();
final int[] bandLevelRange = getBandLevelRange();
for (int band = 0; band < mNumberEqualizerBands; band++) {
//Unit conversion from mHz to Hz and use k prefix if necessary to display
float centerFreqHz = centerFreqs[band] / 1000;
String unitPrefix = "";
if (centerFreqHz >= 1000) {
centerFreqHz = centerFreqHz / 1000;
unitPrefix = "k";
}
(eqContainer.findViewById(eqViewElementIds[band][0])).setVisibility(View.VISIBLE);
(eqContainer.findViewById(eqViewTextElementIds[band][0])).setVisibility(View.VISIBLE);
(eqContainer.findViewById(eqViewElementIds[band][1])).setVisibility(View.VISIBLE);
(eqContainer.findViewById(eqViewTextElementIds[band][1])).setVisibility(View.VISIBLE);
((TextView) eqContainer.findViewById(eqViewElementIds[band][0])).setText(format("%.0f ", centerFreqHz) + unitPrefix + "Hz");
mEqualizerSeekBar[band] = (SizableSeekBar) eqContainer.findViewById(eqViewElementIds[band][1]);
ThemeUtils.themeSeekBar(this, mEqualizerSeekBar[band]);
mEqualizerSeekBar[band].setMax((bandLevelRange[1] / 100) - (bandLevelRange[0] / 100));
mEqualizerSeekBar[band].setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {
if (fromUser) {
//Determine which band changed
int seekbarId = seekBar.getId();
int band = 0;
for (int i = 0; i < eqViewElementIds.length; i++) {
if (eqViewElementIds[i][1] == seekbarId) {
band = i;
}
}
if (mEqPreset != mEqCustomPresetPosition) {
equalizerCopyToCustom();
if (mSpinnerAdapter != null && mSpinnerAdapter.getCount() > mEqCustomPresetPosition) {
mSpinner.setSelection(mEqCustomPresetPosition);
}
} else {
int level = getBandLevelRange()[0] + (progress * 100);
equalizerBandUpdate(band, level);
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
}
});
}
// Initialize the Bass Boost elements.
// Set the SeekBar listener.
if (mBassBoostSupported) {
final SizableSeekBar seekbar = (SizableSeekBar) findViewById(R.id.bb_strength);
ThemeUtils.themeSeekBar(this, seekbar);
seekbar.setMax(OpenSLESConstants.BASSBOOST_MAX_STRENGTH - OpenSLESConstants.BASSBOOST_MIN_STRENGTH);
seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {
// set parameter and state
if (fromUser) {
mPrefs.edit().putBoolean("audiofx.bass.enable", true).apply();
mPrefs.edit().putString("audiofx.bass.strength", String.valueOf(progress)).apply();
mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
}
}
// If slider pos was 0 when starting re-enable effect
@Override
public void onStartTrackingTouch(final SeekBar seekBar) {
if (seekBar.getProgress() == 0) {
mPrefs.edit().putBoolean("audiofx.bass.enable", true).apply();
mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
}
}
// If slider pos = 0 when stopping disable effect
@Override
public void onStopTrackingTouch(final SeekBar seekBar) {
if (seekBar.getProgress() == 0) {
// disable
mPrefs.edit().putBoolean("audiofx.bass.enable", false).apply();
mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
}
}
});
}
// Initialize the Virtualizer elements.
// Set the SeekBar listener.
if (mVirtualizerSupported) {
final SizableSeekBar seekbar = (SizableSeekBar) findViewById(R.id.virtualizer_strength);
ThemeUtils.themeSeekBar(this, seekbar);
seekbar.setMax(OpenSLESConstants.VIRTUALIZER_MAX_STRENGTH - OpenSLESConstants.VIRTUALIZER_MIN_STRENGTH);
seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {
// set parameter and state
if (fromUser) {
mPrefs.edit().putBoolean("audiofx.virtualizer.enable", true).apply();
mPrefs.edit().putString("audiofx.virtualizer.strength", String.valueOf(progress)).apply();
mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
}
}
// If slider pos was 0 when starting re-enable effect
@Override
public void onStartTrackingTouch(final SeekBar seekBar) {
if (seekBar.getProgress() == 0) {
mPrefs.edit().putBoolean("audiofx.virtualizer.enable", true).apply();
mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
}
}
// If slider pos = 0 when stopping disable effect
@Override
public void onStopTrackingTouch(final SeekBar seekBar) {
if (seekBar.getProgress() == 0) {
// disable
mPrefs.edit().putBoolean("audiofx.virtualizer.enable", false).apply();
mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
}
}
});
}
}
@Override
protected void onResume() {
super.onResume();
if (mServiceConnection == null) {
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
service = ((EqualizerService.LocalBinder) binder).getService();
updateUI();
}
@Override
public void onServiceDisconnected(ComponentName name) {
service = null;
}
};
}
Intent serviceIntent = new Intent(this, EqualizerService.class);
bindService(serviceIntent, mServiceConnection, 0);
updateUI();
}
@Override
protected void onPause() {
unbindService(mServiceConnection);
super.onPause();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_equalizer_activity, menu);
MenuItem switchItem = menu.findItem(R.id.action_equalizer);
mInternalEqSwitch = (CustomSwitch) MenuItemCompat.getActionView(switchItem);
mInternalEqSwitch.setOnCheckedChangeListener(mOnCheckedChangeListener);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final boolean isEnabled = mPrefs.getBoolean("audiofx.global.enable", false);
MenuItem switchItem = menu.findItem(R.id.action_equalizer);
mInternalEqSwitch = (CustomSwitch) MenuItemCompat.getActionView(switchItem);
if (mInternalEqSwitch != null) {
mInternalEqSwitch.setOnCheckedChangeListener(null);
mInternalEqSwitch.setChecked(isEnabled);
mInternalEqSwitch.setOnCheckedChangeListener(mOnCheckedChangeListener);
}
//Hide the 'open DSP' button if DSP/Other audio effects aren't available
final Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
if (getPackageManager().resolveActivity(intent, 0) == null) {
MenuItem openDSPItem = menu.findItem(R.id.menu_DSP);
if (openDSPItem != null) {
openDSPItem.setVisible(false);
}
}
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_DSP) {
try {
final Intent openDSP = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
startActivityForResult(openDSP, 1000);
} catch (final ActivityNotFoundException notFound) {
Log.e(TAG, "External EQ not found. " + notFound.getLocalizedMessage());
}
} else if (item.getItemId() == android.R.id.home) {
finish();
}
return true;
}
/**
* Sets the given EQ preset.
*
* @param preset EQ preset id.
*/
void equalizerSetPreset(final int preset) {
mEqPreset = preset;
mPrefs.edit().putString("audiofx.eq.preset", String.valueOf(preset)).apply();
String newLevels;
if (preset == mEqCustomPresetPosition) {
// load custom if possible
newLevels = mPrefs.getString("audiofx.eq.bandlevels.custom", EqualizerService.getZeroedBandsString(mNumberEqualizerBands));
} else {
newLevels = mPrefs.getString("equalizer.preset." + preset, EqualizerService.getZeroedBandsString(mNumberEqualizerBands));
}
mPrefs.edit().putString("audiofx.eq.bandlevels", newLevels).apply();
updateUI();
updateService();
}
void updateUI() {
setupPresets();
supportInvalidateOptionsMenu();
if (mEqualizerSupported) {
equalizerUpdateDisplay();
}
if (mBassBoostSupported) {
((SeekBar) findViewById(R.id.bb_strength)).setProgress(Integer.valueOf(mPrefs.getString("audiofx.bass.strength", "0")));
}
if (mVirtualizerSupported) {
((SeekBar) findViewById(R.id.virtualizer_strength)).setProgress(Integer.valueOf(mPrefs.getString("audiofx.virtualizer.strength", "0")));
}
// Initialize the Equalizer elements.
if (mEqualizerSupported) {
String preset = String.valueOf(mNumberEqualizerBands);
mEqPreset = Integer.valueOf(mPrefs.getString("audiofx.eq.preset", preset));
if (mSpinnerAdapter != null && mSpinnerAdapter.getCount() > mEqPreset) {
mSpinner.setSelection(mEqPreset);
}
}
}
/**
* Updates the EQ by getting the parameters.
*/
private void equalizerUpdateDisplay() {
String levelsString;
float[] floats;
if (mEqPreset == mEqCustomPresetPosition) {
// load custom preset for current device
// here mEQValues needs to be pre-populated with the user's preset values.
String[] customEq = mPrefs.getString("audiofx.eq.bandlevels.custom", EqualizerService.getZeroedBandsString(mNumberEqualizerBands)).split(";");
floats = new float[mNumberEqualizerBands];
for (int band = 0; band < floats.length; band++) {
final float level = Float.parseFloat(customEq[band]);
floats[band] = level / 100.0f;
mEqualizerSeekBar[band].setProgress((int) ((getBandLevelRange()[1] / 100.0f) + (level / 100.0f)));
}
} else {
// try to load preset
levelsString = mPrefs.getString("equalizer.preset." + mEqPreset, EqualizerService.getZeroedBandsString(mNumberEqualizerBands));
String[] bandLevels = levelsString.split(";");
floats = new float[bandLevels.length];
for (int band = 0; band < bandLevels.length; band++) {
final float level = Float.parseFloat(bandLevels[band]);
floats[band] = level / 100.0f;
mEqualizerSeekBar[band].setProgress((int) ((getBandLevelRange()[1] / 100.0f) + (level / 100.0f)));
}
}
}
void equalizerBandUpdate(final int band, final int level) {
String[] currentCustomLevels = mPrefs.getString("audiofx.eq.bandlevels.custom", EqualizerService.getZeroedBandsString(mNumberEqualizerBands)).split(";");
currentCustomLevels[band] = String.valueOf(level);
// save
StringBuilder builder = new StringBuilder();
for (int i = 0; i < mNumberEqualizerBands; i++) {
builder.append(currentCustomLevels[i]);
builder.append(";");
}
builder.deleteCharAt(builder.length() - 1);
mPrefs.edit().putString("audiofx.eq.bandlevels", builder.toString()).apply();
mPrefs.edit().putString("audiofx.eq.bandlevels.custom", builder.toString()).apply();
updateService();
}
/**
* Called when user starts touch eq on a preset
*/
void equalizerCopyToCustom() {
Log.d(TAG, "equalizerCopyToCustom()");
StringBuilder bandLevels = new StringBuilder();
for (int band = 0; band < mNumberEqualizerBands; band++) {
final float level = (getBandLevelRange()[0] / 100) + mEqualizerSeekBar[band].getProgress();
bandLevels.append(level * 100);
bandLevels.append(";");
}
// remove trailing ";"
bandLevels.deleteCharAt(bandLevels.length() - 1);
mPrefs.edit().putString("audiofx.eq.bandlevels.custom", bandLevels.toString()).apply();
mPrefs.edit().putString("audiofx.eq.preset", String.valueOf(mEqCustomPresetPosition)).apply();
}
private String format(String format, Object... args) {
mFormatBuilder.setLength(0);
mFormatter.format(format, args);
return mFormatBuilder.toString();
}
private static final int MSG_UPDATE_SERVICE = 1;
void updateService() {
mHandler.removeMessages(MSG_UPDATE_SERVICE);
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SERVICE, 100);
}
int[] getBandLevelRange() {
String savedCenterFreqs = mPrefs.getString("equalizer.band_level_range", null);
if (savedCenterFreqs == null || savedCenterFreqs.isEmpty()) {
return new int[]{-1500, 1500};
} else {
String[] split = savedCenterFreqs.split(";");
int[] freqs = new int[split.length];
for (int i = 0; i < split.length; i++) {
freqs[i] = Integer.valueOf(split[i]);
}
return freqs;
}
}
private int[] getCenterFreqs() {
String savedCenterFreqs = mPrefs.getString("equalizer.center_freqs", EqualizerService.getZeroedBandsString(mNumberEqualizerBands));
String[] split = savedCenterFreqs.split(";");
int[] freqs = new int[split.length];
for (int i = 0; i < split.length; i++) {
freqs[i] = Integer.valueOf(split[i]);
}
return freqs;
}
private void setupPresets() {
// setup equalizer presets
final int numPresets = Integer.parseInt(mPrefs.getString("equalizer.number_of_presets", "0"));
mEqPresetNames = new String[numPresets + 1];
String[] presetNames = mPrefs.getString("equalizer.preset_names", "").split("\\|");
for (short i = 0; i < numPresets; i++) {
mEqPresetNames[i] = presetNames[i];
}
mEqPresetNames[numPresets] = getString(R.string.custom);
mEqCustomPresetPosition = numPresets;
if (mSpinnerAdapter == null || mSpinnerAdapter.getCount() != mEqPresetNames.length) {
mSpinnerAdapter = new RobotoSpinnerAdapter<>(this, R.layout.spinner_item, mEqPresetNames);
mSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mSpinner.setAdapter(mSpinnerAdapter);
}
}
private CompoundButton.OnCheckedChangeListener mOnCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
EqualizerService.closeEqualizerSessions(EqualizerActivity.this, !isChecked, MusicUtils.getAudioSessionId());
EqualizerService.openEqualizerSession(EqualizerActivity.this, isChecked, MusicUtils.getAudioSessionId());
// set parameter and state
mPrefs.edit().putBoolean("audiofx.global.enable", isChecked).apply();
updateService();
}
};
}