summaryrefslogtreecommitdiffstats
path: root/src/org/cyanogenmod/audiofx
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/cyanogenmod/audiofx')
-rw-r--r--src/org/cyanogenmod/audiofx/ActivityMusic.java789
-rw-r--r--src/org/cyanogenmod/audiofx/BootReceiver.java14
-rw-r--r--src/org/cyanogenmod/audiofx/HeadsetService.java708
-rw-r--r--src/org/cyanogenmod/audiofx/OpenSLESConstants.java124
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/AudioFxApplication.java47
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/Compatibility.java (renamed from src/org/cyanogenmod/audiofx/Compatibility.java)22
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/Constants.java160
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/Preset.java236
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/activity/ActivityMusic.java198
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/activity/ControlPanelPicker.java (renamed from src/org/cyanogenmod/audiofx/ControlPanelPicker.java)22
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/activity/EqualizerManager.java634
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/activity/MasterConfigControl.java367
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/activity/StateCallbacks.java154
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/backends/AndroidEffects.java185
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/backends/EffectSet.java235
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/backends/EffectSetWithAndroidEq.java123
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/backends/IEffectFactory.java16
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/eq/EqBarView.java208
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/eq/EqContainerView.java518
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/eq/EqSwipeController.java133
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/eq/EqUtils.java149
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/fragment/AudioFxBaseFragment.java66
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/fragment/AudioFxFragment.java489
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/fragment/ControlsFragment.java103
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/fragment/EqualizerFragment.java559
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/knobs/KnobCommander.java177
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/knobs/KnobContainer.java380
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/knobs/RadialKnob.java570
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/preset/InfinitePagerAdapter.java95
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/preset/InfiniteViewPager.java106
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/preset/PresetPagerAdapter.java82
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/receiver/QuickSettingsTileReceiver.java36
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/receiver/ServiceDispatcher.java (renamed from src/org/cyanogenmod/audiofx/ServiceDispatcher.java)29
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/service/AudioFxService.java347
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/service/AudioOutputChangeListener.java131
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/service/BootReceiver.java14
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/service/DevicePreferenceManager.java294
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/service/SessionManager.java422
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/stats/AppState.java41
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/stats/UserSession.java202
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/viewpagerindicator/CirclePageIndicator.java505
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/viewpagerindicator/PageIndicator.java63
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/widget/Biquad.java (renamed from src/org/cyanogenmod/audiofx/widget/Biquad.java)8
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/widget/Complex.java (renamed from src/org/cyanogenmod/audiofx/widget/Complex.java)22
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/widget/EqualizerSurface.java (renamed from src/org/cyanogenmod/audiofx/widget/EqualizerSurface.java)15
-rw-r--r--src/org/cyanogenmod/audiofx/audiofx/widget/InterceptableLinearLayout.java (renamed from src/org/cyanogenmod/audiofx/widget/InterceptableLinearLayout.java)7
-rw-r--r--src/org/cyanogenmod/audiofx/widget/Gallery.java120
-rw-r--r--src/org/cyanogenmod/audiofx/widget/Knob.java434
48 files changed, 8112 insertions, 2247 deletions
diff --git a/src/org/cyanogenmod/audiofx/ActivityMusic.java b/src/org/cyanogenmod/audiofx/ActivityMusic.java
deleted file mode 100644
index 883b127..0000000
--- a/src/org/cyanogenmod/audiofx/ActivityMusic.java
+++ /dev/null
@@ -1,789 +0,0 @@
-/*
- * Copyright (C) 2014 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.cyanogenmod.audiofx;
-
-import android.app.ActionBar;
-import android.app.Activity;
-import android.content.*;
-import android.graphics.drawable.ColorDrawable;
-import android.media.audiofx.AudioEffect;
-import android.media.audiofx.AudioEffect.Descriptor;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.*;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-import org.cyanogenmod.audiofx.widget.EqualizerSurface;
-import org.cyanogenmod.audiofx.widget.Gallery;
-import org.cyanogenmod.audiofx.widget.InterceptableLinearLayout;
-import org.cyanogenmod.audiofx.widget.Knob;
-import org.cyanogenmod.audiofx.widget.Knob.OnKnobChangeListener;
-
-import java.util.UUID;
-
-/**
- *
- */
-public class ActivityMusic extends Activity {
-
- private final static String TAG = "AudioFXActivityMusic";
- private final static boolean DEBUG = false;
-
- /**
- * Max number of EQ bands supported
- */
- private final static int EQUALIZER_MAX_BANDS = 32;
-
- /**
- * Indicates if Virtualizer effect is supported.
- */
- private boolean mVirtualizerSupported;
- private boolean mVirtualizerIsHeadphoneOnly;
- /**
- * Indicates if BassBoost effect is supported.
- */
- private boolean mBassBoostSupported;
- /**
- * Indicates if Equalizer effect is supported.
- */
- private boolean mEqualizerSupported;
- /**
- * Indicates if Preset Reverb effect is supported.
- */
- private boolean mPresetReverbSupported;
- private ServiceConnection mServiceConnection;
-
- // Equalizer fields
- private int mNumberEqualizerBands;
- private int mEQCustomPresetPosition = 1;
- private int mEQPreset;
- private String[] mEQPresetNames;
- private String[] mReverbPresetNames;
-
- private int mPRPreset;
-
- private boolean mEQAnimatingToUserPos = false;
-
- private ViewGroup mContentEffectsViewGroup;
- private EqualizerSurface mEqualizerSurface;
- private Gallery mEqGallery;
- private Gallery mReverbGallery;
- private Knob mVirtualizerKnob;
- private Knob mBassKnob;
-
- private boolean mKnobsAvailable = false;
- private Switch mToggleSwitch;
-
- private boolean mStandalone = false;
- private boolean mStateChangeUpdate = false;
-
- private Toast mCurrentToast;
-
- HeadsetService mService;
-
- private String mCurrentDevice = "speaker"; // the sensible default
-
- private static final int[] mReverbPresetRSids = {
- R.string.none, R.string.smallroom, R.string.mediumroom, R.string.largeroom,
- R.string.mediumhall, R.string.largehall, R.string.plate
- };
-
- private Context mContext;
-
- private int mAudioSession = AudioEffect.ERROR_BAD_VALUE;
-
- private static final int MSG_UPDATE_EQ = 1;
- private static final int MSG_UPDATE_SERVICE = 2;
- private static final int MSG_UPDATE_EQ_ANIMATE = 3;
- Handler mHandler = new Handler() {
-
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- switch (msg.what) {
- case MSG_UPDATE_EQ:
- equalizerUpdateDisplayInternal(false);
- break;
- case MSG_UPDATE_SERVICE:
- if (mService != null) {
- mService.update();
- }
- break;
- case MSG_UPDATE_EQ_ANIMATE:
- equalizerUpdateDisplayInternal(true);
- break;
- }
- }
- };
-
- // Broadcast receiver to handle wired and Bluetooth A2dp headset events
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- final String action = intent.getAction();
-
- if (action.equals(HeadsetService.ACTION_UPDATE_PREFERENCES)) {
- if (mCurrentDeviceOverride == false) { // the user has selected a device, don't interrupt them.
- if (mService != null) {
- mCurrentDevice = mService.getAudioOutputRouting();
- }
- }
-
- updateUI(true);
- mStateChangeUpdate = true;
- getActionBar().setSelectedNavigationItem(getCurrentDeviceIndex());
- equalizerSetPreset(mEQPreset);
- equalizerUpdateDisplay(true);
- }
- }
- };
- private ArrayAdapter<String> mNavBarDeviceAdapter;
-
- private boolean mCurrentDeviceOverride = false;
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- startService(new Intent(this, HeadsetService.class));
-
- // Init context to be used in listeners
- mContext = this;
- // Receive intent
- // get calling intent
- final Intent intent = getIntent();
- mAudioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION,
- AudioEffect.ERROR_BAD_VALUE);
- Log.v(TAG, "audio session: " + mAudioSession);
-
- // check for errors
- if (getCallingPackage() == null) {
- mStandalone = true;
- } else {
- mStandalone = false;
- }
- setResult(RESULT_OK);
-
- // query available effects
- final Descriptor[] effects = AudioEffect.queryEffects();
-
- // Determine available/supported effects
- if (DEBUG) Log.v(TAG, "Available effects:");
- for (final Descriptor effect : effects) {
- if (DEBUG) Log.v(TAG, effect.name.toString() + ", type: " + effect.type.toString());
-
- if (effect.type.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) {
- mVirtualizerSupported = true;
- if (effect.uuid.equals(UUID.fromString("1d4033c0-8557-11df-9f2d-0002a5d5c51b"))
- || effect.uuid.equals(UUID.fromString("e6c98a16-22a3-11e2-b87b-f23c91aec05e"))
- || effect.uuid.equals(UUID.fromString("d3467faa-acc7-4d34-acaf-0002a5d5c51b"))) {
- mVirtualizerIsHeadphoneOnly = true;
- }
- } else if (effect.type.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)) {
- mBassBoostSupported = true;
- } else if (effect.type.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)) {
- mEqualizerSupported = true;
- } else if (effect.type.equals(AudioEffect.EFFECT_TYPE_PRESET_REVERB)) {
- mPresetReverbSupported = true;
- }
- }
-
- setContentView(R.layout.music_main);
-
- mContentEffectsViewGroup = (ViewGroup) findViewById(R.id.contentSoundEffects);
-
- // fix up labels
- TextView reverbLabel = (TextView) findViewById(R.id.reverb_label);
- reverbLabel.setText("- " + reverbLabel.getText() + " -");
-
- TextView eqPresetLabel = (TextView) findViewById(R.id.eq_preset_label);
- eqPresetLabel.setText("- " + eqPresetLabel.getText() + " -");
-
- // setup actionbar on off switch
- mToggleSwitch = new Switch(this);
- mToggleSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(final CompoundButton buttonView,
- final boolean isChecked) {
- // set parameter and state
- getPrefs().edit().putBoolean("audiofx.global.enable", isChecked).apply();
-
- updateUI(true);
- setInterception(isChecked);
- updateService();
- }
- });
-
- // setup action bar
- String[] navigationBarDevices = new String[HeadsetService.DEFAULT_AUDIO_DEVICES.length];
- for (int i = 0; i < navigationBarDevices.length; i++) {
- navigationBarDevices[i] = localizeDevice(HeadsetService.DEFAULT_AUDIO_DEVICES[i]);
- }
-
- mNavBarDeviceAdapter = new ArrayAdapter<String>(getBaseContext(), android.R.layout.simple_spinner_dropdown_item,
- navigationBarDevices);
- ActionBar.OnNavigationListener navigationListener = new ActionBar.OnNavigationListener() {
- @Override
- public boolean onNavigationItemSelected(int itemPosition, long itemId) {
- if (mStateChangeUpdate) {
- mStateChangeUpdate = false;
- } else {
- mCurrentDeviceOverride = true;
- mCurrentDevice = HeadsetService.DEFAULT_AUDIO_DEVICES[itemPosition];
- }
- updateUI(true);
- equalizerSetPreset(mEQPreset);
- equalizerUpdateDisplay(true);
- mBassKnob.setValue(Integer.valueOf(getPrefs().getString("audiofx.bass.strength", "0")));
- mVirtualizerKnob.setValue(Integer.valueOf(getPrefs().getString("audiofx.virtualizer.strength", "0")));
- return true;
- }
- };
-
- ActionBar ab = getActionBar();
- final ActionBar.LayoutParams params = new ActionBar.LayoutParams(
- ActionBar.LayoutParams.WRAP_CONTENT,
- ActionBar.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER_VERTICAL | Gravity.END);
-
- ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
- ab.setListNavigationCallbacks(mNavBarDeviceAdapter, navigationListener);
- ab.setBackgroundDrawable(new ColorDrawable(getResources()
- .getColor(R.color.action_bar_background)));
- mStateChangeUpdate = true;
- ab.setSelectedNavigationItem(getCurrentDeviceIndex());
-
- ab.setCustomView(mToggleSwitch, params);
- ab.setDisplayShowTitleEnabled(false);
- ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
-
- // initialize views
- mEqualizerSurface = (EqualizerSurface) findViewById(R.id.frequencyResponse);
- mEqGallery = (Gallery) findViewById(R.id.eqPresets);
- mReverbGallery = (Gallery) findViewById(R.id.reverb_gallery);
- mVirtualizerKnob = (Knob) findViewById(R.id.vIStrengthKnob);
- mBassKnob = (Knob) findViewById(R.id.bBStrengthKnob);
-
- // setup equalizer presets
- final int numPresets = Integer.parseInt(getSharedPreferences("global", 0)
- .getString("equalizer.number_of_presets", "0"));
- mEQPresetNames = new String[numPresets + 3];
-
- String[] presetNames = getSharedPreferences("global", 0).getString("equalizer.preset_names", "").split("\\|");
- for (short i = 0; i < numPresets; i++) {
- mEQPresetNames[i] = localizePresetName(presetNames[i]);
- }
- mEQPresetNames[numPresets] = getString(R.string.electronic);
- mEQPresetNames[numPresets + 1] = getString(R.string.small_speakers);
- mEQPresetNames[numPresets + 2] = getString(R.string.custom);
- mEQCustomPresetPosition = numPresets + 2;
-
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.equalizer_presets,
- mEQPresetNames);
-
- mEqGallery.setAdapter(adapter);
- mEqGallery.setSelection(mEQPreset);
- mEqGallery.setOnItemSelectedListener(new Gallery.OnItemSelectedListener() {
- @Override
- public void onItemSelected(int position) {
- mEQPreset = position;
- if (!mEQAnimatingToUserPos) {
- equalizerSetPreset(position);
- } else if (mEQAnimatingToUserPos && mEQPreset == mEQCustomPresetPosition) {
- mEQAnimatingToUserPos = false;
- }
- }
- });
-
- // setup equalizer
- mNumberEqualizerBands = Integer.parseInt(getSharedPreferences("global", 0)
- .getString("equalizer.number_of_bands", "5"));
- final int[] centerFreqs = getCenterFreqs();
- final int[] bandLevelRange = getBandLevelRange();
- float[] centerFreqsKHz = new float[centerFreqs.length];
- for (int i = 0; i < centerFreqs.length; i++) {
- centerFreqsKHz[i] = (float) centerFreqs[i] / 1000.0f;
- }
- mEqualizerSurface.setCenterFreqs(centerFreqsKHz);
- mEqualizerSurface.setBandLevelRange(bandLevelRange[0] / 100, bandLevelRange[1] / 100);
- final EqualizerSurface.BandUpdatedListener listener = new EqualizerSurface.BandUpdatedListener() {
-
- @Override
- public void onBandUpdated(int band, float dB) {
- if (mEQPreset != mEQCustomPresetPosition && !mEQAnimatingToUserPos) {
- equalizerCopyToCustom();
- mEQAnimatingToUserPos = true;
- mEqGallery.setAnimationDuration(1000);
- mEqGallery.setSelection(mEQCustomPresetPosition, true);
- } else {
- equalizerBandUpdate(band, (int) (dB * 100));
- }
- }
-
- float[] animatingLevels;
-
- @Override
- public void onBandAnimating(int band, float dB) {
- if (animatingLevels == null) {
- animatingLevels = mEqualizerSurface.softCopyLevels();
- }
- animatingLevels[band] = dB;
- if (mService != null) {
- mService.setEqualizerLevels(animatingLevels);
- }
- mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
- }
-
- @Override
- public void onBandAnimationCompleted() {
- if (mService != null) {
- mService.setEqualizerLevels(animatingLevels = null);
- }
- mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
- }
- };
- mEqualizerSurface.registerBandUpdatedListener(listener);
-
- // setup virtualizer knob
- mVirtualizerKnob.setMax(OpenSLESConstants.VIRTUALIZER_MAX_STRENGTH -
- OpenSLESConstants.VIRTUALIZER_MIN_STRENGTH);
- mVirtualizerKnob.setOnKnobChangeListener(new OnKnobChangeListener() {
- // Update the parameters while Knob changes and set the
- // effect parameter.
- @Override
- public void onValueChanged(final Knob knob, final int value,
- final boolean fromUser) {
- if (fromUser) {
- // set parameter and state
- getPrefs().edit().putBoolean("audiofx.virtualizer.enable", true).apply();
- getPrefs().edit().putString("audiofx.virtualizer.strength", String.valueOf(value)).apply();
- mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
- }
- }
-
- @Override
- public boolean onSwitchChanged(final Knob knob, boolean on) {
- if (!mKnobsAvailable) {
- showHeadsetMsg();
- return false;
- }
-// knob.setOn(getPrefs().getBoolean("audiofx.virtualizer.enable", true));
-// knob.setOn(on);
- return true;
- }
-
- @Override
- public void onAnimationFinished(boolean endValue) {
- getPrefs().edit().putBoolean("audiofx.virtualizer.enable", endValue).apply();
-// updateService();
- mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
- }
- });
-
- // setup bass knob
- mBassKnob.setMax(OpenSLESConstants.BASSBOOST_MAX_STRENGTH
- - OpenSLESConstants.BASSBOOST_MIN_STRENGTH);
- mBassKnob.setOnKnobChangeListener(new OnKnobChangeListener() {
- // Update the parameters while SeekBar changes and set the
- // effect parameter.
- @Override
- public void onValueChanged(final Knob knob, final int value,
- final boolean fromUser) {
- if (fromUser) {
- // set parameter and state
- getPrefs().edit().putBoolean("audiofx.bass.enable", true).apply();
- getPrefs().edit().putString("audiofx.bass.strength", String.valueOf(value)).apply();
- mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
- }
- }
-
- @Override
- public boolean onSwitchChanged(final Knob knob, boolean on) {
- if (!mKnobsAvailable) {
- showHeadsetMsg();
- return false;
- }
-// knob.setOn(on);
- return true;
- }
-
- @Override
- public void onAnimationFinished(boolean endValue) {
- getPrefs().edit().putBoolean("audiofx.bass.enable", endValue).apply();
-// updateService();
- mHandler.sendEmptyMessage(MSG_UPDATE_SERVICE);
- }
- });
-
-
- // setup reverb presets
- mReverbPresetNames = new String[mReverbPresetRSids.length];
- for (short i = 0; i < mReverbPresetRSids.length; ++i) {
- mReverbPresetNames[i] = getString(mReverbPresetRSids[i]);
- }
-
- ArrayAdapter<String> reverbAdapter = new ArrayAdapter<String>(this,
- R.layout.equalizer_presets, mReverbPresetNames);
- mReverbGallery.setAdapter(reverbAdapter);
- mReverbGallery.setOnItemSelectedListener(new OnItemSelectedListener() {
-
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- if (position != mPRPreset) {
- presetReverbSetPreset(position);
- }
- mPRPreset = position;
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- }
- });
- mReverbGallery.setSelection(mPRPreset);
-
-
- }
-
- private SharedPreferences getPrefs() {
- return getSharedPreferences(mCurrentDevice, 0);
- }
-
- private final String localizePresetName(final String name) {
- final String[] names = {
- "Normal", "Classical", "Dance", "Flat", "Folk",
- "Heavy Metal", "Hip Hop", "Jazz", "Pop", "Rock"
- };
- final int[] ids = {
- R.string.normal, R.string.classical, R.string.dance, R.string.flat, R.string.folk,
- R.string.heavy_metal, R.string.hip_hop, R.string.jazz, R.string.pop, R.string.rock
- };
-
- for (int i = names.length - 1; i >= 0; --i) {
- if (names[i].equals(name)) {
- return getString(ids[i]);
- }
- }
- return name;
- }
-
- private final String localizeDevice(String device) {
- return getString(mContext.getResources().getIdentifier("device_" + device, "string", getPackageName()));
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- updateUI(false);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- if (mServiceConnection == null) {
- mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder binder) {
- mService = ((HeadsetService.LocalBinder) binder).getService();
- if (!mCurrentDeviceOverride) {
- mCurrentDevice = mService.getAudioOutputRouting();
- }
- updateUI(true);
-
- mStateChangeUpdate = true;
- getActionBar().setSelectedNavigationItem(getCurrentDeviceIndex());
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mService = null;
- }
- };
- }
- Intent serviceIntent = new Intent(this, HeadsetService.class);
- bindService(serviceIntent, mServiceConnection, 0);
-
- final IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(HeadsetService.ACTION_UPDATE_PREFERENCES);
- registerReceiver(mReceiver, intentFilter);
-
- equalizerUpdateDisplay(true);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- // clear the toast
- if (mCurrentToast != null) {
- mCurrentToast.cancel();
- mCurrentToast = null;
- }
-
- unbindService(mServiceConnection);
-
- // Unregister for broadcast intents. (These affect the visible UI,
- // so we only care about them while we're in the foreground.)
- unregisterReceiver(mReceiver);
- }
-
- private void updateUI(boolean fromNavbar) {
- if (!fromNavbar) {
- mStateChangeUpdate = true;
- getActionBar().setSelectedNavigationItem(getCurrentDeviceIndex());
- }
-
- boolean isSpeaker = mCurrentDevice.equals("speaker");
-
- final boolean isEnabled = getPrefs().getBoolean("audiofx.global.enable", isSpeaker);
- mKnobsAvailable = !isSpeaker;
-
- mToggleSwitch.setChecked(isEnabled);
-
- if (mVirtualizerSupported) {
- mVirtualizerKnob.setOn(getPrefs().getBoolean("audiofx.virtualizer.enable", false), false);
- mVirtualizerKnob.setEnabled(isEnabled && mKnobsAvailable);
- } else {
- mVirtualizerKnob.setVisibility(View.GONE);
- }
- if (mBassBoostSupported) {
- mBassKnob.setOn(getPrefs().getBoolean("audiofx.bass.enable", true), false);
- mBassKnob.setEnabled(isEnabled && mKnobsAvailable);
- } else {
- mBassKnob.setVisibility(View.GONE);
- }
- if (mEqualizerSupported) {
- String preset;
- if (isSpeaker) {
- preset = String.valueOf(mNumberEqualizerBands + 2);
- } else {
- preset = "3";
- }
- mEQPreset = Integer.valueOf(getPrefs().getString("audiofx.eq.preset", preset));
- mEqGallery.setEnabled(isEnabled);
- mEqGallery.setSelection(mEQPreset);
- }
- if (mPresetReverbSupported) {
- mPRPreset = Integer.valueOf(getPrefs().getString("audiofx.reverb.preset", "0"));
- mReverbGallery.setSelection(mPRPreset, true);
- mReverbGallery.setEnabled(isEnabled);
- }
-
- setInterception(isEnabled);
- }
-
- private void updateUI() {
- updateUI(false);
- }
-
- private int getCurrentDeviceIndex() {
- for (int i = 0; i < HeadsetService.DEFAULT_AUDIO_DEVICES.length; i++) {
- if (HeadsetService.DEFAULT_AUDIO_DEVICES[i].equals(mCurrentDevice)) {
- return i;
- }
- }
- return 0;
- }
-
- private void setInterception(boolean isEnabled) {
- final InterceptableLinearLayout ill =
- (InterceptableLinearLayout) findViewById(R.id.contentSoundEffects);
- ill.setInterception(!isEnabled);
- if (isEnabled) {
- ill.setOnClickListener(null);
- } else {
- ill.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- // clear the toast
- if (mCurrentToast != null) {
- mCurrentToast.cancel();
- mCurrentToast = null;
- }
- mCurrentToast = Toast.makeText(mContext,
- getString(R.string.power_on_prompt), Toast.LENGTH_SHORT);
- mCurrentToast.setGravity(Gravity.CENTER, 0, 0);
- mCurrentToast.show();
- }
- });
- }
- }
-
- private int[] getBandLevelRange() {
- String savedCenterFreqs = getSharedPreferences("global", 0).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 = getSharedPreferences("global", 0).getString("equalizer.center_freqs",
- HeadsetService.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;
- }
-
- /**
- * Updates the EQ by getting the parameters.
- */
- private void equalizerUpdateDisplay(boolean animate) {
- mHandler.removeMessages(animate ? MSG_UPDATE_EQ_ANIMATE : MSG_UPDATE_EQ);
- mHandler.sendEmptyMessageDelayed(animate ? MSG_UPDATE_EQ_ANIMATE : MSG_UPDATE_EQ, 100);
- }
-
- private void equalizerUpdateDisplayInternal(boolean animate) {
- String levelsString = null;
- 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 = getPrefs().getString("audiofx.eq.bandlevels.custom",
- HeadsetService.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;
- }
- if (animate) {
- mEqualizerSurface.setBands(floats);
- } else {
- for (int band = 0; band < mNumberEqualizerBands; band++) {
- mEqualizerSurface.setBand(band, (float) floats[band] / 100.0f);
- }
- }
- } else {
- // try to load preset
- levelsString = getSharedPreferences("global", 0).getString("equalizer.preset." + mEQPreset,
- HeadsetService.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;
- if (!animate) {
- mEqualizerSurface.setBand(band, (float) level / 100.0f);
- }
- }
- if (animate) {
- mEqualizerSurface.setBands(floats);
- }
- }
- }
-
- /**
- * Called when user starts touch eq on a preset
- */
- private void equalizerCopyToCustom() {
- if (DEBUG) Log.d(TAG, "equalizerCopyToCustom()");
- StringBuilder bandLevels = new StringBuilder();
- for (int band = 0; band < mNumberEqualizerBands; band++) {
- final float level = mEqualizerSurface.getBand(band);
- bandLevels.append(level * 100);
- bandLevels.append(";");
- }
- // remove trailing ";"
- bandLevels.deleteCharAt(bandLevels.length() - 1);
- getPrefs().edit().putString("audiofx.eq.bandlevels.custom", bandLevels.toString()).apply();
- getPrefs().edit().putString("audiofx.eq.preset", String.valueOf(mEQCustomPresetPosition)).apply();
- }
-
- private void equalizerBandUpdate(final int band, final int level) {
- if (DEBUG) Log.d(TAG, "equalizerBandUpdate(band: " + band + ", level: " + level + ")");
-
- String[] currentCustomLevels = getPrefs().getString("audiofx.eq.bandlevels.custom",
- HeadsetService.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);
- getPrefs().edit().putString("audiofx.eq.bandlevels", builder.toString()).apply();
- getPrefs().edit().putString("audiofx.eq.bandlevels.custom", builder.toString()).apply();
-
- updateService();
- }
-
- private void updateService() {
- mHandler.removeMessages(MSG_UPDATE_SERVICE);
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SERVICE, 100);
- }
-
- private void equalizerSetPreset(final int preset) {
- if (DEBUG) Log.d(TAG, "equalizerSetPreset(" + preset + ")");
-
- mEQPreset = preset;
- getPrefs().edit().putString("audiofx.eq.preset", String.valueOf(preset)).apply();
-
- String newLevels = null;
- if (preset == mEQCustomPresetPosition) {
- // load custom if possible
- newLevels = getPrefs().getString("audiofx.eq.bandlevels.custom",
- HeadsetService.getZeroedBandsString(mNumberEqualizerBands));
- } else {
- newLevels = getSharedPreferences("global", 0).getString("equalizer.preset." + preset,
- HeadsetService.getZeroedBandsString(mNumberEqualizerBands));
- }
- getPrefs().edit().putString("audiofx.eq.bandlevels", newLevels).apply();
- equalizerUpdateDisplay(true);
-
- updateService();
- }
-
-
- private void presetReverbSetPreset(final int preset) {
- getPrefs().edit().putString("audiofx.reverb.preset", String.valueOf(preset)).apply();
- updateService();
- }
-
- private void showHeadsetMsg() {
- // clear the toast
- if (mCurrentToast != null) {
- mCurrentToast.cancel();
- mCurrentToast = null;
- }
-
- final Context context = getApplicationContext();
- final int duration = Toast.LENGTH_SHORT;
-
- mCurrentToast = Toast.makeText(context, getString(R.string.effect_unavalable_for_speaker), duration);
- mCurrentToast.setGravity(Gravity.CENTER, mCurrentToast.getXOffset() / 2, mCurrentToast.getYOffset() / 2);
- mCurrentToast.show();
- }
-
-}
diff --git a/src/org/cyanogenmod/audiofx/BootReceiver.java b/src/org/cyanogenmod/audiofx/BootReceiver.java
deleted file mode 100644
index b032310..0000000
--- a/src/org/cyanogenmod/audiofx/BootReceiver.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.cyanogenmod.audiofx;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * Created by roman on 5/12/14.
- */
-public class BootReceiver extends BroadcastReceiver {
- public void onReceive(Context context, Intent intent) {
- context.startService(new Intent(context, HeadsetService.class));
- }
-}
diff --git a/src/org/cyanogenmod/audiofx/HeadsetService.java b/src/org/cyanogenmod/audiofx/HeadsetService.java
deleted file mode 100644
index 98bf20e..0000000
--- a/src/org/cyanogenmod/audiofx/HeadsetService.java
+++ /dev/null
@@ -1,708 +0,0 @@
-/*
- * Copyright (C) 2014 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.cyanogenmod.audiofx;
-
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.media.AudioManager;
-import android.media.AudioPatch;
-import android.media.AudioPort;
-import android.media.AudioSystem;
-import android.media.audiofx.AudioEffect;
-import android.media.audiofx.BassBoost;
-import android.media.audiofx.Equalizer;
-import android.media.audiofx.PresetReverb;
-import android.media.audiofx.Virtualizer;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.util.Arrays;
-
-import cyanogenmod.media.AudioSessionInfo;
-import cyanogenmod.media.CMAudioManager;
-
-/**
- * <p>This calls listen to events that affect DSP function and responds to them.</p>
- * <ol>
- * <li>new audio session declarations</li>
- * <li>headset plug / unplug events</li>
- * <li>preference update events.</li>
- * </ol>
- *
- * @author alankila
- */
-public class HeadsetService extends Service {
-
- public static final String ACTION_UPDATE_PREFERENCES = "org.cyanogenmod.audiofx.UPDATE_PREFS";
- public static final String[] DEFAULT_AUDIO_DEVICES = new String[]{
- "headset", "speaker", "usb", "bluetooth", "wireless", "lineout"
- };
-
- static String getZeroedBandsString(int length) {
- StringBuffer buff = new StringBuffer();
- for (int i = 0; i < length; i++) {
- buff.append("0;");
- }
- buff.deleteCharAt(buff.length() - 1);
- return buff.toString();
- }
-
- /**
- * Helper class representing the full complement of effects attached to one
- * audio session.
- *
- * @author alankila
- */
- private static class EffectSet {
- /**
- * Session-specific equalizer
- */
- private final Equalizer mEqualizer;
- /**
- * Session-specific bassboost
- */
- private final BassBoost mBassBoost;
- /**
- * Session-specific virtualizer
- */
- private final Virtualizer mVirtualizer;
-
- private final PresetReverb mPresetReverb;
-
- private short mEqNumPresets = -1;
- private short mEqNumBands = -1;
-
- public EffectSet(int sessionId) {
- mEqualizer = new Equalizer(0, sessionId);
- mBassBoost = new BassBoost(0, sessionId);
- mVirtualizer = new Virtualizer(0, sessionId);
- mPresetReverb = new PresetReverb(0, sessionId);
- }
-
- /*
- * Take lots of care to not poke values that don't need
- * to be poked- this can cause audible pops.
- */
-
- public void enableEqualizer(boolean enable) {
- if (enable != mEqualizer.getEnabled()) {
- if (!enable) {
- for (short i = 0; i < getNumEqualizerBands(); i++) {
- mEqualizer.setBandLevel(i, (short) 0);
- }
- }
- mEqualizer.setEnabled(enable);
- }
- }
-
- public void setEqualizerLevels(short[] levels) {
- if (mEqualizer.getEnabled()) {
- for (short i = 0; i < levels.length; i++) {
- if (mEqualizer.getBandLevel(i) != levels[i]) {
- mEqualizer.setBandLevel(i, levels[i]);
- }
- }
- }
- }
-
- public short getNumEqualizerBands() {
- if (mEqNumBands < 0) {
- mEqNumBands = mEqualizer.getNumberOfBands();
- }
- return mEqNumBands;
- }
-
- public short getNumEqualizerPresets() {
- if (mEqNumPresets < 0) {
- mEqNumPresets = mEqualizer.getNumberOfPresets();
- }
- return mEqNumPresets;
- }
-
- public void enableBassBoost(boolean enable) {
- if (enable != mBassBoost.getEnabled()) {
- if (!enable) {
- mBassBoost.setStrength((short) 1);
- mBassBoost.setStrength((short) 0);
- }
- mBassBoost.setEnabled(enable);
- }
- }
-
- public void setBassBoostStrength(short strength) {
- if (mBassBoost.getEnabled() && mBassBoost.getRoundedStrength() != strength) {
- mBassBoost.setStrength(strength);
- }
- }
-
- public void enableVirtualizer(boolean enable) {
- if (enable != mVirtualizer.getEnabled()) {
- if (!enable) {
- mVirtualizer.setStrength((short) 1);
- mVirtualizer.setStrength((short) 0);
- }
- mVirtualizer.setEnabled(enable);
- }
- }
-
- public void setVirtualizerStrength(short strength) {
- if (mVirtualizer.getEnabled() && mVirtualizer.getRoundedStrength() != strength) {
- mVirtualizer.setStrength(strength);
- }
- }
-
- public void enableReverb(boolean enable) {
- if (enable != mPresetReverb.getEnabled()) {
- if (!enable) {
- mPresetReverb.setPreset((short) 0);
- }
- mPresetReverb.setEnabled(enable);
- }
- }
-
- public void setReverbPreset(short preset) {
- if (mPresetReverb.getEnabled() && mPresetReverb.getPreset() != preset) {
- mPresetReverb.setPreset(preset);
- }
- }
-
- public void release() {
- mEqualizer.release();
- mBassBoost.release();
- mVirtualizer.release();
- mPresetReverb.release();
- }
- }
-
- protected static final String TAG = HeadsetService.class.getSimpleName();
- public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-
- private void addSession(int sessionId) {
- if (sessionId == 0) {
- return;
- }
- if (DEBUG) Log.i(TAG, String.format("New audio session: %d", sessionId));
-
- synchronized (mAudioSessionsL) {
- if (mAudioSessionsL.indexOfKey(sessionId) < 0) {
- mAudioSessionsL.put(sessionId, new EffectSet(sessionId));
- }
- updateLocked();
- }
- }
-
- private void removeSession(int sessionId) {
- if (sessionId == 0) {
- return;
- }
- if (DEBUG) Log.i(TAG, String.format("Audio session removed: %d", sessionId));
-
- synchronized (mAudioSessionsL) {
- EffectSet gone = mAudioSessionsL.removeReturnOld(sessionId);
- if (gone != null) {
- gone.release();
- }
- }
- }
-
- public void addSession(AudioSessionInfo info) {
- if (info.getStream() == AudioManager.STREAM_MUSIC &&
- (info.getFlags() < 0 || (info.getFlags() & 0x8) > 0 || (info.getFlags() & 0x10) > 0) &&
- (info.getChannelMask() < 0 || info.getChannelMask() > 1)) {
-
- // Never auto-attach is someone is recording! We don't want to
- // interfere with any sort of
- // loopback mechanisms.
- final boolean recording = AudioSystem.isSourceActive(0)
- || AudioSystem.isSourceActive(6);
- if (recording) {
- Log.w(TAG, "Recording in progress, not performing auto-attach!");
- return;
- }
- addSession(info.getSessionId());
- }
- }
-
- public void removeSession(AudioSessionInfo info) {
- if (info.getStream() == AudioManager.STREAM_MUSIC) {
- removeSession(info.getSessionId());
- }
- }
-
- public class LocalBinder extends Binder {
- public HeadsetService getService() {
- return HeadsetService.this;
- }
- }
-
- private final LocalBinder mBinder = new LocalBinder();
-
- /**
- * Known audio sessions and their associated audioeffect suites.
- */
- private final SparseArray<EffectSet> mAudioSessionsL = new SparseArray<EffectSet>();
-
- AudioPortListener mAudioPortListener;
-
- /**
- * Has DSPManager assumed control of equalizer levels?
- */
- private float[] mOverriddenEqualizerLevels;
-
- /**
- * Update audio parameters when preferences have been updated.
- */
- private final BroadcastReceiver mPreferenceUpdateReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.i(TAG, "Preferences updated.");
- update();
- }
- };
-
- private class AudioPortListener implements AudioManager.OnAudioPortUpdateListener {
- private boolean mUseBluetooth;
- private boolean mUseHeadset;
- private boolean mUseUSB;
- private boolean mUseWifiDisplay;
- private boolean mUseSpeaker;
- private boolean mUseLineOut;
-
- private final Context mContext;
-
- public AudioPortListener(Context context) {
- mContext = context;
- }
-
- @Override
- public void onAudioPortListUpdate(AudioPort[] portList) {
- final boolean prevUseHeadset = mUseHeadset;
- final boolean prevUseBluetooth = mUseBluetooth;
- final boolean prevUseUSB = mUseUSB;
- final boolean prevUseWireless = mUseWifiDisplay;
- final boolean prevUseSpeaker = mUseSpeaker;
- final boolean prevUseLineOut = mUseLineOut;
-
- AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- int device = am.getDevicesForStream(AudioManager.STREAM_MUSIC);
- mUseBluetooth = (device & AudioManager.DEVICE_OUT_BLUETOOTH_A2DP) != 0
- || (device & AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES) != 0
- || (device & AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER) != 0
- || (device & AudioManager.DEVICE_OUT_BLUETOOTH_SCO) != 0
- || (device & AudioManager.DEVICE_OUT_BLUETOOTH_SCO_CARKIT) != 0
- || (device & AudioManager.DEVICE_OUT_BLUETOOTH_SCO_HEADSET) != 0;
-
- mUseHeadset = (device & AudioManager.DEVICE_OUT_WIRED_HEADPHONE) != 0
- || (device & AudioManager.DEVICE_OUT_WIRED_HEADSET) != 0;
-
- mUseLineOut = (device & AudioManager.DEVICE_OUT_LINE) != 0;
-
- mUseUSB = (device & AudioManager.DEVICE_OUT_USB_ACCESSORY) != 0
- || (device & AudioManager.DEVICE_OUT_USB_DEVICE) != 0;
-
- mUseWifiDisplay = false; //TODO add support for wireless display..
-
- mUseSpeaker = (device & AudioManager.DEVICE_OUT_SPEAKER) != 0;
-
- Log.i(TAG, "Headset=" + mUseHeadset + "; Bluetooth="
- + mUseBluetooth + " ; USB=" + mUseUSB + "; Speaker=" + mUseSpeaker +
- "; Line out=" + mUseLineOut);
-
- if (prevUseHeadset != mUseHeadset
- || prevUseBluetooth != mUseBluetooth
- || prevUseUSB != mUseUSB
- || prevUseWireless != mUseWifiDisplay
- || prevUseSpeaker != mUseSpeaker
- || prevUseLineOut != mUseLineOut) {
-
- update();
-
- Intent i = new Intent(ACTION_UPDATE_PREFERENCES);
- mContext.sendBroadcast(i);
- }
- }
-
- @Override
- public void onAudioPatchListUpdate(AudioPatch[] patchList) {
-
- }
-
- @Override
- public void onServiceDied() {
-
- }
-
- public String getInternalAudioOutputRouting() {
- if (mUseSpeaker) {
- return "speaker";
- }
- if (mUseBluetooth) {
- return "bluetooth";
- }
- if (mUseHeadset) {
- return "headset";
- }
- if (mUseUSB) {
- return "usb";
- }
- if (mUseWifiDisplay) {
- return "wireless";
- }
- if (mUseLineOut) {
- return "lineout";
- }
- return "speaker";
- }
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i(TAG, "Starting service.");
-
- registerReceiver(mPreferenceUpdateReceiver,
- new IntentFilter(ACTION_UPDATE_PREFERENCES));
-
- AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- am.registerAudioPortUpdateListener(mAudioPortListener = new AudioPortListener(this));
-
- saveDefaults();
- }
-
-
- /**
- * maps {@link AudioEffect#EXTRA_CONTENT_TYPE} to an AudioManager.STREAM_* item
- */
- private static int mapContentTypeToStream(int contentType) {
- switch (contentType) {
- case AudioEffect.CONTENT_TYPE_VOICE:
- return AudioManager.STREAM_VOICE_CALL;
- case AudioEffect.CONTENT_TYPE_GAME:
- // explicitly don't support game effects right now
- return -1;
- case AudioEffect.CONTENT_TYPE_MOVIE:
- case AudioEffect.CONTENT_TYPE_MUSIC:
- default:
- return AudioManager.STREAM_MUSIC;
- }
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent != null && intent.getAction() != null) {
- if (DEBUG) {
- Log.i(TAG, "onStartCommand() called with " + "intent = [" + intent + "], flags = ["
- + flags + "], startId = [" + startId + "], extras = [" +
- (intent.getExtras() == null ? "null" : intent.getExtras().toString())
- + "]");
- }
- String action = intent.getAction();
- int sessionId = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION, 0);
- String pkg = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
- int stream = mapContentTypeToStream(
- intent.getIntExtra(AudioEffect.EXTRA_CONTENT_TYPE,
- AudioEffect.CONTENT_TYPE_MUSIC));
-
- if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) {
- if (DEBUG) {
- Log.i(TAG, String.format("New audio session: %d package: %s contentType=%d",
- sessionId, pkg, stream));
- }
- addSession(sessionId);
-
- } else if (action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
-
- removeSession(sessionId);
-
- } else if (action.equals(CMAudioManager.ACTION_AUDIO_SESSIONS_CHANGED)) {
-
- final AudioSessionInfo info = (AudioSessionInfo) intent.getParcelableExtra(
- CMAudioManager.EXTRA_SESSION_INFO);
- if (info != null && info.getSessionId() > 0) {
- boolean added = intent.getBooleanExtra(CMAudioManager.EXTRA_SESSION_ADDED,
- false);
- if (added) {
- addSession(info);
- } else {
- removeSession(info);
- }
- }
- }
- }
- return START_STICKY;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.i(TAG, "Stopping service.");
-
- unregisterReceiver(mPreferenceUpdateReceiver);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- /**
- * Gain temporary control over the global equalizer.
- * Used by DSPManager when testing a new equalizer setting.
- *
- * @param levels
- */
- public void setEqualizerLevels(float[] levels) {
- mOverriddenEqualizerLevels = levels;
- update();
- }
-
- /**
- * There appears to be no way to find out what the current actual audio routing is.
- * For instance, if a wired headset is plugged in, the following objects/classes are involved:
- * </p>
- * <ol>
- * <li>wiredaccessoryobserver</li>
- * <li>audioservice</li>
- * <li>audiosystem</li>
- * <li>audiopolicyservice</li>
- * <li>audiopolicymanager</li>
- * </ol>
- * <p>Once the decision of new routing has been made by the policy manager, it is relayed to
- * audiopolicyservice, which waits for some time to let application buffers drain, and then
- * informs it to hardware. The full chain is:</p>
- * <ol>
- * <li>audiopolicymanager</li>
- * <li>audiopolicyservice</li>
- * <li>audiosystem</li>
- * <li>audioflinger</li>
- * <li>audioeffect (if any)</li>
- * </ol>
- * <p>However, the decision does not appear to be relayed to java layer, so we must
- * make a guess about what the audio output routing is.</p>
- *
- * @return string token that identifies configuration to use
- */
- public String getAudioOutputRouting() {
- if (mAudioPortListener != null) {
- return mAudioPortListener.getInternalAudioOutputRouting();
- }
- return "speaker";
- }
-
- public EffectSet getEffects(int session) {
- synchronized (mAudioSessionsL) {
- return mAudioSessionsL.get(session);
- }
- }
-
- private void saveDefaults() {
- EffectSet temp;
- try {
- temp = new EffectSet(0);
- } catch (Exception e) {
- // this is really bad- likely the media stack is broken.
- // disable ourself if we get into this state, as the service
- // will restart itself repeatedly!
- Log.e(TAG, e.getMessage(), e);
- stopSelf();
- return;
- }
-
- SharedPreferences prefs = getSharedPreferences("global", 0);
-
- final int numBands = temp.getNumEqualizerBands();
- final int numPresets = temp.getNumEqualizerPresets();
- SharedPreferences.Editor editor = prefs.edit();
- editor.putString("equalizer.number_of_presets", String.valueOf(numPresets)).apply();
- editor.putString("equalizer.number_of_bands", String.valueOf(numBands)).apply();
-
- // range
- short[] rangeShortArr = temp.mEqualizer.getBandLevelRange();
-
-
- editor.putString("equalizer.band_level_range", rangeShortArr[0] + ";" + rangeShortArr[1]).apply();
-
- // center freqs
- StringBuilder centerFreqs = new StringBuilder();
- // audiofx.global.centerfreqs
- for (short i = 0; i < numBands; i++) {
- centerFreqs.append(temp.mEqualizer.getCenterFreq(i));
- centerFreqs.append(";");
-
- }
- centerFreqs.deleteCharAt(centerFreqs.length() - 1);
- editor.putString("equalizer.center_freqs", centerFreqs.toString()).apply();
-
- // populate preset names
- StringBuilder presetNames = new StringBuilder();
- for (int i = 0; i < numPresets; i++) {
- String presetName = temp.mEqualizer.getPresetName((short) i);
- presetNames.append(presetName);
- presetNames.append("|");
-
- // populate preset band values
- StringBuilder presetBands = new StringBuilder();
- temp.mEqualizer.usePreset((short) i);
-
- for (int j = 0; j < numBands; j++) {
- // loop through preset bands
- presetBands.append(temp.mEqualizer.getBandLevel((short) j));
- presetBands.append(";");
- }
- presetBands.deleteCharAt(presetBands.length() - 1);
- editor.putString("equalizer.preset." + i, presetBands.toString()).apply();
- }
- presetNames.deleteCharAt(presetNames.length() - 1);
- editor.putString("equalizer.preset_names", presetNames.toString()).apply();
- temp.release();
-
- // add ci-extreme
- StringBuilder ciExtremeBuilder = new StringBuilder("0;800;400;100;1000");
- if (numBands > 5) {
- int extraBands = numBands - 5;
- for (int i = 0; i < extraBands; i++) {
- ciExtremeBuilder.insert(0, "0;");
- }
- }
- editor.putString("equalizer.preset." + numPresets, ciExtremeBuilder.toString()).apply();
-
- // add small-speaker
- StringBuilder ssBuilder = new StringBuilder("-170;270;50;-220;200");
- if (numBands > 5) {
- int extraBands = numBands - 5;
- for (int i = 0; i < extraBands; i++) {
- ssBuilder.insert(0, "0;");
- }
- }
- editor.putString("equalizer.preset." + (numPresets + 1), ssBuilder.toString()).apply();
- editor.commit();
-
- // Enable for the speaker by default
- if (!getSharedPrefsFile("speaker").exists()) {
- SharedPreferences spk = getSharedPreferences("speaker", 0);
- spk.edit().putBoolean("audiofx.global.enable", true).apply();
- spk.edit().putString("audiofx.eq.preset", String.valueOf(numPresets + 1)).apply();
- }
- }
-
- /**
- * Push new configuration to audio stack.
- */
- void update() {
- synchronized (mAudioSessionsL) {
- updateLocked();
- }
- }
-
- private void updateLocked() {
- final String mode = getAudioOutputRouting();
- SharedPreferences preferences = getSharedPreferences(
- mode, 0);
-
- if (DEBUG) Log.i(TAG, "Selected configuration: " + mode);
-
- for (int i = 0; i < mAudioSessionsL.size(); i++) {
- updateDsp(preferences, mAudioSessionsL.valueAt(i));
- }
- }
-
- private void updateDsp(SharedPreferences prefs, EffectSet session) {
- final boolean globalEnabled = prefs.getBoolean("audiofx.global.enable", false);
-
- try {
- session.enableBassBoost(globalEnabled && prefs.getBoolean("audiofx.bass.enable", false));
- session.setBassBoostStrength(Short.valueOf(prefs
- .getString("audiofx.bass.strength", "0")));
-
- } catch (Exception e) {
- Log.e(TAG, "Error enabling bass boost!", e);
- }
-
- try {
- short preset = Short.decode(prefs.getString("audiofx.reverb.preset",
- String.valueOf(PresetReverb.PRESET_NONE)));
- session.enableReverb(globalEnabled && (preset > 0));
- session.setReverbPreset(preset);
-
- } catch (Exception e) {
- Log.e(TAG, "Error enabling reverb preset", e);
- }
-
- try {
- session.enableEqualizer(globalEnabled);
- final int customPresetPos = session.getNumEqualizerPresets() + 2;
- final int preset = Integer.valueOf(prefs.getString("audiofx.eq.preset",
- String.valueOf(customPresetPos)));
- final int bands = session.getNumEqualizerBands();
-
- /*
- * Equalizer state is in a single string preference with all values
- * separated by ;
- */
- String[] levels = null;
- short[] equalizerLevels = null;
-
- if (mOverriddenEqualizerLevels != null) {
-
- } else if (preset == customPresetPos) {
- if (DEBUG) Log.i(TAG, "loading custom band levels");
- levels = prefs.getString("audiofx.eq.bandlevels.custom",
- getZeroedBandsString(bands)).split(";");
- } else {
- if (DEBUG) Log.i(TAG, "loading preset band levels");
- levels = getSharedPreferences("global", 0).getString("equalizer.preset." + preset,
- getZeroedBandsString(bands)).split(";");
- }
-
- if (levels != null) {
- if (DEBUG) Log.i(TAG, "band levels applied: " + Arrays.toString(levels));
- equalizerLevels = new short[levels.length];
- for (int i = 0; i < levels.length; i++) {
- equalizerLevels[i] = (short) (Float.parseFloat(levels[i]));
- }
- } else if (mOverriddenEqualizerLevels != null) {
- equalizerLevels = new short[mOverriddenEqualizerLevels.length];
- for (int i = 0; i < mOverriddenEqualizerLevels.length; i++) {
- equalizerLevels[i] = (short) mOverriddenEqualizerLevels[i];
- }
- }
- if (equalizerLevels != null) {
- session.setEqualizerLevels(equalizerLevels);
- }
-
-
- } catch (Exception e) {
- Log.e(TAG, "Error enabling equalizer!", e);
- }
-
- try {
- session.enableVirtualizer(globalEnabled
- && prefs.getBoolean("audiofx.virtualizer.enable", false));
- session.setVirtualizerStrength(Short.valueOf(prefs.getString(
- "audiofx.virtualizer.strength", "0")));
-
- } catch (Exception e) {
- Log.e(TAG, "Error enabling virtualizer!");
- }
- }
-}
diff --git a/src/org/cyanogenmod/audiofx/OpenSLESConstants.java b/src/org/cyanogenmod/audiofx/OpenSLESConstants.java
deleted file mode 100644
index 2131b55..0000000
--- a/src/org/cyanogenmod/audiofx/OpenSLESConstants.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2010-2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.cyanogenmod.audiofx;
-
-/**
- * OpenSL ES constants class
- */
-public final class OpenSLESConstants {
- private OpenSLESConstants() {
- // Empty constructor
- }
-
- /**
- * Minimum volume level in millibel (mb).
- */
- public static final short SL_MILLIBEL_MIN = -9600;
- /**
- * This value is used when equalizer setting is not defined.
- */
- public static final short SL_EQUALIZER_UNDEFINED = (short) 0xFFFF;
-
- /**
- * The minimum bass boost strength in o/oo.
- */
- public static final short BASSBOOST_MIN_STRENGTH = 0;
- /**
- * The maximum bass boost strength in o/oo.
- */
- public static final short BASSBOOST_MAX_STRENGTH = 1000;
-
- /**
- * The minimum reverb room level in mb.
- */
- public static final short REVERB_MIN_ROOM_LEVEL = SL_MILLIBEL_MIN;
- /**
- * The maximum reverb room level in mb.
- */
- public static final short REVERB_MAX_ROOM_LEVEL = 0;
- /**
- * The minimum reverb room HF level in mb.
- */
- public static final short REVERB_MIN_ROOM_HF_LEVEL = SL_MILLIBEL_MIN;
- /**
- * The maximum reverb room HF level in mb.
- */
- public static final short REVERB_MAX_ROOM_HF_LEVEL = 0;
- /**
- * The minimum reverb decay time in ms.
- */
- public static final short REVERB_MIN_DECAY_TIME = 100;
- /**
- * The maximum reverb decay time in ms.
- */
- // XXX: OpenSL ES is normally 20000 but can only support 7000 for now
- public static final short REVERB_MAX_DECAY_TIME = 7000;
- /**
- * The minimum reverb decay HF ratio in o/oo.
- */
- public static final short REVERB_MIN_DECAY_HF_RATIO = 100;
- /**
- * The maximum reverb decay HF ratio in o/oo.
- */
- public static final short REVERB_MAX_DECAY_HF_RATIO = 2000;
- /**
- * The minimum reverb level in mb.
- */
- public static final short REVERB_MIN_REVERB_LEVEL = SL_MILLIBEL_MIN;
- /**
- * The maximum reverb level in mb.
- */
- public static final short REVERB_MAX_REVERB_LEVEL = 2000;
- /**
- * The minimum reverb diffusion in o/oo.
- */
- public static final short REVERB_MIN_DIFFUSION = 0;
- /**
- * The maximum reverb diffusion in o/oo.
- */
- public static final short REVERB_MAX_DIFFUSION = 1000;
- /**
- * The minimum reverb density in o/oo.
- */
- public static final short REVERB_MIN_DENSITY = 0;
- /**
- * The maximum reverb density in o/oo.
- */
- public static final short REVERB_MAX_DENSITY = 1000;
-
- /**
- * The minimum virtualizer strength in o/oo.
- */
- public static final short VIRTUALIZER_MIN_STRENGTH = 0;
- /**
- * The maximum virtualizer strength in o/oo.
- */
- public static final short VIRTUALIZER_MAX_STRENGTH = 1000;
-
- /**
- * The minimum volume effect level in millibel (mb).
- */
- public static final short VOLUME_MIN_LEVEL = SL_MILLIBEL_MIN;
- /**
- * The minimum volume stereo position in o/oo.
- */
- public static final short VOLUME_MIN_STEREO_POSITION = -1000;
- /**
- * The maximum volume stereo position in o/oo.
- */
- public static final short VOLUME_MAX_STEREO_POSITION = 1000;
-}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/AudioFxApplication.java b/src/org/cyanogenmod/audiofx/audiofx/AudioFxApplication.java
new file mode 100644
index 0000000..fa47637
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/AudioFxApplication.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.cyngn.audiofx;
+
+import android.app.Application;
+import android.util.Log;
+
+import com.cyanogen.ambient.analytics.AnalyticsServices;
+import com.cyanogen.ambient.analytics.Event;
+import com.cyanogen.ambient.common.api.AmbientApiClient;
+
+public class AudioFxApplication extends Application {
+
+ private static final String TAG = AudioFxApplication.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private AmbientApiClient mClient;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mClient = new AmbientApiClient.Builder(this)
+ .addApi(AnalyticsServices.API)
+ .build();
+ mClient.connect();
+ }
+
+ public void sendEvent(Event event) {
+ if (DEBUG) {
+ Log.i(TAG, "sendEvent() called with event = [" + event + "]");
+ }
+ AnalyticsServices.AnalyticsApi.sendEvent(mClient, event);
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/Compatibility.java b/src/org/cyanogenmod/audiofx/audiofx/Compatibility.java
index c0e72c2..313c480 100644
--- a/src/org/cyanogenmod/audiofx/Compatibility.java
+++ b/src/org/cyanogenmod/audiofx/audiofx/Compatibility.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.cyanogenmod.audiofx;
+package com.cyngn.audiofx;
import android.app.Activity;
import android.app.IntentService;
@@ -30,6 +30,8 @@ import android.media.audiofx.AudioEffect;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
+import com.cyngn.audiofx.activity.ActivityMusic;
+import com.cyngn.audiofx.stats.UserSession;
import java.util.List;
@@ -64,8 +66,8 @@ public class Compatibility {
Intent i = new Intent(getIntent());
i.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
SharedPreferences pref = getSharedPreferences("musicfx", MODE_PRIVATE);
- String defPackage = pref.getString("defaultpanelpackage", null);
- String defName = pref.getString("defaultpanelname", null);
+ String defPackage = pref.getString(Constants.MUSICFX_DEFAULT_PACKAGE_KEY, null);
+ String defName = pref.getString(Constants.MUSICFX_DEFAULT_PANEL_KEY, null);
log("read " + defPackage + "/" + defName + " as default");
if (defPackage == null || defName == null) {
Log.e(TAG, "no default set!");
@@ -79,6 +81,8 @@ public class Compatibility {
} else {
i.setComponent(new ComponentName(defPackage, defName));
}
+
+ i.putExtra(ActivityMusic.EXTRA_CALLING_PACKAGE, getCallingPackage());
startActivity(i);
finish();
}
@@ -149,9 +153,9 @@ public class Compatibility {
Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
List<ResolveInfo> ris = mPackageManager.queryIntentActivities(i, PackageManager.GET_DISABLED_COMPONENTS);
log("found: " + ris.size());
- SharedPreferences pref = getSharedPreferences("musicfx", MODE_PRIVATE);
- String savedDefPackage = pref.getString("defaultpanelpackage", null);
- String savedDefName = pref.getString("defaultpanelname", null);
+ SharedPreferences pref = Constants.getMusicFxPrefs(this);
+ String savedDefPackage = pref.getString(Constants.MUSICFX_DEFAULT_PACKAGE_KEY, null);
+ String savedDefName = pref.getString(Constants.MUSICFX_DEFAULT_PANEL_KEY, null);
log("saved default: " + savedDefName);
for (ResolveInfo foo: ris) {
if (foo.activityInfo.name.equals(Compatibility.Redirector.class.getName())) {
@@ -206,10 +210,10 @@ public class Compatibility {
// Write the selected default to the prefs so that the Redirector activity
// knows which one to use.
- SharedPreferences pref = getSharedPreferences("musicfx", MODE_PRIVATE);
+ SharedPreferences pref = Constants.getMusicFxPrefs(this);
Editor ed = pref.edit();
- ed.putString("defaultpanelpackage", defPackage);
- ed.putString("defaultpanelname", defName);
+ ed.putString(Constants.MUSICFX_DEFAULT_PACKAGE_KEY, defPackage);
+ ed.putString(Constants.MUSICFX_DEFAULT_PANEL_KEY, defName);
ed.commit();
log("wrote " + defPackage + "/" + defName + " as default");
}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/Constants.java b/src/org/cyanogenmod/audiofx/audiofx/Constants.java
new file mode 100644
index 0000000..c1d5475
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/Constants.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.cyngn.audiofx;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import com.cyngn.audiofx.eq.EqUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Constants {
+
+ // current pref version, bump to rebuild prefs
+ public static final int CURRENT_PREFS_INT_VERSION = 2;
+
+ // effect type identifiers
+ public static final int EFFECT_TYPE_ANDROID = 1;
+ public static final int EFFECT_TYPE_MAXXAUDIO = 2;
+ public static final int EFFECT_TYPE_DTS = 3;
+
+ // global settings
+ public static final String AUDIOFX_GLOBAL_FILE = "global";
+
+ public static final String DEVICE_SPEAKER = "speaker";
+ public static final String DEVICE_HEADSET = "headset";
+ public static final String DEVICE_LINE_OUT = "lineout";
+ public static final String DEVICE_PREFIX_USB = "usb";
+ public static final String DEVICE_PREFIX_CAST = "wireless";
+ public static final String DEVICE_PREFIX_BLUETOOTH = "bluetooth";
+
+ public static final String SAVED_DEFAULTS = "saved_defaults";
+
+ public static final String AUDIOFX_GLOBAL_USE_DTS = "audiofx.global.use_dts";
+ public static final String AUDIOFX_GLOBAL_HAS_DTS = "audiofx.global.has_dts";
+ public static final String AUDIOFX_GLOBAL_ENABLE_DTS = "audiofx.global.dts.enable";
+ public static final String AUDIOFX_GLOBAL_HAS_MAXXAUDIO = "audiofx.global.hasmaxxaudio";
+ public static final String AUDIOFX_GLOBAL_HAS_BASSBOOST = "audiofx.global.hasbassboost";
+ public static final String AUDIOFX_GLOBAL_HAS_VIRTUALIZER = "audiofx.global.hasvirtualizer";
+ public static final String AUDIOFX_GLOBAL_PREFS_VERSION_INT = "audiofx.global.prefs.version";
+
+ // per-device settings
+ public static final boolean DEVICE_DEFAULT_GLOBAL_ENABLE = false;
+
+ /**
+ * not really global enable, but really the device global enable...
+ */
+ public static final String DEVICE_AUDIOFX_GLOBAL_ENABLE = "audiofx.global.enable";
+ public static final String DEVICE_AUDIOFX_BASS_ENABLE = "audiofx.bass.enable";
+ public static final String DEVICE_AUDIOFX_BASS_STRENGTH = "audiofx.bass.strength";
+ public static final String DEVICE_AUDIOFX_REVERB_PRESET = "audiofx.reverb.preset";
+ public static final String DEVICE_AUDIOFX_VIRTUALIZER_ENABLE = "audiofx.virtualizer.enable";
+ public static final String DEVICE_AUDIOFX_VIRTUALIZER_STRENGTH = "audiofx.virtualizer.strength";
+ public static final String DEVICE_AUDIOFX_TREBLE_ENABLE = "audiofx.treble.enable";
+ public static final String DEVICE_AUDIOFX_TREBLE_STRENGTH = "audiofx.treble.strength";
+ public static final String DEVICE_AUDIOFX_MAXXVOLUME_ENABLE = "audiofx.maxxvolume.enable";
+
+ public static final String DEVICE_AUDIOFX_EQ_PRESET = "audiofx.eq.preset";
+ public static final String DEVICE_AUDIOFX_EQ_PRESET_LEVELS = "audiofx.eq.preset.levels";
+
+ // eq
+ public static final String EQUALIZER_NUMBER_OF_PRESETS = "equalizer.number_of_presets";
+ public static final String EQUALIZER_NUMBER_OF_BANDS = "equalizer.number_of_bands";
+ public static final String EQUALIZER_BAND_LEVEL_RANGE = "equalizer.band_level_range";
+ public static final String EQUALIZER_CENTER_FREQS = "equalizer.center_freqs";
+ public static final String EQUALIZER_PRESET = "equalizer.preset.";
+ public static final String EQUALIZER_PRESET_NAMES = "equalizer.preset_names";
+
+ // musicfx constants
+ public static final String MUSICFX_PREF_NAME = "musicfx";
+ public static final String MUSICFX_DEFAULT_PACKAGE_KEY = "defaultpanelpackage";
+ public static final String MUSICFX_DEFAULT_PANEL_KEY = "defaultpanelname";
+
+ public static SharedPreferences getMusicFxPrefs(Context context) {
+ return context.getSharedPreferences(MUSICFX_PREF_NAME, Context.MODE_PRIVATE);
+ }
+
+ public static SharedPreferences getGlobalPrefs(Context context) {
+ return context.getSharedPreferences(AUDIOFX_GLOBAL_FILE, 0);
+ }
+
+ public static List<Preset> getCustomPresets(Context ctx, int bands) {
+ ArrayList<Preset> presets = new ArrayList<Preset>();
+ final SharedPreferences presetPrefs = ctx.getSharedPreferences("custom_presets", 0);
+ String[] presetNames = presetPrefs.getString("preset_names", "").split("\\|");
+
+ for (int i = 0; i < presetNames.length; i++) {
+ String storedPresetString = presetPrefs.getString(presetNames[i], null);
+ if (storedPresetString == null) {
+ continue;
+ }
+ Preset.CustomPreset p = Preset.CustomPreset.fromString(storedPresetString);
+ presets.add(p);
+ }
+
+ return presets;
+ }
+
+ public static void saveCustomPresets(Context ctx, List<Preset> presets) {
+ final SharedPreferences.Editor presetPrefs = ctx.getSharedPreferences("custom_presets", 0).edit();
+ presetPrefs.clear();
+
+ StringBuffer presetNames = new StringBuffer();
+ for (int i = 0; i < presets.size(); i++) {
+ final Preset preset = presets.get(i);
+ if (preset instanceof Preset.CustomPreset
+ && !(preset instanceof Preset.PermCustomPreset)) {
+ Preset.CustomPreset p = (Preset.CustomPreset) preset;
+ presetNames.append(p.getName());
+ presetNames.append("|");
+
+ presetPrefs.putString(p.getName(), p.toString());
+ }
+ }
+ if (presetNames.length() > 0) {
+ presetNames.deleteCharAt(presetNames.length() - 1);
+ }
+
+ presetPrefs.putString("preset_names", presetNames.toString());
+ presetPrefs.commit();
+ }
+
+ public static int[] getBandLevelRange(Context context) {
+ String savedCenterFreqs = context.getSharedPreferences("global", 0).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;
+ }
+ }
+
+ public static int[] getCenterFreqs(Context context, int eqBands) {
+ String savedCenterFreqs = context.getSharedPreferences("global", 0).getString("equalizer.center_freqs",
+ EqUtils.getZeroedBandsString(eqBands));
+ 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;
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/Preset.java b/src/org/cyanogenmod/audiofx/audiofx/Preset.java
new file mode 100644
index 0000000..e284658
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/Preset.java
@@ -0,0 +1,236 @@
+package com.cyngn.audiofx;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.cyngn.audiofx.eq.EqUtils;
+
+public class Preset implements Parcelable {
+
+ protected String mName;
+ protected final float[] mLevels;
+
+ private Preset(String name, float[] levels) {
+ this.mName = name;
+ mLevels = new float[levels.length];
+ for (int i = 0; i < levels.length; i++) {
+ mLevels[i] = levels[i];
+ }
+ }
+
+ public float[] getLevels() {
+ return mLevels;
+ }
+
+ public float getBandLevel(int band) {
+ return mLevels[band];
+ }
+
+ @Override
+ public String toString() {
+ return mName + "|" + EqUtils.floatLevelsToString(mLevels);
+ }
+
+ private static Preset fromString(String input) {
+ final String[] split = input.split("\\|");
+ if (split == null || split.length != 2) {
+ return null;
+ }
+ float[] levels = EqUtils.stringBandsToFloats(split[1]);
+ return new Preset(split[0], levels);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Preset) {
+ Preset other = (Preset) o;
+
+ if (this.mLevels.length != ((Preset) o).mLevels.length) {
+ return false;
+ }
+
+ for(int i = 0; i < mLevels.length; i++) {
+ if (mLevels[i] != other.mLevels[i]) {
+ return false;
+ }
+ }
+
+ return other.mName.equals(mName);
+ }
+ return super.equals(o);
+ }
+
+ private Preset(Parcel in) {
+ if (in.readInt() == 1) {
+ mName = in.readString();
+ }
+ mLevels = new float[in.readInt()];
+ in.readFloatArray(mLevels);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mName != null ? 1 : 0);
+ if (mName != null) {
+ dest.writeString(mName);
+ }
+ dest.writeInt(mLevels.length);
+ dest.writeFloatArray(mLevels);
+ }
+
+ public static final Parcelable.Creator<Preset> CREATOR = new Parcelable.Creator<Preset>() {
+ @Override
+ public Preset createFromParcel(Parcel in) {
+ return new Preset(in);
+ }
+
+ @Override
+ public Preset[] newArray(int size) {
+ return new Preset[size];
+ }
+ };
+
+ public String getName() {
+ return mName;
+ }
+
+ public static class StaticPreset extends Preset {
+ public StaticPreset(String name, float[] levels) {
+ super(name, levels);
+ }
+ }
+
+ public static class CustomPreset extends Preset {
+
+ private boolean mLocked;
+
+ public CustomPreset(String name, float[] levels, boolean locked) {
+ super(name, levels);
+ mLocked = locked;
+ }
+
+ public boolean isLocked() {
+ return mLocked;
+ }
+
+ public void setLocked(boolean locked) {
+ mLocked = locked;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public void setLevel(int band, float level) {
+ mLevels[band] = level;
+ }
+
+ public void setLevels(float[] levels) {
+ for (int i = 0; i < levels.length; i++) {
+ mLevels[i] = levels[i];
+ }
+ }
+
+ public float getLevel(int band) {
+ return mLevels[band];
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CustomPreset) {
+ return super.equals(o) && mLocked == ((CustomPreset) o).mLocked;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "|" + mLocked;
+ }
+
+ public static CustomPreset fromString(String input) {
+ final String[] split = input.split("\\|");
+ if (split == null || split.length != 3) {
+ return null;
+ }
+ float[] levels = EqUtils.stringBandsToFloats(split[1]);
+ return new CustomPreset(split[0], levels, Boolean.valueOf(split[2]));
+ }
+
+ public static final Parcelable.Creator<CustomPreset> CREATOR
+ = new Parcelable.Creator<CustomPreset>() {
+ @Override
+ public CustomPreset createFromParcel(Parcel in) {
+ return new CustomPreset(in);
+ }
+
+ @Override
+ public CustomPreset[] newArray(int size) {
+ return new CustomPreset[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mLocked ? 1 : 0);
+ }
+
+ protected CustomPreset(Parcel in) {
+ super(in);
+ mLocked = in.readInt() == 1;
+ }
+
+ }
+
+ public static class PermCustomPreset extends CustomPreset {
+
+ public PermCustomPreset(String name, float[] levels) {
+ super(name, levels, false);
+ }
+
+ @Override
+ public String toString() {
+ return mName + "|" + EqUtils.floatLevelsToString(mLevels);
+ }
+
+ public static PermCustomPreset fromString(String input) {
+ final String[] split = input.split("\\|");
+ if (split == null || split.length != 2) {
+ return null;
+ }
+ float[] levels = EqUtils.stringBandsToFloats(split[1]);
+ return new PermCustomPreset(split[0], levels);
+ }
+
+ protected PermCustomPreset(Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ public static final Creator<PermCustomPreset> CREATOR = new Creator<PermCustomPreset>() {
+ @Override
+ public PermCustomPreset createFromParcel(Parcel in) {
+ return new PermCustomPreset(in);
+ }
+
+ @Override
+ public PermCustomPreset[] newArray(int size) {
+ return new PermCustomPreset[size];
+ }
+ };
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/activity/ActivityMusic.java b/src/org/cyanogenmod/audiofx/audiofx/activity/ActivityMusic.java
new file mode 100644
index 0000000..f75273d
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/activity/ActivityMusic.java
@@ -0,0 +1,198 @@
+package com.cyngn.audiofx.activity;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import com.cyanogen.ambient.analytics.Event;
+import com.cyngn.audiofx.AudioFxApplication;
+import com.cyngn.audiofx.Constants;
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.fragment.AudioFxFragment;
+import com.cyngn.audiofx.knobs.KnobCommander;
+import com.cyngn.audiofx.service.AudioFxService;
+import com.cyngn.audiofx.stats.AppState;
+import com.cyngn.audiofx.stats.UserSession;
+
+public class ActivityMusic extends Activity {
+
+ private static final String TAG = ActivityMusic.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final String TAG_AUDIOFX = "audiofx";
+ public static final String EXTRA_CALLING_PACKAGE = "audiofx::extra_calling_package";
+
+ private CheckBox mCurrentDeviceToggle;
+ MasterConfigControl mConfig;
+ String mCallingPackage;
+
+ private boolean mWaitingForService = true;
+ private SharedPreferences.OnSharedPreferenceChangeListener mServiceReadyObserver;
+
+ private CompoundButton.OnCheckedChangeListener mGlobalEnableToggleListener
+ = new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(final CompoundButton buttonView,
+ final boolean isChecked) {
+ if (UserSession.getInstance() != null) {
+ UserSession.getInstance().deviceEnabledDisabled();
+ }
+ mConfig.setCurrentDeviceEnabled(isChecked);
+ }
+ };
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ if (DEBUG)
+ Log.i(TAG, "onCreate() called with "
+ + "savedInstanceState = [" + savedInstanceState + "]");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ mCallingPackage = getIntent().getStringExtra(EXTRA_CALLING_PACKAGE);
+ Log.i(TAG, "calling package: " + mCallingPackage);
+
+ mConfig = MasterConfigControl.getInstance(this);
+
+ final SharedPreferences globalPrefs = Constants.getGlobalPrefs(this);
+
+ mWaitingForService = !defaultsSetup();
+ if (mWaitingForService) {
+ Log.w(TAG, "waiting for service.");
+ mServiceReadyObserver = new SharedPreferences.OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ String key) {
+ if (key.equals(Constants.SAVED_DEFAULTS) && defaultsSetup()) {
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
+ mConfig.onResetDefaults();
+ init(savedInstanceState);
+
+ mWaitingForService = false;
+ invalidateOptionsMenu();
+ mServiceReadyObserver = null;
+ }
+ }
+ };
+ globalPrefs.registerOnSharedPreferenceChangeListener(mServiceReadyObserver);
+ startService(new Intent(ActivityMusic.this, AudioFxService.class));
+ // TODO add loading fragment if service initialization takes too long
+ } else {
+ init(savedInstanceState);
+ }
+ }
+
+ private boolean defaultsSetup() {
+ final int targetVersion = Constants.CURRENT_PREFS_INT_VERSION;
+ final SharedPreferences prefs = Constants.getGlobalPrefs(this);
+ final int currentVersion = prefs.getInt(Constants.AUDIOFX_GLOBAL_PREFS_VERSION_INT, 0);
+ final boolean defaultsSaved = prefs.getBoolean(Constants.SAVED_DEFAULTS, false);
+ return defaultsSaved && currentVersion >= targetVersion;
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ // should null it out if one was there, compat redirector with package will go through onCreate
+ mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mServiceReadyObserver != null) {
+ Constants.getGlobalPrefs(this)
+ .unregisterOnSharedPreferenceChangeListener(mServiceReadyObserver);
+ mServiceReadyObserver = null;
+ }
+ super.onDestroy();
+ }
+
+ private void init(Bundle savedInstanceState) {
+ mConfig = MasterConfigControl.getInstance(this);
+
+ ActionBar ab = getActionBar();
+ ab.setTitle(R.string.app_title);
+ ab.setDisplayShowTitleEnabled(true);
+
+ final View extraView = LayoutInflater.from(this)
+ .inflate(R.layout.action_bar_custom_components, null);
+ ActionBar.LayoutParams lp = new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
+ ActionBar.LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.CENTER_VERTICAL);
+ ab.setCustomView(extraView, lp);
+ ab.setDisplayShowCustomEnabled(true);
+
+ mCurrentDeviceToggle = (CheckBox) ab.getCustomView().findViewById(R.id.global_toggle);
+ mCurrentDeviceToggle.setOnCheckedChangeListener(mGlobalEnableToggleListener);
+
+ if (savedInstanceState == null && findViewById(R.id.main_fragment) != null) {
+ getFragmentManager()
+ .beginTransaction()
+ .add(R.id.main_fragment, new AudioFxFragment(), TAG_AUDIOFX)
+ .commit();
+ }
+ applyOemDecor();
+ }
+
+ private void applyOemDecor() {
+ ActionBar ab = getActionBar();
+ if (mConfig.hasMaxxAudio()) {
+ ab.setSubtitle(R.string.powered_by_maxx_audio);
+ } else if (mConfig.hasDts()) {
+ final ViewStub stub = (ViewStub) ab.getCustomView().findViewById(R.id.logo_stub);
+ stub.setLayoutResource(R.layout.action_bar_dts_logo);
+ stub.inflate();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ if (DEBUG) Log.i(TAG, "onResume() called with " + "");
+ super.onResume();
+
+ // initiate a new session
+ new UserSession(mCallingPackage);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ if (DEBUG) Log.d(TAG, "Session: " + UserSession.getInstance());
+
+ final Event.Builder builder = new Event.Builder("session", "ended");
+ UserSession.getInstance().append(builder);
+ AppState.appendState(mConfig, KnobCommander.getInstance(this), builder);
+ ((AudioFxApplication) getApplicationContext()).sendEvent(builder.build());
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (DEBUG) Log.i(TAG, "onConfigurationChanged() called with "
+ + "newConfig = [" + newConfig + "]");
+ if (newConfig.orientation != getResources().getConfiguration().orientation) {
+ mCurrentDeviceToggle = null;
+ }
+ }
+
+ public void setGlobalToggleChecked(boolean checked) {
+ if (mCurrentDeviceToggle != null) {
+ mCurrentDeviceToggle.setOnCheckedChangeListener(null);
+ mCurrentDeviceToggle.setChecked(checked);
+ mCurrentDeviceToggle.setOnCheckedChangeListener(mGlobalEnableToggleListener);
+ }
+ }
+
+ public CompoundButton getGlobalSwitch() {
+ return mCurrentDeviceToggle;
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/ControlPanelPicker.java b/src/org/cyanogenmod/audiofx/audiofx/activity/ControlPanelPicker.java
index 1c6eaaf..a9fd876 100644
--- a/src/org/cyanogenmod/audiofx/ControlPanelPicker.java
+++ b/src/org/cyanogenmod/audiofx/audiofx/activity/ControlPanelPicker.java
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package org.cyanogenmod.audiofx;
+package com.cyngn.audiofx.activity;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.internal.app.AlertController.AlertParams.OnPrepareListViewListener;
-import org.cyanogenmod.audiofx.Compatibility.Service;
+import com.cyngn.audiofx.Compatibility;
+import com.cyngn.audiofx.Compatibility.Service;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
@@ -31,11 +32,8 @@ import android.database.Cursor;
import android.database.MatrixCursor;
import android.media.audiofx.AudioEffect;
import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ListView;
+import com.cyngn.audiofx.R;
import java.util.List;
@@ -81,9 +79,9 @@ public class ControlPanelPicker extends AlertActivity implements OnClickListener
p.mOnClickListener = mItemClickListener;
p.mLabelColumn = "title";
p.mIsSingleChoice = true;
- p.mPositiveButtonText = getString(com.android.internal.R.string.ok);
+ p.mPositiveButtonText = getString(getOkStringResId());
p.mPositiveButtonListener = this;
- p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+ p.mNegativeButtonText = getString(getCancelStringResId());
p.mOnPrepareListViewListener = this;
p.mTitle = getString(R.string.picker_title);
p.mCheckedItem = defpanelidx;
@@ -91,6 +89,14 @@ public class ControlPanelPicker extends AlertActivity implements OnClickListener
setupAlert();
}
+ private int getOkStringResId() {
+ return getResources().getIdentifier("ok", "string", "android");
+ }
+
+ private int getCancelStringResId() {
+ return getResources().getIdentifier("cancel", "string", "android");
+ }
+
private DialogInterface.OnClickListener mItemClickListener =
new DialogInterface.OnClickListener() {
diff --git a/src/org/cyanogenmod/audiofx/audiofx/activity/EqualizerManager.java b/src/org/cyanogenmod/audiofx/audiofx/activity/EqualizerManager.java
new file mode 100644
index 0000000..03e9853
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/activity/EqualizerManager.java
@@ -0,0 +1,634 @@
+package com.cyngn.audiofx.activity;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.widget.CompoundButton;
+
+import com.cyngn.audiofx.Constants;
+import com.cyngn.audiofx.Preset;
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.eq.EqUtils;
+import com.cyngn.audiofx.service.AudioFxService;
+import com.cyngn.audiofx.stats.UserSession;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class EqualizerManager {
+
+ private static final String TAG = EqualizerManager.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final MasterConfigControl mConfig;
+ private final Context mContext;
+
+ private float mMinFreq;
+ private float mMaxFreq;
+
+ private float mMinDB;
+ private float mMaxDB;
+ private int mNumBands;
+ private CompoundButton.OnCheckedChangeListener mLockChangeListener;
+
+ /*
+ * presets from the library custom preset.
+ */
+ private int mPredefinedPresets;
+ private float[] mCenterFreqs;
+ private float[] mGlobalLevels;
+
+ private AtomicBoolean mAnimatingToCustom = new AtomicBoolean(false);
+
+ // whether we are in between presets, animating them and such
+ private boolean mChangingPreset = false;
+
+ private int mCurrentPreset;
+
+ private final ArrayList<Preset> mEqPresets = new ArrayList<Preset>();
+ private int mEQCustomPresetPosition;
+
+ private String mZeroedBandString;
+
+ private static final int MSG_SAVE_PRESETS = 1;
+ private static final int MSG_SEND_EQ_OVERRIDE = 2;
+
+ private Handler mHandler = new Handler(new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SAVE_PRESETS:
+ Constants.saveCustomPresets(mContext, mEqPresets);
+ break;
+ case MSG_SEND_EQ_OVERRIDE:
+ mConfig.overrideEqLevels((short)msg.arg1, (short) msg.arg2);
+ break;
+ }
+ return true;
+ }}, true);
+
+ public EqualizerManager(Context context, MasterConfigControl config) {
+ mContext = context;
+ mConfig = config;
+
+ applyDefaults();
+ }
+
+ public void applyDefaults() {
+ mEqPresets.clear();
+ // setup eq
+ int bands = Integer.parseInt(getGlobalPref("equalizer.number_of_bands", "5"));
+ final int[] centerFreqs = Constants.getCenterFreqs(mContext, bands);
+ final int[] bandLevelRange = Constants.getBandLevelRange(mContext);
+
+ float[] centerFreqsKHz = new float[centerFreqs.length];
+ for (int i = 0; i < centerFreqs.length; i++) {
+ centerFreqsKHz[i] = (float) centerFreqs[i] / 1000.0f;
+ }
+
+ mMinDB = bandLevelRange[0] / 100;
+ mMaxDB = bandLevelRange[1] / 100;
+
+ mNumBands = centerFreqsKHz.length;
+ mGlobalLevels = new float[mNumBands];
+ for (int i = 0; i < mGlobalLevels.length; i++) {
+ mGlobalLevels[i] = 0;
+ }
+
+ mZeroedBandString = EqUtils.getZeroedBandsString(getNumBands());
+
+ mCenterFreqs = Arrays.copyOf(centerFreqsKHz, mNumBands);
+ System.arraycopy(centerFreqsKHz, 0, mCenterFreqs, 0, mNumBands);
+ mMinFreq = mCenterFreqs[0] / 2;
+ mMaxFreq = (float) Math.pow(mCenterFreqs[mNumBands - 1], 2) / mCenterFreqs[mNumBands - 2] / 2;
+
+ // setup equalizer presets
+ final int numPresets = Integer.parseInt(getGlobalPref("equalizer.number_of_presets", "0"));
+
+ if (numPresets > 0) {
+ // add library-provided presets
+ String[] presetNames = getGlobalPref("equalizer.preset_names", "").split("\\|");
+ mPredefinedPresets = presetNames.length + 1; // we consider first EQ to be part of predefined
+ for (int i = 0; i < numPresets; i++) {
+ mEqPresets.add(new Preset.StaticPreset(presetNames[i], getPersistedPresetLevels(i)));
+ }
+ } else {
+ mPredefinedPresets = 1; // custom is predefined
+ }
+ // add custom preset
+ mEqPresets.add(new Preset.PermCustomPreset(mContext.getString(R.string.user),
+ getPersistedCustomLevels()));
+ mEQCustomPresetPosition = mEqPresets.size() - 1;
+
+ // restore custom prefs
+ mEqPresets.addAll(Constants.getCustomPresets(mContext, mNumBands));
+
+ // setup default preset for speaker
+ mCurrentPreset = Integer.parseInt(getPref(Constants.DEVICE_AUDIOFX_EQ_PRESET, "0"));
+ if (mCurrentPreset > mEqPresets.size() - 1) {
+ mCurrentPreset = 0;
+ }
+ setPreset(mCurrentPreset);
+ }
+
+ public boolean isUserPreset() {
+ boolean result = mCurrentPreset >= mPredefinedPresets;
+ /*if (DEBUG) {
+ Log.i(TAG, "isUserPreset(), current preset: " + mCurrentPreset);
+ Log.i(TAG, "----> predefined presets: " + mPredefinedPresets);
+ Log.d(TAG, "----> RESULT: " + result);
+ }*/
+ return result;
+ }
+
+ public boolean isCustomPreset() {
+ return mCurrentPreset == mEQCustomPresetPosition;
+ }
+
+ public CompoundButton.OnCheckedChangeListener getLockChangeListener() {
+ if (mLockChangeListener == null) {
+ mLockChangeListener = new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isUserPreset()) {
+ ((Preset.CustomPreset) mEqPresets.get(mCurrentPreset)).setLocked(isChecked);
+ }
+ }
+ };
+ }
+ return mLockChangeListener;
+ }
+
+ public boolean isChangingPresets() {
+ return mChangingPreset;
+ }
+
+ public void setChangingPresets(boolean changing) {
+ if (mChangingPreset != changing) {
+ mChangingPreset = changing;
+ if (changing) {
+ mConfig.getCallbacks().notifyEqControlStateChanged(false, false, false, false);
+ } else {
+ updateEqControls();
+ }
+ }
+ }
+
+ public boolean isAnimatingToCustom() {
+ return mAnimatingToCustom.get();
+ }
+
+ public void setAnimatingToCustom(boolean animating) {
+ mAnimatingToCustom.set(animating);
+ if (!animating) {
+ // finished animation
+ updateEqControls();
+ }
+ }
+
+ private void savePresetsDelayed() {
+ mHandler.sendEmptyMessageDelayed(MSG_SAVE_PRESETS, 500);
+ }
+
+ public int indexOf(Preset p) {
+ return mEqPresets.indexOf(p);
+ }
+
+ void onPreDeviceChanged() {
+ // need to update the current preset based on the device here.
+ int newPreset = Integer.parseInt(getPref(Constants.DEVICE_AUDIOFX_EQ_PRESET, "0"));
+ if (newPreset > mEqPresets.size() - 1) {
+ newPreset = 0;
+ }
+
+ // this should be ready to go for callbacks to query the new device preset below
+ mCurrentPreset = newPreset;
+
+ }
+
+ void onPostDeviceChanged() {
+ setPreset(mCurrentPreset, false);
+ }
+
+ public Preset getCurrentPreset() {
+ return mEqPresets.get(mCurrentPreset);
+ }
+
+ /**
+ * Copy current config levels from the current preset into custom values since the user has
+ * initiated some change. Then update the current preset to 'custom'.
+ */
+ public int copyToCustom() {
+ updateGlobalLevels(mCurrentPreset);
+ if (DEBUG) {
+ Log.w(TAG, "using levels from preset: " + mCurrentPreset + ": " + Arrays.toString(mGlobalLevels));
+ }
+
+ String levels = EqUtils.floatLevelsToString(
+ EqUtils.convertDecibelsToMillibels(
+ mEqPresets.get(mCurrentPreset).getLevels()));
+ setGlobalPref("custom", levels);
+
+ ((Preset.PermCustomPreset) mEqPresets.get(mEQCustomPresetPosition)).setLevels(mGlobalLevels);
+ if (DEBUG)
+ Log.i(TAG, "copyToCustom() wrote current preset levels to index: " + mEQCustomPresetPosition);
+ setPreset(mEQCustomPresetPosition);
+ savePresetsDelayed();
+ return mEQCustomPresetPosition;
+ }
+
+ public int addPresetFromCustom() {
+ updateGlobalLevels(mEQCustomPresetPosition);
+ if (DEBUG) {
+ Log.w(TAG, "using levels from preset: " + mCurrentPreset + ": " + Arrays.toString(mGlobalLevels));
+ }
+
+ int writtenToIndex = addPreset(mGlobalLevels);
+ if (DEBUG)
+ Log.i(TAG, "addPresetFromCustom() wrote current preset levels to index: " + writtenToIndex);
+ setPreset(writtenToIndex);
+ savePresetsDelayed();
+ return writtenToIndex;
+ }
+
+ /**
+ * Loops through all presets. And finds the first preset that can be written to.
+ * If one is not found, then one is inserted, and that new index is returned.
+ * @return the index that the levels were copied to
+ */
+ private int addPreset(float[] levels) {
+ if (UserSession.getInstance() != null) {
+ UserSession.getInstance().presetCreated();
+ }
+
+ final int customPresets = Constants.getCustomPresets(mContext, mNumBands).size();
+ // format the name so it's like "Custom <N>", start with "Custom 2"
+ final String name = String.format(mContext.getString(R.string.user_n), customPresets + 2);
+
+ Preset.CustomPreset customPreset = new Preset.CustomPreset(name, levels, false);
+ mEqPresets.add(customPreset);
+
+ mConfig.getCallbacks().notifyPresetsChanged();
+
+ return mEqPresets.size() - 1;
+ }
+
+ /**
+ * Set a new level!
+ * <p/>
+ * This call will be propogated to all listeners registered with addEqStateChangeCallback().
+ *
+ * @param band the band index the band index which changed
+ * @param dB the new decibel value
+ * @param systemChange is this change generated by the system?
+ */
+ public void setLevel(final int band, final float dB, final boolean fromSystem) {
+ if (DEBUG) Log.i(TAG, "setLevel(" + band + ", " + dB + ", " + fromSystem + ")");
+
+ mGlobalLevels[band] = dB;
+
+ if (fromSystem && !mConfig.isUserDeviceOverride()) {
+ // quickly convert decibel to millibel and send away to the service
+ mHandler.obtainMessage(MSG_SEND_EQ_OVERRIDE, band, (short) (dB * 100)).sendToTarget();
+ }
+
+ mConfig.getCallbacks().notifyBandLevelChangeChanged(band, dB, fromSystem);
+
+ if (!fromSystem) { // user is touching
+ // persist
+
+ final Preset preset = mEqPresets.get(mCurrentPreset);
+ if (preset instanceof Preset.CustomPreset) {
+ if (mAnimatingToCustom.get()) {
+ if (DEBUG) {
+ Log.d(TAG, "setLevel() not persisting new custom band becuase animating.");
+ }
+ } else {
+ ((Preset.CustomPreset) preset).setLevel(band, dB);
+ if (preset instanceof Preset.PermCustomPreset) {
+ // store these as millibels
+ String levels = EqUtils.floatLevelsToString(
+ EqUtils.convertDecibelsToMillibels(
+ preset.getLevels()));
+ setGlobalPref("custom", levels);
+ }
+ }
+ // needs to be updated immediately here for the service.
+ final String levels = EqUtils.floatLevelsToString(preset.getLevels());
+ setPref(Constants.DEVICE_AUDIOFX_EQ_PRESET_LEVELS, levels);
+
+ mConfig.updateService(AudioFxService.EQ_CHANGED);
+ }
+ savePresetsDelayed();
+ }
+ }
+
+ /**
+ * Set a new preset index.
+ * <p/>
+ * This call will be propogated to all listeners registered with addEqStateChangeCallback().
+ *
+ * @param newPresetIndex the new preset index.
+ */
+ public void setPreset(final int newPresetIndex, boolean updateBackend) {
+ mCurrentPreset = newPresetIndex;
+ updateEqControls(); // do this before callback is propogated
+
+ mConfig.getCallbacks().notifyPresetChanged(newPresetIndex);
+
+ // persist
+ setPref(Constants.DEVICE_AUDIOFX_EQ_PRESET, String.valueOf(newPresetIndex));
+
+ // update mGlobalLevels
+ float[] newlevels = getPresetLevels(newPresetIndex);
+ for (int i = 0; i < newlevels.length; i++) {
+ setLevel(i, newlevels[i], true);
+ }
+
+ setPref(Constants.DEVICE_AUDIOFX_EQ_PRESET_LEVELS, EqUtils.floatLevelsToString(newlevels));
+
+ if (updateBackend) {
+ mConfig.updateService(AudioFxService.EQ_CHANGED);
+ }
+ }
+
+ public void setPreset(final int newPresetIndex) {
+ setPreset(newPresetIndex, true);
+ }
+
+ private void updateEqControls() {
+ final boolean userPreset = isUserPreset();
+ mConfig.getCallbacks().notifyEqControlStateChanged(mEQCustomPresetPosition == mCurrentPreset,
+ userPreset, userPreset, userPreset);
+ }
+
+ /**
+ * @return Get the current preset index
+ */
+ public int getCurrentPresetIndex() {
+ return mCurrentPreset;
+ }
+
+ /*===============
+ * eq methods
+ *===============*/
+
+ public float projectX(double freq) {
+ double pos = Math.log(freq);
+ double minPos = Math.log(mMinFreq);
+ double maxPos = Math.log(mMaxFreq);
+ return (float) ((pos - minPos) / (maxPos - minPos));
+ }
+
+ public double reverseProjectX(float pos) {
+ double minPos = Math.log(mMinFreq);
+ double maxPos = Math.log(mMaxFreq);
+ return Math.exp(pos * (maxPos - minPos) + minPos);
+ }
+
+ public float projectY(double dB) {
+ double pos = (dB - mMinDB) / (mMaxDB - mMinDB);
+ return (float) (1 - pos);
+ }
+
+ public static double lin2dB(double rho) {
+ return rho != 0 ? Math.log(rho) / Math.log(10) * 20 : -99.9;
+ }
+
+ public float getMinFreq() {
+ return mMinFreq;
+ }
+
+ public float getMaxFreq() {
+ return mMaxFreq;
+ }
+
+ public float getMinDB() {
+ return mMinDB;
+ }
+
+ public float getMaxDB() {
+ return mMaxDB;
+ }
+
+ public int getNumBands() {
+ return mNumBands;
+ }
+
+ public float getCenterFreq(int band) {
+ return mCenterFreqs[band];
+ }
+
+ public float[] getCenterFreqs() {
+ return mCenterFreqs;
+ }
+
+ public float[] getLevels() {
+ return mGlobalLevels;
+ }
+
+ public float getLevel(int band) {
+ return mGlobalLevels[band];
+ }
+
+ /*===============
+ * preset methods
+ *===============*/
+
+ public float[] getPersistedPresetLevels(int presetIndex) {
+ String newLevels = null;
+
+ if (mEqPresets.size() > presetIndex
+ && mEqPresets.get(presetIndex) instanceof Preset.PermCustomPreset) {
+ return getPersistedCustomLevels();
+ } else {
+ newLevels = getGlobalPref("equalizer.preset." + presetIndex, mZeroedBandString);
+ }
+
+ // stored as millibels, convert to decibels
+ float[] levels = EqUtils.stringBandsToFloats(newLevels);
+ return EqUtils.convertMillibelsToDecibels(levels);
+ }
+
+ private float[] getPersistedCustomLevels() {
+ String newLevels = getGlobalPref("custom", mZeroedBandString);
+ // stored as millibels, convert to decibels
+ float[] levels = EqUtils.stringBandsToFloats(newLevels);
+ return EqUtils.convertMillibelsToDecibels(levels);
+ }
+
+ /**
+ * Get preset levels in decibels for a given index
+ *
+ * @param presetIndex index which to fetch preset levels for
+ * @return an array of floats[] with the given index's preset levels
+ */
+ public float[] getPresetLevels(int presetIndex) {
+ return mEqPresets.get(presetIndex).getLevels();
+ }
+
+ /**
+ * Helper method which maps a preset index to a color value.
+ *
+ * @param index the preset index which to fetch a color for
+ * @return a color which is associated with this preset.
+ */
+ public int getAssociatedPresetColorHex(int index) {
+ int r = -1;
+ index = index % mEqPresets.size();
+ if (mEqPresets.get(index) instanceof Preset.CustomPreset) {
+ r = R.color.preset_custom;
+ } else {
+ switch (index) {
+ case 0:
+ r = R.color.preset_normal;
+ break;
+ case 1:
+ r = R.color.preset_classical;
+ break;
+ case 2:
+ r = R.color.preset_dance;
+ break;
+ case 3:
+ r = R.color.preset_flat;
+ break;
+ case 4:
+ r = R.color.preset_folk;
+ break;
+ case 5:
+ r = R.color.preset_metal;
+ break;
+ case 6:
+ r = R.color.preset_hiphop;
+ break;
+ case 7:
+ r = R.color.preset_jazz;
+ break;
+ case 8:
+ r = R.color.preset_pop;
+ break;
+ case 9:
+ r = R.color.preset_rock;
+ break;
+ case 10:
+ r = R.color.preset_electronic;
+ break;
+ case 11:
+ r = R.color.preset_small_speakers;
+ break;
+ default:
+ return r;
+ }
+ }
+ return mContext.getResources().getColor(r);
+ }
+
+ /**
+ * Get total number of presets
+ *
+ * @return int value with total number of presets
+ */
+ public int getPresetCount() {
+ return mEqPresets.size();
+ }
+
+ public Preset getPreset(int index) {
+ return mEqPresets.get(index);
+ }
+
+ public String getLocalizedPresetName(int index) {
+ // already localized
+ return localizePresetName(mEqPresets.get(index).getName());
+ }
+
+ private final String localizePresetName(final String name) {
+ // missing electronic, multimedia, small speakers, custom
+ final String[] names = {
+ "Normal", "Classical", "Dance", "Flat", "Folk",
+ "Heavy Metal", "Hip Hop", "Jazz", "Pop", "Rock",
+ "Electronic", "Small speakers", "Multimedia",
+ "Custom"
+ };
+ final int[] ids = {
+ R.string.normal, R.string.classical, R.string.dance, R.string.flat, R.string.folk,
+ R.string.heavy_metal, R.string.hip_hop, R.string.jazz, R.string.pop, R.string.rock,
+ R.string.ci_extreme, R.string.small_speakers, R.string.multimedia,
+ R.string.user
+ };
+
+ for (int i = names.length - 1; i >= 0; --i) {
+ if (names[i].equalsIgnoreCase(name)) {
+ return mContext.getString(ids[i]);
+ }
+ }
+ return name;
+ }
+
+ public boolean isEqualizerLocked() {
+ return getCurrentPreset() instanceof Preset.CustomPreset
+ && !(getCurrentPreset() instanceof Preset.PermCustomPreset)
+ && ((Preset.CustomPreset) getCurrentPreset()).isLocked();
+ }
+
+ public void renameCurrentPreset(String s) {
+ if (UserSession.getInstance() != null) {
+ UserSession.getInstance().presetRenamed();
+ }
+
+ if (isUserPreset()) {
+ ((Preset.CustomPreset) getCurrentPreset()).setName(s);
+ }
+
+ mConfig.getCallbacks().notifyPresetsChanged();
+
+ savePresetsDelayed();
+ }
+
+ public boolean removePreset(int index) {
+ if (UserSession.getInstance() != null) {
+ UserSession.getInstance().presetRemoved();
+ }
+
+ if (index > mEQCustomPresetPosition) {
+ mEqPresets.remove(index);
+ mConfig.getCallbacks().notifyPresetsChanged();
+
+ if (mCurrentPreset == index) {
+ if (DEBUG) {
+ Log.w(TAG, "removePreset() called on current preset, changing preset");
+ }
+ updateGlobalLevels(mCurrentPreset - 1);
+ setPreset(mCurrentPreset - 1);
+ }
+ savePresetsDelayed();
+ return true;
+ }
+ return false;
+ }
+
+ private void updateGlobalLevels(int presetIndexToCopy) {
+ final float[] presetLevels = getPresetLevels(presetIndexToCopy);
+ for (int i = 0; i < mGlobalLevels.length; i++) {
+ mGlobalLevels[i] = presetLevels[i];
+ }
+ }
+
+ // I AM SO LAZY!
+ private String getGlobalPref(String key, String defValue) {
+ return mConfig.getGlobalPrefs().getString(key, defValue);
+ }
+
+ private void setGlobalPref(String key, String value) {
+ mConfig.getGlobalPrefs().edit().putString(key, value).apply();
+ }
+
+ private String getPref(String key, String defValue) {
+ return mConfig.getPrefs().getString(key, defValue);
+ }
+
+ private void setPref(String key, String value) {
+ mConfig.getPrefs().edit().putString(key, value).apply();
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/activity/MasterConfigControl.java b/src/org/cyanogenmod/audiofx/audiofx/activity/MasterConfigControl.java
new file mode 100644
index 0000000..d83d0de
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/activity/MasterConfigControl.java
@@ -0,0 +1,367 @@
+package com.cyngn.audiofx.activity;
+
+import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
+import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
+import static android.media.AudioDeviceInfo.TYPE_DOCK;
+import static android.media.AudioDeviceInfo.TYPE_IP;
+import static android.media.AudioDeviceInfo.TYPE_LINE_ANALOG;
+import static android.media.AudioDeviceInfo.TYPE_LINE_DIGITAL;
+import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
+import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE;
+import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
+import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET;
+import static android.media.AudioDeviceInfo.convertDeviceTypeToInternalDevice;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+import com.cyngn.audiofx.Constants;
+import com.cyngn.audiofx.service.AudioFxService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Master configuration class for AudioFX.
+ *
+ * Contains the main hub where data is stored for the current eq graph (which there should be
+ * one of, thus only once instance of this class exists).
+ *
+ * Anyone can obtain an instance of this class. If one does not exist, a new one is created.
+ * Immediately before the new instance creation happens, some defaults are pre-populated
+ * with MasterConfigControl.saveDefaults(). That method doesn't ever have to be directly called.
+ */
+public class MasterConfigControl {
+
+ private static final String TAG = MasterConfigControl.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean SERVICE_DEBUG = false;
+
+ private final Context mContext;
+
+ private AudioFxService.LocalBinder mService;
+ private ServiceConnection mServiceConnection;
+ private int mServiceRefCount = 0;
+
+ private AudioDeviceInfo mCurrentDevice;
+ private AudioDeviceInfo mUserDeviceOverride;
+
+ private final StateCallbacks mCallbacks;
+ private final EqualizerManager mEqManager;
+ private final AudioManager mAudioManager;
+
+ private static MasterConfigControl sInstance;
+ private boolean mShouldBindToService = false;
+
+ public static MasterConfigControl getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new MasterConfigControl(context);
+ }
+ return sInstance;
+ }
+
+ private MasterConfigControl(Context context) {
+ mContext = context.getApplicationContext();
+
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+ mCallbacks = new StateCallbacks(this);
+ mEqManager = new EqualizerManager(context, this);
+ }
+
+ public void onResetDefaults() {
+ mEqManager.applyDefaults();
+ }
+
+ public synchronized boolean bindService() {
+ boolean conn = true;
+ if (SERVICE_DEBUG) Log.i(TAG, "bindService() refCount=" + mServiceRefCount);
+ if (mServiceConnection == null && mServiceRefCount == 0) {
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ if (SERVICE_DEBUG) Log.i(TAG, "onServiceConnected refCount=" + mServiceRefCount);
+ mService = ((AudioFxService.LocalBinder) binder);
+ LocalBroadcastManager.getInstance(mContext).registerReceiver(
+ mDeviceChangeReceiver,
+ new IntentFilter(AudioFxService.ACTION_DEVICE_OUTPUT_CHANGED));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (SERVICE_DEBUG) Log.w(TAG, "onServiceDisconnected refCount =" + mServiceRefCount);
+ LocalBroadcastManager.getInstance(mContext).unregisterReceiver(
+ mDeviceChangeReceiver);
+ mService = null;
+ }
+ };
+
+ Intent serviceIntent = new Intent(mContext, AudioFxService.class);
+ conn = mContext.bindService(serviceIntent, mServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+ if (conn) {
+ mServiceRefCount++;
+ }
+ return mServiceRefCount > 0;
+ }
+
+ public synchronized void unbindService() {
+ if (SERVICE_DEBUG) Log.i(TAG, "unbindService() called refCount=" + mServiceRefCount);
+ if (mServiceRefCount > 0) {
+ mServiceRefCount--;
+ if (mServiceRefCount == 0) {
+ mContext.unbindService(mServiceConnection);
+ mService = null;
+ mServiceConnection = null;
+ }
+ }
+ }
+
+ public boolean checkService() {
+ if (mService == null && mServiceRefCount == 0 && mShouldBindToService) {
+ Log.e(TAG, "Service went away, rebinding");
+ bindService();
+ }
+ return mService != null;
+ }
+
+ private final BroadcastReceiver mDeviceChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int device = intent.getIntExtra("device", -1);
+ Log.d(TAG, "deviceChanged: " + device);
+ if (device > -1) {
+ AudioDeviceInfo info = getDeviceById(device);
+ if (info != null) {
+ setCurrentDevice(info, false);
+ }
+ }
+ }
+ };
+
+ public void updateService(int flags) {
+ if (checkService()) {
+ mService.update(flags);
+ }
+ }
+
+ public StateCallbacks getCallbacks() {
+ return mCallbacks;
+ }
+
+ public EqualizerManager getEqualizerManager() {
+ return mEqManager;
+ }
+
+ public synchronized void setCurrentDeviceEnabled(boolean isChecked) {
+ getPrefs().edit().putBoolean(Constants.DEVICE_AUDIOFX_GLOBAL_ENABLE, isChecked).apply();
+ getCallbacks().notifyGlobalToggle(isChecked);
+ updateService(AudioFxService.ALL_CHANGED);
+ }
+
+ public synchronized boolean isCurrentDeviceEnabled() {
+ return getPrefs().getBoolean(Constants.DEVICE_AUDIOFX_GLOBAL_ENABLE, false);
+ }
+
+ public synchronized SharedPreferences getGlobalPrefs() {
+ return mContext.getSharedPreferences(Constants.AUDIOFX_GLOBAL_FILE, 0);
+ }
+
+ /**
+ * Update the current device used when querying any device-specific values such as the current
+ * preset, or the user's custom eq preset settings.
+ *
+ * @param audioOutputRouting the new device key
+ */
+ public synchronized void setCurrentDevice(AudioDeviceInfo device, final boolean userSwitch) {
+
+ final AudioDeviceInfo current = getCurrentDevice();
+
+ Log.d(TAG, "setCurrentDevice name=" + (current == null ? null : current.getProductName()) +
+ " fromUser=" + userSwitch +
+ " cur=" + (current == null ? null : current.getType()) +
+ " new=" + (device == null ? null : device.getType()));
+
+ if (userSwitch) {
+ mUserDeviceOverride = device;
+ } else {
+ if (device != null) {
+ mCurrentDevice = device;
+ }
+ mUserDeviceOverride = null;
+ }
+
+ mEqManager.onPreDeviceChanged();
+
+ mCallbacks.notifyDeviceChanged(device, userSwitch);
+
+ mEqManager.onPostDeviceChanged();
+ }
+
+ public AudioDeviceInfo getSystemDevice() {
+ if (mCurrentDevice == null) {
+ final int forMusic = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
+ for (AudioDeviceInfo ai : getConnectedDevices()) {
+ if ((convertDeviceTypeToInternalDevice(ai.getType()) & forMusic) > 0) {
+ return ai;
+ }
+ }
+ }
+ return mCurrentDevice;
+ }
+
+ public boolean isUserDeviceOverride() {
+ return mUserDeviceOverride != null;
+ }
+
+ public AudioDeviceInfo getCurrentDevice() {
+ if (isUserDeviceOverride()) {
+ return mUserDeviceOverride;
+ }
+ return getSystemDevice();
+ }
+
+ public AudioDeviceInfo getDeviceById(int id) {
+ for (AudioDeviceInfo ai : mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
+ if (ai.getId() == id) {
+ return ai;
+ }
+ }
+ return null;
+ }
+
+ public List<AudioDeviceInfo> getConnectedDevices(int... filter) {
+ final List<AudioDeviceInfo> devices = new ArrayList<AudioDeviceInfo>();
+ for (AudioDeviceInfo ai : mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
+ if (filter.length == 0) {
+ devices.add(ai);
+ } else {
+ for (int i = 0; i < filter.length; i++) {
+ if (ai.getType() == filter[i]) {
+ devices.add(ai);
+ continue;
+ }
+ }
+ }
+ }
+ return devices;
+ }
+
+ public String getCurrentDeviceIdentifier() {
+ return getDeviceIdentifierString(getCurrentDevice());
+ }
+
+ public SharedPreferences getPrefs() {
+ return mContext.getSharedPreferences(getCurrentDeviceIdentifier(), 0);
+ }
+
+ public boolean hasDts() {
+ return getGlobalPrefs().getBoolean(Constants.AUDIOFX_GLOBAL_HAS_DTS, false);
+ }
+
+ public boolean hasMaxxAudio() {
+ return getGlobalPrefs().getBoolean(Constants.AUDIOFX_GLOBAL_HAS_MAXXAUDIO, false);
+ }
+
+ public boolean getMaxxVolumeEnabled() {
+ return getPrefs().getBoolean(Constants.DEVICE_AUDIOFX_MAXXVOLUME_ENABLE, false);
+ }
+
+ public boolean hasBassBoost() {
+ return getGlobalPrefs().getBoolean(Constants.AUDIOFX_GLOBAL_HAS_BASSBOOST, false);
+ }
+
+ public boolean hasVirtualizer() {
+ return getGlobalPrefs().getBoolean(Constants.AUDIOFX_GLOBAL_HAS_VIRTUALIZER, false);
+ }
+
+ public void setMaxxVolumeEnabled(boolean enable) {
+ getPrefs().edit().putBoolean(Constants.DEVICE_AUDIOFX_MAXXVOLUME_ENABLE, enable).apply();
+ updateService(AudioFxService.VOLUME_BOOST_CHANGED);
+ }
+
+ void overrideEqLevels(short band, short level) {
+ if (checkService()) {
+ mService.setOverrideLevels(band, level);
+ }
+ }
+
+ public static String getDeviceDisplayString(Context context, AudioDeviceInfo info) {
+ int type = info == null ? -1 : info.getType();
+ switch (type) {
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ return context.getString(com.cyngn.audiofx.R.string.device_headset);
+ case TYPE_LINE_ANALOG:
+ case TYPE_LINE_DIGITAL:
+ return context.getString(com.cyngn.audiofx.R.string.device_line_out);
+ case TYPE_BLUETOOTH_SCO:
+ case TYPE_BLUETOOTH_A2DP:
+ case TYPE_USB_DEVICE:
+ case TYPE_USB_ACCESSORY:
+ case TYPE_DOCK:
+ case TYPE_IP:
+ return info.getProductName().toString();
+ default:
+ return context.getString(com.cyngn.audiofx.R.string.device_speaker);
+ }
+ }
+
+ private static String appendProductName(AudioDeviceInfo info, String prefix) {
+ StringBuilder nm = new StringBuilder(prefix);
+ if (info != null && info.getProductName() != null) {
+ nm.append("-").append(info.getProductName().toString().replaceAll("\\W+", ""));
+ }
+ return nm.toString();
+ }
+
+ private static String appendDeviceAddress(AudioDeviceInfo info, String prefix) {
+ StringBuilder nm = new StringBuilder(prefix);
+ if (info != null && info.getAddress() != null) {
+ nm.append("-").append(info.getAddress().replace(":", ""));
+ }
+ return nm.toString();
+ }
+
+ public static String getDeviceIdentifierString(AudioDeviceInfo info) {
+ int type = info == null ? -1 : info.getType();
+ switch (type) {
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ return Constants.DEVICE_HEADSET;
+ case TYPE_LINE_ANALOG:
+ case TYPE_LINE_DIGITAL:
+ return Constants.DEVICE_LINE_OUT;
+ case TYPE_BLUETOOTH_SCO:
+ case TYPE_BLUETOOTH_A2DP:
+ return appendDeviceAddress(info, Constants.DEVICE_PREFIX_BLUETOOTH);
+ case TYPE_USB_DEVICE:
+ case TYPE_USB_ACCESSORY:
+ case TYPE_DOCK:
+ return appendProductName(info, Constants.DEVICE_PREFIX_USB);
+ case TYPE_IP:
+ return appendProductName(info, Constants.DEVICE_PREFIX_CAST);
+ default:
+ return Constants.DEVICE_SPEAKER;
+ }
+ }
+
+ /**
+ * Set whether to automatically attempt to bind to the service.
+ * @param bindToService
+ */
+ public void setAutoBindToService(boolean bindToService) {
+ mShouldBindToService = bindToService;
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/activity/StateCallbacks.java b/src/org/cyanogenmod/audiofx/audiofx/activity/StateCallbacks.java
new file mode 100644
index 0000000..0aa7379
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/activity/StateCallbacks.java
@@ -0,0 +1,154 @@
+
+package com.cyngn.audiofx.activity;
+
+import android.media.AudioDeviceInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StateCallbacks {
+
+ private static final String TAG = "StateCallbacks";
+
+ private final MasterConfigControl mConfig;
+
+ private final List<EqUpdatedCallback> mEqUpdateCallbacks = new ArrayList<EqUpdatedCallback>();
+
+ private final List<DeviceChangedCallback> mDeviceChangedCallbacks = new ArrayList<DeviceChangedCallback>();
+
+ private final List<EqControlStateCallback> mEqControlStateCallbacks = new ArrayList<EqControlStateCallback>();
+
+ StateCallbacks(MasterConfigControl config) {
+ mConfig = config;
+ }
+
+ /**
+ * Implement this callback to receive any changes called to the
+ * MasterConfigControl instance
+ */
+ public interface EqUpdatedCallback {
+ /**
+ * A band level has been changed
+ *
+ * @param band the band index which changed
+ * @param dB the new decibel value
+ * @param fromSystem whether the event was from the system or from the
+ * user
+ */
+ public void onBandLevelChange(int band, float dB, boolean fromSystem);
+
+ /**
+ * The preset has been set
+ *
+ * @param newPresetIndex the new preset index.
+ */
+ public void onPresetChanged(int newPresetIndex);
+
+ public void onPresetsChanged();
+ }
+
+ public void addEqUpdatedCallback(EqUpdatedCallback callback) {
+ synchronized (mEqUpdateCallbacks) {
+ mEqUpdateCallbacks.add(callback);
+ }
+ }
+
+ public void removeEqUpdatedCallback(EqUpdatedCallback callback) {
+ synchronized (mEqUpdateCallbacks) {
+ mEqUpdateCallbacks.remove(callback);
+ }
+ }
+
+ void notifyPresetsChanged() {
+ synchronized (mEqUpdateCallbacks) {
+ for (final EqUpdatedCallback callback : mEqUpdateCallbacks) {
+ callback.onPresetsChanged();
+ }
+ }
+ }
+
+ void notifyPresetChanged(final int index) {
+ synchronized (mEqUpdateCallbacks) {
+ for (final EqUpdatedCallback callback : mEqUpdateCallbacks) {
+ callback.onPresetChanged(index);
+ }
+ }
+ }
+
+ void notifyBandLevelChangeChanged(final int band, final float dB, final boolean fromSystem) {
+ synchronized (mEqUpdateCallbacks) {
+ for (final EqUpdatedCallback callback : mEqUpdateCallbacks) {
+ callback.onBandLevelChange(band, dB, fromSystem);
+ }
+ }
+ }
+
+ /**
+ * Callback for changes to visibility and state of the EQ
+ */
+ public interface EqControlStateCallback {
+ public void updateEqState(boolean saveVisible, boolean removeVisible,
+ boolean renameVisible, boolean unlockVisible);
+ }
+
+ public void addEqControlStateCallback(EqControlStateCallback callback) {
+ synchronized (mEqControlStateCallbacks) {
+ mEqControlStateCallbacks.add(callback);
+ }
+ }
+
+ public synchronized void removeEqControlStateCallback(EqControlStateCallback callback) {
+ synchronized (mEqControlStateCallbacks) {
+ mEqControlStateCallbacks.remove(callback);
+ }
+ }
+
+ void notifyEqControlStateChanged(boolean saveVisible, boolean removeVisible,
+ boolean renameVisible, boolean unlockVisible) {
+ synchronized (mEqControlStateCallbacks) {
+ for (final EqControlStateCallback callback : mEqControlStateCallbacks) {
+ callback.updateEqState(saveVisible, removeVisible, renameVisible, unlockVisible);
+ }
+ }
+ }
+
+ /**
+ * Register this callback to receive notification when the output device
+ * changes.
+ */
+ public interface DeviceChangedCallback {
+ public void onDeviceChanged(AudioDeviceInfo device, boolean userChange);
+ public void onGlobalDeviceToggle(boolean on);
+
+ }
+
+ public void addDeviceChangedCallback(DeviceChangedCallback callback) {
+ synchronized (mDeviceChangedCallbacks) {
+ mDeviceChangedCallbacks.add(callback);
+ callback.onDeviceChanged(mConfig.getCurrentDevice(), false);
+ }
+ }
+
+ public synchronized void removeDeviceChangedCallback(DeviceChangedCallback callback) {
+ synchronized (mDeviceChangedCallbacks) {
+ mDeviceChangedCallbacks.remove(callback);
+ }
+ }
+
+ void notifyGlobalToggle(boolean on) {
+ synchronized (mDeviceChangedCallbacks) {
+ for (DeviceChangedCallback callback : mDeviceChangedCallbacks) {
+ callback.onGlobalDeviceToggle(on);
+ }
+
+ }
+ }
+
+ void notifyDeviceChanged(final AudioDeviceInfo newDevice, final boolean fromUser) {
+ synchronized (mDeviceChangedCallbacks) {
+ for (final DeviceChangedCallback callback : mDeviceChangedCallbacks) {
+ callback.onDeviceChanged(newDevice, fromUser);
+ }
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/backends/AndroidEffects.java b/src/org/cyanogenmod/audiofx/audiofx/backends/AndroidEffects.java
new file mode 100644
index 0000000..090eb73
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/backends/AndroidEffects.java
@@ -0,0 +1,185 @@
+package com.cyngn.audiofx.backends;
+
+import android.media.AudioDeviceInfo;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.BassBoost;
+import android.media.audiofx.PresetReverb;
+import android.media.audiofx.Virtualizer;
+import android.util.Log;
+
+import com.cyngn.audiofx.Constants;
+
+/**
+ * EffectSet which comprises standard Android effects
+ */
+class AndroidEffects extends EffectSetWithAndroidEq {
+
+ /**
+ * Session-specific bassboost
+ */
+ private BassBoost mBassBoost;
+
+ /**
+ * Session-specific virtualizer
+ */
+ private Virtualizer mVirtualizer;
+
+ /**
+ * Session-specific reverb
+ */
+ private PresetReverb mPresetReverb;
+
+ public AndroidEffects(int sessionId, AudioDeviceInfo deviceInfo) {
+ super(sessionId, deviceInfo);
+ }
+
+ @Override
+ protected void onCreate() {
+ super.onCreate();
+
+ mBassBoost = new BassBoost(100, mSessionId);
+ mVirtualizer = new Virtualizer(100, mSessionId);
+ mPresetReverb = new PresetReverb(100, mSessionId);
+ }
+
+ @Override
+ public void release() {
+ super.release();
+
+ try {
+ if (mBassBoost != null) {
+ mBassBoost.release();
+ }
+ } catch (Exception e) {
+ // ignored;
+ }
+ try {
+ if (mVirtualizer != null) {
+ mVirtualizer.release();
+ }
+ } catch (Exception e) {
+ // ignored
+ }
+ try {
+ if (mPresetReverb != null) {
+ mPresetReverb.release();
+ }
+ } catch (Exception e) {
+ // ignored
+ }
+ mBassBoost = null;
+ mVirtualizer = null;
+ mPresetReverb = null;
+ }
+
+ @Override
+ public synchronized void setDevice(AudioDeviceInfo deviceInfo) {
+ super.setDevice(deviceInfo);
+ }
+
+ @Override
+ public void setGlobalEnabled(boolean globalEnabled) {
+ super.setGlobalEnabled(globalEnabled);
+
+ if (!globalEnabled) {
+ // disable everything. it will get explictly enabled
+ // individually when necessary.
+ try {
+ if (mVirtualizer != null) {
+ mVirtualizer.setEnabled(false);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to disable virtualizer!", e);
+ }
+ try {
+ if (mBassBoost != null) {
+ mBassBoost.setEnabled(false);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to disable bass boost!", e);
+ }
+ try {
+ if (mPresetReverb != null) {
+ mPresetReverb.setEnabled(false);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to disable reverb!", e);
+ }
+ }
+ }
+
+ @Override
+ public boolean hasVirtualizer() {
+ return mVirtualizer != null && mVirtualizer.getStrengthSupported();
+ }
+
+ @Override
+ public boolean hasBassBoost() {
+ return mBassBoost != null && mBassBoost.getStrengthSupported();
+ }
+
+ @Override
+ public void enableBassBoost(boolean enable) {
+ try {
+ if (mBassBoost != null) {
+ mBassBoost.setEnabled(enable);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to " + (enable ? "enable" : "disable") + " bass boost!", e);
+ }
+ }
+
+ @Override
+ public void setBassBoostStrength(short strength) {
+ setParameterSafe(mBassBoost, BassBoost.PARAM_STRENGTH, strength);
+ }
+
+ @Override
+ public void enableVirtualizer(boolean enable) {
+ try {
+ if (mVirtualizer != null) {
+ mVirtualizer.setEnabled(enable);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to " + (enable ? "enable" : "disable") + " virtualizer!", e);
+ }
+ }
+
+ @Override
+ public void setVirtualizerStrength(short strength) {
+ setParameterSafe(mVirtualizer, Virtualizer.PARAM_STRENGTH, strength);
+ }
+
+ @Override
+ public void enableReverb(boolean enable) {
+ try {
+ if (mPresetReverb != null) {
+ mPresetReverb.setEnabled(enable);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to " + (enable ? "enable" : "disable") + " preset reverb!", e);
+ }
+
+ }
+
+ @Override
+ public void setReverbPreset(short preset) {
+ setParameterSafe(mPresetReverb, PresetReverb.PARAM_PRESET, preset);
+ }
+
+ @Override
+ public int getBrand() {
+ return Constants.EFFECT_TYPE_ANDROID;
+ }
+
+ private void setParameterSafe(AudioEffect e, int p, short v) {
+ if (e == null) {
+ return;
+ }
+ try {
+ e.setParameter(p, v);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to set param " + p + " for effect " + e.getDescriptor().name, ex);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/backends/EffectSet.java b/src/org/cyanogenmod/audiofx/audiofx/backends/EffectSet.java
new file mode 100644
index 0000000..47ae894
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/backends/EffectSet.java
@@ -0,0 +1,235 @@
+package com.cyngn.audiofx.backends;
+
+import android.media.AudioDeviceInfo;
+import android.util.Log;
+
+/**
+ * Helper class representing the full complement of effects attached to one
+ * audio session.
+ */
+public abstract class EffectSet {
+
+ protected static final String TAG = "AudioFx-EffectSet";
+
+ protected final int mSessionId;
+
+ protected boolean mGlobalEnabled;
+
+ private AudioDeviceInfo mDeviceInfo;
+
+ private boolean mMarkedForDeath = false;
+
+ public EffectSet(int sessionId, AudioDeviceInfo deviceInfo) {
+ mSessionId = sessionId;
+ mDeviceInfo = deviceInfo;
+ try {
+ onCreate();
+ } catch (Exception e) {
+ Log.e(TAG, "error creating" + this + ", releasing and throwing!");
+ release();
+ throw e;
+ }
+ }
+
+ /**
+ * Called to do subclass-first initialization in case
+ * an implementation has ordering restrictions.
+ *
+ * This call is wrapped in a try/catch - if an exception is thrown here,
+ * a release will immediately be called.
+ */
+ protected void onCreate() { }
+
+ /**
+ * Destroy all effects in this set.
+ *
+ * Attempting to use this object after calling release is
+ * undefined behavior.
+ */
+ public void release() { }
+
+ /**
+ * Returns the enumerated brand of this implementation
+ * @return brandId
+ */
+ public abstract int getBrand();
+
+ /**
+ * Called when the user toggles the engine on or off. If the
+ * implementation has a built-in bypass mode, this is where
+ * to use it.
+ *
+ * @param globalEnabled
+ */
+ public void setGlobalEnabled(boolean globalEnabled) {
+ mGlobalEnabled = globalEnabled;
+ }
+
+ public boolean isGlobalEnabled() {
+ return mGlobalEnabled;
+ }
+
+ /**
+ * Called when the output device has changed. All cached
+ * data should be cleared at this point.
+ *
+ * @param deviceInfo
+ */
+ public void setDevice(AudioDeviceInfo deviceInfo) {
+ mDeviceInfo = deviceInfo;
+ }
+
+ /**
+ * Return the current active output device
+ * @return deviceInfo
+ */
+ public AudioDeviceInfo getDevice() {
+ return mDeviceInfo;
+ }
+
+ /**
+ * Begin bulk-update of parameters. This can be used if the
+ * implementation supports operation in a transactional/atomic
+ * manner. Parameter changes will immediately follow this call
+ * and should be committed to the backend when the subsequent
+ * commitUpdate() is called.
+ *
+ * Optional.
+ *
+ * @return status - false on failure
+ */
+ public boolean beginUpdate() { return true; }
+
+ /**
+ * Commit accumulated updates to the backend. See above.
+ *
+ * begin/commit are used when a large number of parameters need
+ * to be sent to the backend, such as in the case of a device
+ * switch or preset change. This can increase performance and
+ * reduce click/pop issues.
+ *
+ * Optional.
+ *
+ * @return status - false on failure
+ */
+ public boolean commitUpdate() { return true; }
+
+ /* ---- Top level effects begin here ---- */
+
+ // required effects
+ public abstract boolean hasVirtualizer();
+
+ public abstract boolean hasBassBoost();
+
+ // optional effects
+ public boolean hasTrebleBoost() {
+ return false;
+ }
+
+ public boolean hasVolumeBoost() {
+ return false;
+ }
+
+ public boolean hasReverb() {
+ return false;
+ }
+
+ public abstract void enableEqualizer(boolean enable);
+
+ /**
+ * @param levels in decibels
+ */
+ public abstract void setEqualizerLevelsDecibels(float[] levels);
+
+ public abstract short getNumEqualizerBands();
+
+ /**
+ * @param band
+ * @param level in millibels
+ */
+ public abstract void setEqualizerBandLevel(short band, float level);
+
+ /**
+ * @return level in millibels
+ */
+ public abstract int getEqualizerBandLevel(short band);
+
+ public abstract String getEqualizerPresetName(short preset);
+
+ public abstract void useEqualizerPreset(short preset);
+
+ public abstract short getNumEqualizerPresets();
+
+ public abstract short[] getEqualizerBandLevelRange();
+
+ /**
+ * @param band
+ * @return center frequency of the band in millihertz
+ */
+ public abstract int getCenterFrequency(short band);
+
+ public abstract void enableBassBoost(boolean enable);
+
+ /**
+ * @param strength with range [0-1000]
+ */
+ public abstract void setBassBoostStrength(short strength);
+
+ public abstract void enableVirtualizer(boolean enable);
+
+ /**
+ * @param strength with range [0-1000]
+ */
+ public abstract void setVirtualizerStrength(short strength);
+
+ public void enableReverb(boolean enable) {
+ return;
+ }
+
+ public void setReverbPreset(short preset) {
+ return;
+ }
+
+ public void enableTrebleBoost(boolean enable) {
+ return;
+ }
+
+ /**
+ * @param strength with range [0-100]
+ */
+ public void setTrebleBoostStrength(short strength) {
+ return;
+ }
+
+ public void enableVolumeBoost(boolean enable) {
+ return;
+ }
+
+ /**
+ * How long should we delay for when releasing the effects?
+ * This helps certain effect implementations when the
+ * app is reusing a session ID. By default this
+ * behavior is disabled.
+ */
+ public int getReleaseDelay() {
+ return 0;
+ }
+
+ public boolean isMarkedForDeath() {
+ return mMarkedForDeath;
+ }
+
+ public void setMarkedForDeath(boolean die) {
+ mMarkedForDeath = die;
+ }
+
+ @Override
+ public String toString() {
+ return "EffectSet (" + this.getClass().getSimpleName() + ")"
+ + " [ "
+ + " mSessionId: " + mSessionId
+ + " mDeviceInfo: " + mDeviceInfo
+ + " mGlobalEnabled: " + mGlobalEnabled
+ + " ]";
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/backends/EffectSetWithAndroidEq.java b/src/org/cyanogenmod/audiofx/audiofx/backends/EffectSetWithAndroidEq.java
new file mode 100644
index 0000000..81cd53c
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/backends/EffectSetWithAndroidEq.java
@@ -0,0 +1,123 @@
+package com.cyngn.audiofx.backends;
+
+import android.media.AudioDeviceInfo;
+import android.media.audiofx.Equalizer;
+import android.util.Log;
+
+import com.cyngn.audiofx.eq.EqUtils;
+
+/**
+ * Created by roman on 3/1/16.
+ */
+public abstract class EffectSetWithAndroidEq extends EffectSet {
+ /**
+ * Session-specific equalizer
+ */
+ private Equalizer mEqualizer;
+
+ private short mEqNumPresets = -1;
+ private short mEqNumBands = -1;
+
+ public EffectSetWithAndroidEq(int sessionId, AudioDeviceInfo deviceInfo) {
+ super(sessionId, deviceInfo);
+ }
+
+ @Override
+ protected void onCreate() {
+ mEqualizer = new Equalizer(100, mSessionId);
+ super.onCreate();
+
+ }
+
+ @Override
+ public synchronized void release() {
+ if (mEqualizer != null) {
+ mEqualizer.release();
+ mEqualizer = null;
+ }
+ super.release();
+ }
+
+ @Override
+ public void setGlobalEnabled(boolean globalEnabled) {
+ super.setGlobalEnabled(globalEnabled);
+
+ enableEqualizer(globalEnabled);
+ }
+
+ @Override
+ public void enableEqualizer(boolean enable) {
+ try {
+ mEqualizer.setEnabled(enable);
+ } catch (Exception e) {
+ Log.e(TAG, "enableEqualizer failed! enable=" + enable + " sessionId=" + mSessionId, e);
+ }
+ }
+
+ @Override
+ public void setEqualizerLevelsDecibels(float[] levels) {
+ final short[] equalizerLevels = EqUtils.convertDecibelsToMillibelsInShorts(levels);
+ for (short i = 0; i < equalizerLevels.length; i++) {
+ setBandLevelSafe(i, equalizerLevels[i]);
+ }
+ }
+
+ @Override
+ public short getNumEqualizerBands() {
+ if (mEqNumBands < 0) {
+ mEqNumBands = mEqualizer.getNumberOfBands();
+ }
+ return mEqNumBands;
+ }
+
+ @Override
+ public void setEqualizerBandLevel(short band, float level) {
+ setBandLevelSafe(band, (short)level);
+ }
+
+ @Override
+ public int getEqualizerBandLevel(short band) {
+ return mEqualizer.getBandLevel(band);
+ }
+
+ @Override
+ public String getEqualizerPresetName(short preset) {
+ return mEqualizer.getPresetName(preset);
+ }
+
+ @Override
+ public void useEqualizerPreset(short preset) {
+ mEqualizer.usePreset(preset);
+ }
+
+ @Override
+ public short getNumEqualizerPresets() {
+ if (mEqNumPresets < 0) {
+ mEqNumPresets = mEqualizer.getNumberOfPresets();
+ }
+ return mEqNumPresets;
+ }
+
+ @Override
+ public short[] getEqualizerBandLevelRange() {
+ return mEqualizer.getBandLevelRange();
+ }
+
+ @Override
+ public int getCenterFrequency(short band) {
+ return mEqualizer.getCenterFreq(band);
+ }
+
+ @Override
+ public synchronized void setDevice(AudioDeviceInfo deviceInfo) {
+ super.setDevice(deviceInfo);
+ }
+
+ private synchronized void setBandLevelSafe(short band, short level) {
+ try {
+ mEqualizer.setBandLevel(band, level);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to set eq band=" + band + " level=" + level, e);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/backends/IEffectFactory.java b/src/org/cyanogenmod/audiofx/audiofx/backends/IEffectFactory.java
new file mode 100644
index 0000000..8e629b8
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/backends/IEffectFactory.java
@@ -0,0 +1,16 @@
+package com.cyngn.audiofx.backends;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+
+interface IEffectFactory {
+
+ /**
+ * Create a new EffectSet based on current stream parameters.
+ * @param context context to create the effect with
+ * @param sessionId session id to attach the effect to
+ * @param currentDevice current device that the effect should initially setup for
+ * @return an {@link EffectSet}
+ */
+ EffectSet createEffectSet(Context context, int sessionId, AudioDeviceInfo currentDevice);
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/eq/EqBarView.java b/src/org/cyanogenmod/audiofx/audiofx/eq/EqBarView.java
new file mode 100644
index 0000000..df697dd
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/eq/EqBarView.java
@@ -0,0 +1,208 @@
+package com.cyngn.audiofx.eq;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.EqualizerManager;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.activity.StateCallbacks;
+
+public class EqBarView extends FrameLayout implements StateCallbacks.EqUpdatedCallback {
+
+ private static final String TAG = EqBarView.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private EqualizerManager mEqManager;
+
+ private float mNormalWidth;
+ private float mParentHeight = -1;
+ private float mLastTouchX;
+ private float mLastTouchY;
+ private float mPosX;
+ private float mPosY = -1;
+ private boolean mUserInteracting;
+ private int mParentTop;
+ private Integer mIndex;
+ private float mInitialLevel;
+
+ public EqBarView(Context context) {
+ super(context);
+ init();
+ }
+
+ public EqBarView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public EqBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ mEqManager = MasterConfigControl.getInstance(mContext).getEqualizerManager();
+ mNormalWidth = getResources().getDimension(R.dimen.eq_bar_width);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ private EqContainerView.EqBandInfo getInfo() {
+ return (EqContainerView.EqBandInfo) getTag();
+ }
+
+ public void setParentHeight(float h, int top) {
+ mParentHeight = h;
+ mParentTop = top;
+ updateHeight();
+ }
+
+ void updateHeight() {
+ if (DEBUG) Log.d(TAG, "updateHeight()");
+
+ if (getInfo() != null) {
+ float level = mEqManager.getLevel(getIndex());
+ float yProjection = 1 - mEqManager.projectY(level);
+ float height = (yProjection * (mParentHeight));
+ mPosY = height;
+
+ if (DEBUG) {
+ Log.d(TAG, getIndex() + "level: " + level + ", yProjection: "
+ + yProjection + ", mPosY: " + mPosY);
+ }
+ updateHeight((int) mPosY);
+ } else {
+ if (DEBUG) Log.d(TAG, "could not updateHeight()");
+ }
+ }
+
+ public int getIndex() {
+ if (mIndex == null) {
+ mIndex = (getInfo()).mIndex;
+ }
+ return mIndex;
+ }
+
+ public boolean isUserInteracting() {
+ return mUserInteracting;
+ }
+
+ /* package */ void startInteraction(float x, float y) {
+
+ mLastTouchX = x;
+ mLastTouchY = y;
+ mUserInteracting = true;
+
+ if (DEBUG) Log.d(TAG, "initial level: " + mInitialLevel);
+ mInitialLevel = (1 - (mPosY / mParentHeight)) * (mEqManager.getMinDB() - mEqManager.getMaxDB())
+ - mEqManager.getMinDB();
+
+ updateWidth((int) (mNormalWidth * 2));
+ }
+
+ /* package */ void endInteraction() {
+ mUserInteracting = false;
+
+ updateWidth((int) mNormalWidth);
+ }
+
+ private void updateHeight(int h) {
+ if (!isInLayout()) {
+ final ViewGroup.LayoutParams params = getLayoutParams();
+ params.height = h;
+ setLayoutParams(params);
+ }
+ }
+
+ private void updateWidth(int w) {
+ final ViewGroup.LayoutParams params = getLayoutParams();
+ params.width = w;
+ setLayoutParams(params);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mEqManager.isEqualizerLocked()) {
+ return false;
+ }
+
+ final float x = event.getRawX();
+ final float y = event.getRawY() - mParentTop;
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ startInteraction(x, y);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ // Calculate the distance moved
+ final float dx = x - mLastTouchX;
+ final float dy = y - mLastTouchY;
+
+ mPosX += dx;
+ mPosY -= dy;
+
+ // Remember this touch position for the next move event
+ mLastTouchX = x;
+ mLastTouchY = y;
+
+ int wy = (int) mParentHeight;
+ float level = (1 - (mPosY / wy)) * (mEqManager.getMinDB() - mEqManager.getMaxDB())
+ - mEqManager.getMinDB();
+
+ if (DEBUG) Log.d(TAG, "new level: " + level);
+ if (level < mEqManager.getMinDB()) {
+ level = mEqManager.getMinDB();
+ } else if (level > mEqManager.getMaxDB()) {
+ level = mEqManager.getMaxDB();
+ }
+
+ if (mInitialLevel != level) {
+ mEqManager.setLevel(getInfo().mIndex, level, false);
+ } else {
+ updateHeight();
+ }
+
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ endInteraction();
+ break;
+
+ }
+
+ return true;
+ }
+
+ public float getPosY() {
+ return mPosY;
+ }
+
+ @Override
+ public void onBandLevelChange(int band, float dB, boolean fromSystem) {
+ if (getInfo().mIndex != band) {
+ return;
+ }
+
+ updateHeight();
+ }
+
+ @Override
+ public void onPresetChanged(int newPresetIndex) {
+
+ }
+
+ @Override
+ public void onPresetsChanged() {
+
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/eq/EqContainerView.java b/src/org/cyanogenmod/audiofx/audiofx/eq/EqContainerView.java
new file mode 100644
index 0000000..51b58a6
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/eq/EqContainerView.java
@@ -0,0 +1,518 @@
+package com.cyngn.audiofx.eq;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+import android.widget.CheckBox;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.EqualizerManager;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.activity.StateCallbacks;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EqContainerView extends FrameLayout
+ implements StateCallbacks.EqUpdatedCallback, StateCallbacks.EqControlStateCallback {
+
+ private static final String TAG = EqContainerView.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private int mWidth;
+ private int mHeight;
+ private MasterConfigControl mConfig;
+ private EqualizerManager mEqManager;
+ private List<EqBandInfo> mBandInfo;
+ private List<EqBarView> mBarViews;
+ private List<Integer> mSelectedBands;
+
+ private CheckBox mLockBox;
+ private ImageView mRenameControl;
+ private ImageView mRemoveControl;
+ private ImageView mSaveControl;
+ private ViewGroup mControls;
+ private boolean mControlsVisible;
+
+ private boolean mSaveVisible;
+ private boolean mRemoveVisible;
+ private boolean mRenameVisible;
+ private boolean mUnlockVisible;
+
+ private int mSelectedBandColor;
+ private boolean mFirstLayout = true;
+
+ private Paint mTextPaint;
+ private Paint mFreqPaint;
+ private Paint mSelectedFreqPaint;
+ private Paint mCenterLinePaint;
+ private Path mDashPath;
+
+ private Handler mHandler;
+
+ private Runnable mVibrateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ Vibrator v = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ v.vibrate(30);
+ }
+ };
+
+ private int mPaddingTop;
+ private int mPaddingBottom;
+ private int mBarWidth;
+ private int mBarSeparation;
+ private int mBarBottomGrabSpacePadding;
+
+ public void stopListening() {
+ for (EqBarView barView : mBarViews) {
+ barView.setTag(null);
+ mConfig.getCallbacks().removeEqUpdatedCallback(barView);
+ }
+ mConfig.getCallbacks().removeEqUpdatedCallback(this);
+ }
+
+ public void startListening() {
+ for (int i = 0; i < mBandInfo.size(); i++) {
+
+ final EqBarView eqBarView = mBarViews.get(i);
+ eqBarView.setTag(mBandInfo.get(i));
+ mConfig.getCallbacks().addEqUpdatedCallback(eqBarView);
+ }
+ mConfig.getCallbacks().addEqUpdatedCallback(this);
+ }
+
+ public static class EqBandInfo {
+ public int mIndex;
+
+ public String mFreq;
+ public String mDb;
+ public EqBarView mBar;
+ }
+
+ public EqContainerView(Context context) {
+ super(context);
+ init();
+ }
+
+ public EqContainerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public EqContainerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+
+ mHandler = new Handler();
+
+ final Resources r = getResources();
+
+ mBarWidth = r.getDimensionPixelSize(R.dimen.eq_bar_width);
+ mBarSeparation = r.getDimensionPixelSize(R.dimen.separator_width);
+
+ mBarBottomGrabSpacePadding = r.getDimensionPixelSize(R.dimen.eq_bar_bottom_grab_space);
+ int freqTextSize = r.getDimensionPixelSize(R.dimen.eq_label_text_size);
+ int selectedBoxTextSize = r.getDimensionPixelSize(R.dimen.eq_selected_box_height);
+
+ int extraTopSpace = r.getDimensionPixelSize(R.dimen.eq_bar_top_padding);
+
+ mPaddingTop = selectedBoxTextSize + extraTopSpace;
+ mPaddingBottom = selectedBoxTextSize + mBarBottomGrabSpacePadding;
+
+ mConfig = MasterConfigControl.getInstance(mContext);
+ mEqManager = mConfig.getEqualizerManager();
+
+ mBarViews = new ArrayList<>();
+ mBandInfo = new ArrayList<>();
+ mSelectedBands = new ArrayList<>();
+
+ setWillNotDraw(false);
+
+ mSelectedBandColor = r.getColor(R.color.band_bar_color_selected);
+
+ mTextPaint = new Paint();
+ mTextPaint.setAntiAlias(true);
+ mTextPaint.setColor(Color.WHITE);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setElegantTextHeight(true);
+ mTextPaint.setTextSize(selectedBoxTextSize);
+
+ mFreqPaint = new Paint();
+ mFreqPaint.setAntiAlias(true);
+ mFreqPaint.setColor(Color.WHITE);
+ mFreqPaint.setTextAlign(Paint.Align.CENTER);
+ mFreqPaint.setTextSize(freqTextSize);
+
+ mSelectedFreqPaint = new Paint(mFreqPaint);
+ mSelectedFreqPaint.setAntiAlias(true);
+ mSelectedFreqPaint.setTextSize(selectedBoxTextSize);
+
+ mCenterLinePaint = new Paint();
+ mCenterLinePaint.setColor(Color.WHITE);
+ mCenterLinePaint.setAntiAlias(true);
+ mCenterLinePaint.setPathEffect(new DashPathEffect(new float[]{6, 6}, 0));
+ mCenterLinePaint.setStyle(Paint.Style.STROKE);
+ mCenterLinePaint.setAntiAlias(true);
+
+ getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ generateAndAddBars();
+ }
+ });
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return true;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mControls = (ViewGroup) findViewById(R.id.eq_controls);
+
+ mLockBox = (CheckBox) findViewById(R.id.lock);
+ mLockBox.setOnCheckedChangeListener(mEqManager.getLockChangeListener());
+
+ mRenameControl = (ImageView) findViewById(R.id.rename);
+ mRemoveControl = (ImageView) findViewById(R.id.remove);
+ mSaveControl = (ImageView) findViewById(R.id.save);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ if (DEBUG) Log.d(TAG, "onAttachedToWindow()");
+ super.onAttachedToWindow();
+
+ mConfig.getCallbacks().addEqControlStateCallback(this);
+ onPresetChanged(mEqManager.getCurrentPresetIndex()); // update initial state
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if (DEBUG) Log.d(TAG, "onDetachedFromWindow()");
+ mConfig.getCallbacks().removeEqControlStateCallback(this);
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ private void generateAndAddBars() {
+ if (mFirstLayout) {
+ mFirstLayout = false;
+ mBarViews.clear();
+
+ for (int i = 0; i < mEqManager.getNumBands(); i++) {
+ final EqBandInfo band = new EqBandInfo();
+ band.mIndex = i;
+ mBandInfo.add(band);
+
+ final EqBarView bar = new EqBarView(mContext);
+ band.mBar = bar;
+ bar.setTag(band);
+ bar.setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mEqManager.isEqualizerLocked()) {
+ return false;
+ }
+ switch (event.getActionMasked()) {
+
+ case MotionEvent.ACTION_DOWN:
+ startBarInteraction(bar);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ stopBarInteraction(bar);
+ break;
+ }
+
+ return false;
+ }
+ });
+
+ // set correct initial alpha
+ if (i % 2 == 0) {
+ bar.setAlpha(0.6f);
+ } else {
+ bar.setAlpha(0.8f);
+ }
+ bar.setBackgroundColor(Color.WHITE);
+ bar.setElevation(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2,
+ getResources().getDisplayMetrics()));
+
+ addView(bar, getFrameParams(i));
+ bar.setParentHeight(mHeight, getTop());
+
+ final float freq = mEqManager.getCenterFreq(i);
+ String frequencyText = String.format(freq < 1000 ? "%.0f" : "%.0fk",
+ freq < 1000 ? freq : freq / 1000);
+ band.mFreq = frequencyText;
+ mBarViews.add(bar);
+ }
+ updateSelectedBands();
+ } else {
+ for (EqBarView barView : mBarViews) {
+ barView.setParentHeight(mHeight, getTop());
+ }
+ }
+ }
+
+ public EqBarView startTouchingBarUnder(MotionEvent event) {
+ EqBarView foundBar = findBar(event.getX(), event.getY(), mBarViews);
+ if (foundBar != null) {
+ foundBar.updateHeight();
+
+ foundBar.startInteraction(event.getRawX(), event.getRawY());
+ startBarInteraction(foundBar);
+ }
+ return foundBar;
+ }
+
+ public void startBarInteraction(EqBarView bar) {
+ setControlsVisible(false, false);
+ EqBandInfo band = (EqBandInfo) bar.getTag();
+ mSelectedBands.add(band.mIndex);
+ updateSelectedBands();
+ AsyncTask.execute(mVibrateRunnable);
+ }
+
+ public void stopBarInteraction(EqBarView bar) {
+ EqBandInfo band = (EqBandInfo) bar.getTag();
+ mSelectedBands.remove((Integer) band.mIndex);
+ updateSelectedBands();
+ setControlsVisible(mControlsVisible, true);
+ }
+
+ private EqBarView findBar(float x, float y, List<EqBarView> targets) {
+ final int count = targets.size();
+ for (int i = 0; i < count; i++) {
+ final EqBarView target = targets.get(i);
+ if (target.getRight() > x && target.getTop() < y
+ && target.getBottom() > y && target.getLeft() < x) {
+ return target;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mWidth = w;
+ mHeight = h - mPaddingTop - mPaddingBottom;
+ generateAndAddBars();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ //---------------------------------------------------
+
+ if (mFirstLayout)
+ return;
+
+ int dashY = bottom - mPaddingBottom - (mHeight / 2);
+
+ final int widthOfBars = (mEqManager.getNumBands() * mBarWidth)
+ + ((mEqManager.getNumBands() - 1) * mBarSeparation);
+ final int freeSpace = mWidth - widthOfBars;
+
+ int mCurLeft = (freeSpace / 2);
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+
+ if (child instanceof EqBarView) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+
+ int l = mCurLeft;
+ int r = l + mBarWidth;
+
+ mCurLeft += mBarWidth + mBarSeparation;
+
+ if (((EqBarView) child).isUserInteracting()) {
+ l -= childWidth / 4;
+ r += childWidth / 4;
+ }
+
+ final int layoutTop = top + mHeight - childHeight + mPaddingTop;
+ final int layoutBottom = layoutTop + childHeight
+ + mPaddingBottom - (mPaddingBottom - mBarBottomGrabSpacePadding);
+ child.layout(l, layoutTop, r, layoutBottom);
+ }
+ }
+
+ if (changed || mDashPath == null) {
+ mDashPath = new Path();
+ mDashPath.reset();
+ mDashPath.moveTo(freeSpace / 2, dashY);
+ mDashPath.lineTo(widthOfBars + (freeSpace / 2), dashY);
+ }
+
+ mControls.layout(
+ right - mControls.getMeasuredWidth() - mControls.getPaddingLeft(),
+ top + mControls.getPaddingTop(),
+ right - mControls.getPaddingRight(),
+ top + mControls.getMeasuredHeight() + mControls.getPaddingTop()
+ + mControls.getPaddingBottom()
+ );
+ }
+
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ canvas.drawPath(mDashPath, mCenterLinePaint);
+
+ for (int i = 0; i < mBandInfo.size(); i++) {
+ EqBandInfo info = mBandInfo.get(i);
+
+ final float x = info.mBar.getX() + (info.mBar.getWidth() / 2);
+ final boolean userInteracting = info.mBar.isUserInteracting();
+ if (userInteracting) {
+ canvas.drawText(
+ info.mDb,
+ x,
+ info.mBar.getY() - (mTextPaint.getTextSize() / 2),
+ mTextPaint);
+ }
+
+ Paint drawPaint = userInteracting ? mSelectedFreqPaint : mFreqPaint;
+
+ canvas.drawText(info.mFreq, x,
+ info.mBar.getBottom() + drawPaint.getTextSize(),
+ drawPaint);
+ }
+ }
+
+ private void updateSelectedBands() {
+ for (int i = 0; i < mEqManager.getNumBands(); i++) {
+ EqBandInfo tag = mBandInfo.get(i);
+ final EqBarView bar = (EqBarView) findViewWithTag(tag);
+ if (bar != null) {
+ final ViewPropertyAnimator barAnimation = bar.animate().withLayer();
+ if (mSelectedBands.isEmpty()) {
+ if (i % 2 == 0) {
+ barAnimation.alpha(0.6f);
+ } else {
+ barAnimation.alpha(0.8f);
+ }
+ } else if (mSelectedBands.contains(i)) {
+ barAnimation.alpha(1f);
+ bar.setBackgroundColor(mSelectedBandColor);
+ } else {
+ barAnimation.alpha(0.40f);
+ }
+ }
+ }
+ }
+
+ private FrameLayout.LayoutParams getFrameParams(int index) {
+ int width = getResources().getDimensionPixelSize(R.dimen.eq_bar_width);
+ int height = Math.round((1 - mEqManager.projectY(mEqManager.getLevel(index))) * mHeight);
+ FrameLayout.LayoutParams ll = new FrameLayout.LayoutParams(width, height);
+ ll.gravity = Gravity.TOP;
+ return ll;
+ }
+
+ @Override
+ public void onBandLevelChange(int band, float dB, boolean fromSystem) {
+ if (mFirstLayout) return;
+ mBandInfo.get(band).mDb = dB != 0 ? String.format("%+1.1f", dB) : "0.0";
+ invalidate();
+ }
+
+ @Override
+ public void onPresetChanged(int newPresetIndex) {
+ updateEqState();
+ if (mEqManager.isUserPreset()) {
+ mLockBox.setChecked(mEqManager.isEqualizerLocked());
+ }
+ }
+
+ @Override
+ public void updateEqState(boolean saveVisible, boolean removeVisible,
+ boolean renameVisible, boolean unlockVisible) {
+ mControlsVisible = mEqManager.isUserPreset() || mEqManager.isCustomPreset();
+ mSaveVisible = saveVisible;
+ mRemoveVisible = removeVisible;
+ mRenameVisible = renameVisible;
+ mUnlockVisible = unlockVisible;
+ updateEqState();
+ }
+
+ public void updateEqState() {
+ setControlsVisible(mControlsVisible && mSelectedBands.isEmpty(), false);
+
+ animateControl(mLockBox, mUnlockVisible);
+ animateControl(mRemoveControl, mRemoveVisible);
+ animateControl(mRenameControl, mRenameVisible);
+ animateControl(mSaveControl, mSaveVisible);
+ }
+
+ private void animateControl(final View v, boolean visible) {
+ if (visible) {
+ v.setVisibility(View.VISIBLE);
+ v.animate()
+ .alpha(1f)
+ .setDuration(350)
+ .withEndAction(null);
+ } else {
+ v.animate()
+ .alpha(0f)
+ .setDuration(350)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ v.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onPresetsChanged() {
+ }
+
+ public void setControlsVisible(boolean visible, boolean keepChange) {
+ if (keepChange) {
+ mControlsVisible = visible;
+ }
+
+ if (mControls != null) {
+ animateControl(mControls, visible);
+ }
+ }
+
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/eq/EqSwipeController.java b/src/org/cyanogenmod/audiofx/audiofx/eq/EqSwipeController.java
new file mode 100644
index 0000000..e96d944
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/eq/EqSwipeController.java
@@ -0,0 +1,133 @@
+package com.cyngn.audiofx.eq;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.EqualizerManager;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.preset.InfiniteViewPager;
+
+public class EqSwipeController extends LinearLayout {
+
+ /*
+ * x velocity max for deciding whether to try to grab a bar
+ */
+ private static final int X_VELOCITY_THRESH = 20;
+
+ private static final int MINIMUM_TIME_HOLD_TIME = 100;
+
+ EqContainerView mEq;
+ InfiniteViewPager mPager;
+ private VelocityTracker mVelocityTracker = null;
+ long mDownTime;
+ EqBarView mBar;
+ boolean mBarActive;
+ private ViewGroup mControls;
+
+ private final EqualizerManager mEqManager;
+ private float mDownPositionX;
+ private float mDownPositionY;
+
+ public EqSwipeController(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mEqManager = MasterConfigControl.getInstance(context).getEqualizerManager();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mEq = (EqContainerView) findViewById(R.id.eq_container);
+ mPager = (InfiniteViewPager) findViewById(R.id.pager);
+ mControls = (ViewGroup) findViewById(R.id.eq_controls);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+
+ // don't intercept touches over the EQ controls
+ if (mControls.getRight() > x && mControls.getTop() < y
+ && mControls.getBottom() > y && mControls.getLeft() < x) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int index = event.getActionIndex();
+ int action = event.getActionMasked();
+ int pointerId = event.getPointerId(index);
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mDownPositionX = event.getRawX();
+ mDownPositionY = event.getRawY();
+ mDownTime = System.currentTimeMillis();
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ mVelocityTracker.addMovement(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mVelocityTracker != null) {
+ mVelocityTracker.addMovement(event);
+ mVelocityTracker.computeCurrentVelocity(1000);
+ float xVelocity = mVelocityTracker.getXVelocity(pointerId);
+ float yVelocity = mVelocityTracker.getYVelocity(pointerId);
+
+ final float deltaX = mDownPositionX - event.getRawX();
+ final float deltaY = mDownPositionY - event.getRawY();
+ final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
+
+ final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
+ final int touchSlop = viewConfiguration.getScaledTouchSlop();
+
+ if (!mBarActive && !mEqManager.isChangingPresets()
+ && !mEqManager.isEqualizerLocked()
+ && Math.abs(xVelocity) < X_VELOCITY_THRESH
+ && System.currentTimeMillis() - mDownTime > MINIMUM_TIME_HOLD_TIME) {
+ if (distanceSquared < touchSlop * touchSlop) {
+ mBarActive = true;
+ mBar = mEq.startTouchingBarUnder(event);
+ }
+ }
+ }
+
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+
+ if (mBarActive) {
+ // reset state?
+ if (mBar != null) {
+ mEq.stopBarInteraction(mBar);
+ mBar.endInteraction();
+ }
+ }
+ mBar = null;
+ mBarActive = false;
+ break;
+ }
+ if (mBarActive && mBar != null) {
+ return mBar.onTouchEvent(event);
+ } else {
+ return mPager.onTouchEvent(event);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/eq/EqUtils.java b/src/org/cyanogenmod/audiofx/audiofx/eq/EqUtils.java
new file mode 100644
index 0000000..30d546c
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/eq/EqUtils.java
@@ -0,0 +1,149 @@
+package com.cyngn.audiofx.eq;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.cyngn.audiofx.Preset;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class EqUtils {
+
+ private static final String TAG = EqUtils.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String DEFAULT_DELIMITER = ";";
+
+ public static String getZeroedBandsString(int length) {
+ return getZeroedBandsString(length, DEFAULT_DELIMITER);
+ }
+
+ public static float[] stringBandsToFloats(String input) {
+ return stringBandsToFloats(input, DEFAULT_DELIMITER);
+ }
+
+ public static String floatLevelsToString(float[] levels) {
+ return floatLevelsToString(levels, DEFAULT_DELIMITER);
+ }
+
+ public static short[] stringBandsToShorts(String input) {
+ return stringBandsToShorts(input, DEFAULT_DELIMITER);
+ }
+
+ public static String shortLevelsToString(short[] levels) {
+ return shortLevelsToString(levels, DEFAULT_DELIMITER);
+ }
+
+ public static String getZeroedBandsString(int length, final String delimiter) {
+ StringBuilder buff = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ buff.append("0").append(delimiter);
+ }
+ buff.deleteCharAt(buff.length() - 1);
+ return buff.toString();
+ }
+
+ public static String floatLevelsToString(float[] levels, final String delimiter) {
+ // save
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < levels.length; i++) {
+ builder.append(levels[i]);
+ builder.append(delimiter);
+ }
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.toString();
+ }
+
+
+ public static String shortLevelsToString(short[] levels, final String delimiter) {
+ // save
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < levels.length; i++) {
+ builder.append(levels[i]);
+ builder.append(delimiter);
+ }
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.toString();
+ }
+
+ public static String intLevelsToString(int[] levels, final String delimiter) {
+ // save
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < levels.length; i++) {
+ builder.append(levels[i]);
+ builder.append(delimiter);
+ }
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.toString();
+ }
+
+ public static <T> String levelsToString(T[] levels, final String delimiter) {
+ // save
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < levels.length; i++) {
+ builder.append(levels[i]);
+ builder.append(delimiter);
+ }
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.toString();
+ }
+
+ public static short[] stringBandsToShorts(String input, final String delimiter) {
+ String[] levels = input.split(delimiter);
+
+ short[] equalizerLevels = new short[levels.length];
+ for (int i = 0; i < levels.length; i++) {
+ equalizerLevels[i] = (short) (Float.parseFloat(levels[i]));
+ }
+ return equalizerLevels;
+ }
+
+
+ public static float[] stringBandsToFloats(String input, final String delimiter) {
+ String[] levels = input.split(delimiter);
+
+ float[] equalizerLevels = new float[levels.length];
+ for (int i = 0; i < levels.length; i++) {
+ equalizerLevels[i] = (Float.parseFloat(levels[i]));
+ }
+ return equalizerLevels;
+ }
+
+ public static float[] convertDecibelsToMillibels(float[] decibels) {
+ if (DEBUG) Log.i(TAG, "++ convertDecibelsToMillibels(" + Arrays.toString(decibels) + ")");
+
+ float[] newvals = new float[decibels.length];
+ for (int i = 0; i < decibels.length; i++) {
+ newvals[i] = decibels[i] * 100;
+ }
+
+
+ if (DEBUG) Log.i(TAG, "-- convertDecibelsToMillibels(" + Arrays.toString(newvals) + ")");
+ return newvals;
+ }
+
+ public static short[] convertDecibelsToMillibelsInShorts(float[] decibels) {
+ if (DEBUG) Log.i(TAG, "++ convertDecibelsToMillibels(" + Arrays.toString(decibels) + ")");
+
+ short[] newvals = new short[decibels.length];
+ for (int i = 0; i < decibels.length; i++) {
+ newvals[i] = (short) (decibels[i] * 100);
+ }
+
+
+ if (DEBUG) Log.i(TAG, "-- convertDecibelsToMillibels(" + Arrays.toString(newvals) + ")");
+ return newvals;
+ }
+
+ public static float[] convertMillibelsToDecibels(float[] millibels) {
+ if (DEBUG) Log.i(TAG, "++ convertMillibelsToDecibels(" + Arrays.toString(millibels) + ")");
+ float[] newvals = new float[millibels.length];
+ for (int i = 0; i < millibels.length; i++) {
+ newvals[i] = millibels[i] / 100;
+ }
+ if (DEBUG) Log.i(TAG, "-- convertMillibelsToDecibels(" + Arrays.toString(newvals) + ")");
+ return newvals;
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/fragment/AudioFxBaseFragment.java b/src/org/cyanogenmod/audiofx/audiofx/fragment/AudioFxBaseFragment.java
new file mode 100644
index 0000000..d487ff0
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/fragment/AudioFxBaseFragment.java
@@ -0,0 +1,66 @@
+package com.cyngn.audiofx.fragment;
+
+import android.animation.Animator;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.widget.CompoundButton;
+import com.cyngn.audiofx.activity.ActivityMusic;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+
+public class AudioFxBaseFragment extends Fragment {
+
+ MasterConfigControl mConfig;
+
+ AudioFxFragment mFrag;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mFrag = (AudioFxFragment) getParentFragment();
+
+ mConfig = MasterConfigControl.getInstance(getActivity());
+ }
+
+ public int getDisabledColor() {
+ return mFrag.getDisabledColor();
+ }
+
+ public int getCurrentBackgroundColor() {
+ return mFrag.mCurrentBackgroundColor;
+ }
+
+ public void animateBackgroundColorTo(Integer colorTo, Animator.AnimatorListener listener,
+ AudioFxFragment.ColorUpdateListener updateListener) {
+ if (mFrag != null) {
+ mFrag.animateBackgroundColorTo(colorTo, listener, updateListener);
+ }
+ }
+
+ /**
+ * Call to change the color and propogate it up to the activity, which will call
+ * {@link #updateFragmentBackgroundColors(int)}
+ *
+ * @param color
+ */
+ public void setBackgroundColor(int color, boolean cancelAnimated) {
+ if (mFrag != null) {
+ mFrag.updateBackgroundColors(color, cancelAnimated);
+ }
+ }
+
+ /**
+ * For sub class fragments to override and apply the color
+ *
+ * @param color the new color to apply to any colored elements
+ */
+ public void updateFragmentBackgroundColors(int color) {
+ }
+
+ /**
+ * For sub class fragments to override when they might need to update their enabled states
+ */
+ public void updateEnabledState() {
+
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/fragment/AudioFxFragment.java b/src/org/cyanogenmod/audiofx/audiofx/fragment/AudioFxFragment.java
new file mode 100644
index 0000000..2c9c5f5
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/fragment/AudioFxFragment.java
@@ -0,0 +1,489 @@
+package com.cyngn.audiofx.fragment;
+
+import android.animation.Animator;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.media.AudioDeviceInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import com.cyngn.audiofx.Compatibility;
+import com.cyngn.audiofx.Constants;
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.ActivityMusic;
+import com.cyngn.audiofx.activity.EqualizerManager;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.activity.StateCallbacks;
+import com.cyngn.audiofx.stats.UserSession;
+import com.cyngn.audiofx.widget.InterceptableLinearLayout;
+
+import java.util.List;
+import java.util.Map;
+
+public class AudioFxFragment extends Fragment implements StateCallbacks.DeviceChangedCallback {
+
+ private static final String TAG = AudioFxFragment.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final String TAG_EQUALIZER = "equalizer";
+ public static final String TAG_CONTROLS = "controls";
+
+ Handler mHandler;
+ int mCurrentBackgroundColor;
+
+ // whether we are in the middle of animating while switching devices
+ boolean mDeviceChanging;
+
+ private MenuItem mMenuDevices;
+
+ // current selected index
+ public int mSelectedPosition = 0;
+
+ EqualizerFragment mEqFragment;
+ ControlsFragment mControlFragment;
+
+ InterceptableLinearLayout mInterceptLayout;
+ private ValueAnimator mColorChangeAnimator;
+
+ private int mDisabledColor;
+
+ private MasterConfigControl mConfig;
+ private EqualizerManager mEqManager;
+
+ private AudioDeviceInfo mSystemDevice;
+ private AudioDeviceInfo mUserSelection;
+
+ private final Map<MenuItem, AudioDeviceInfo> mMenuItems = new ArrayMap<MenuItem, AudioDeviceInfo>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mConfig = MasterConfigControl.getInstance(getActivity());
+ mEqManager = mConfig.getEqualizerManager();
+
+ if (savedInstanceState != null) {
+ int user = savedInstanceState.getInt("user_device");
+ mUserSelection = mConfig.getDeviceById(user);
+ int system = savedInstanceState.getInt("system_device");
+ mSystemDevice = mConfig.getDeviceById(system);
+ }
+
+ mHandler = new Handler();
+ mDisabledColor = getResources().getColor(R.color.disabled_eq);
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt("user_device", mUserSelection == null ? -1 : mUserSelection.getId());
+ outState.putInt("system_device", mSystemDevice == null ? -1 : mSystemDevice.getId());
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ }
+
+ private boolean showFragments() {
+ boolean createNewFrags = true;
+ final FragmentTransaction fragmentTransaction = getChildFragmentManager()
+ .beginTransaction();
+ if (mEqFragment == null) {
+ mEqFragment = (EqualizerFragment) getChildFragmentManager()
+ .findFragmentByTag(TAG_EQUALIZER);
+
+ if (mEqFragment != null) {
+ fragmentTransaction.show(mEqFragment);
+ }
+ }
+ if (mControlFragment == null) {
+ mControlFragment = (ControlsFragment) getChildFragmentManager()
+ .findFragmentByTag(TAG_CONTROLS);
+ if (mControlFragment != null) {
+ fragmentTransaction.show(mControlFragment);
+ }
+ }
+
+ if (mEqFragment != null && mControlFragment != null) {
+ createNewFrags = false;
+ }
+
+ fragmentTransaction.commit();
+
+ return createNewFrags;
+ }
+
+ @Override
+ public void onResume() {
+
+ mConfig.getCallbacks().addDeviceChangedCallback(this);
+ mConfig.bindService();
+ mConfig.setAutoBindToService(true);
+
+ updateEnabledState();
+
+ super.onResume();
+
+ mCurrentBackgroundColor = !mConfig.isCurrentDeviceEnabled()
+ ? mDisabledColor
+ : mEqManager.getAssociatedPresetColorHex(
+ mEqManager.getCurrentPresetIndex());
+ updateBackgroundColors(mCurrentBackgroundColor, false);
+
+ promptIfNotDefault();
+ }
+
+ private void promptIfNotDefault() {
+ final String audioFxPackageName = getActivity().getPackageName();
+
+ final SharedPreferences musicFxPrefs = Constants.getMusicFxPrefs(getActivity());
+ final String defaultPackage = musicFxPrefs.getString(Constants.MUSICFX_DEFAULT_PACKAGE_KEY,
+ audioFxPackageName);
+ final boolean notDefault = !defaultPackage.equals(audioFxPackageName);
+
+ if (notDefault) {
+ new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.snack_bar_not_default)
+ .setNegativeButton(R.string.snack_bar_not_default_not_now,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getActivity().finish();
+ }
+ })
+ .setPositiveButton(R.string.snack_bar_not_default_set,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent updateIntent = new Intent(getActivity(),
+ Compatibility.Service.class);
+ updateIntent.putExtra("defPackage", audioFxPackageName);
+ updateIntent.putExtra("defName", ActivityMusic.class.getName());
+ getActivity().startService(updateIntent);
+ dialog.dismiss();
+ }
+ })
+ .setCancelable(false)
+ .create()
+ .show();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ mConfig.setAutoBindToService(false);
+ mConfig.getCallbacks().removeDeviceChangedCallback(this);
+ super.onPause();
+ mConfig.unbindService();
+ }
+
+ public void updateBackgroundColors(Integer color, boolean cancelAnimated) {
+ if (cancelAnimated && mColorChangeAnimator != null) {
+ mColorChangeAnimator.cancel();
+ }
+ mCurrentBackgroundColor = color;
+ if (mEqFragment != null) {
+ mEqFragment.updateFragmentBackgroundColors(color);
+ }
+ if (mControlFragment != null) {
+ mControlFragment.updateFragmentBackgroundColors(color);
+ }
+ }
+
+ public void updateEnabledState() {
+ boolean currentDeviceEnabled = mConfig.isCurrentDeviceEnabled();
+ if (mEqFragment != null) {
+ mEqFragment.updateEnabledState();
+ }
+ if (mControlFragment != null) {
+ mControlFragment.updateEnabledState();
+ }
+
+ ((ActivityMusic) getActivity()).setGlobalToggleChecked(currentDeviceEnabled);
+
+ if (mInterceptLayout != null) {
+ mInterceptLayout.setInterception(!currentDeviceEnabled);
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.devices, menu);
+ mMenuDevices = menu.findItem(R.id.devices);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ mMenuDevices.getSubMenu().clear();
+ mMenuItems.clear();
+
+ final AudioDeviceInfo currentDevice = mConfig.getCurrentDevice();
+
+ MenuItem selectedItem = null;
+
+ List<AudioDeviceInfo> speakerDevices = mConfig.getConnectedDevices(
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ if (speakerDevices.size() > 0) {
+ AudioDeviceInfo ai = speakerDevices.get(0);
+ int viewId = View.generateViewId();
+ MenuItem item = mMenuDevices.getSubMenu().add(R.id.devices, viewId,
+ Menu.NONE, MasterConfigControl.getDeviceDisplayString(getActivity(), ai));
+ item.setIcon(R.drawable.ic_action_dsp_icons_speaker);
+ mMenuItems.put(item, ai);
+ selectedItem = item;
+ }
+
+ List<AudioDeviceInfo> headsetDevices = mConfig.getConnectedDevices(
+ AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ if (headsetDevices.size() > 0) {
+ AudioDeviceInfo ai = headsetDevices.get(0);
+ int viewId = View.generateViewId();
+ MenuItem item = mMenuDevices.getSubMenu().add(R.id.devices, viewId,
+ Menu.NONE, MasterConfigControl.getDeviceDisplayString(getActivity(), ai));
+ item.setIcon(R.drawable.ic_action_dsp_icons_headphones);
+ mMenuItems.put(item, ai);
+ if (currentDevice.getId() == ai.getId()) {
+ selectedItem = item;
+ }
+ }
+
+ List<AudioDeviceInfo> lineOutDevices = mConfig.getConnectedDevices(
+ AudioDeviceInfo.TYPE_LINE_ANALOG, AudioDeviceInfo.TYPE_LINE_DIGITAL);
+ if (lineOutDevices.size() > 0) {
+ AudioDeviceInfo ai = lineOutDevices.get(0);
+ int viewId = View.generateViewId();
+ MenuItem item = mMenuDevices.getSubMenu().add(R.id.devices, viewId,
+ Menu.NONE, MasterConfigControl.getDeviceDisplayString(getActivity(), ai));
+ item.setIcon(R.drawable.ic_action_dsp_icons_lineout);
+ mMenuItems.put(item, ai);
+ if (currentDevice.getId() == ai.getId()) {
+ selectedItem = item;
+ }
+ }
+
+ List<AudioDeviceInfo> bluetoothDevices = mConfig.getConnectedDevices(
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
+ for (AudioDeviceInfo ai : bluetoothDevices) {
+ int viewId = View.generateViewId();
+ MenuItem item = mMenuDevices.getSubMenu().add(R.id.devices, viewId,
+ Menu.NONE, MasterConfigControl.getDeviceDisplayString(getActivity(), ai));
+ item.setIcon(R.drawable.ic_action_dsp_icons_bluetoof);
+ mMenuItems.put(item, ai);
+ if (currentDevice.getId() == ai.getId()) {
+ selectedItem = item;
+ }
+ }
+
+ List<AudioDeviceInfo> usbDevices = mConfig.getConnectedDevices(
+ AudioDeviceInfo.TYPE_USB_ACCESSORY, AudioDeviceInfo.TYPE_USB_DEVICE);
+ for (AudioDeviceInfo ai : usbDevices) {
+ int viewId = View.generateViewId();
+ MenuItem item = mMenuDevices.getSubMenu().add(R.id.devices, viewId,
+ Menu.NONE, MasterConfigControl.getDeviceDisplayString(getActivity(), ai));
+ item.setIcon(R.drawable.ic_action_device_usb);
+ mMenuItems.put(item, ai);
+ if (currentDevice.getId() == ai.getId()) {
+ selectedItem = item;
+ }
+ }
+ mMenuDevices.getSubMenu().setGroupCheckable(R.id.devices, true, true);
+ selectedItem.setChecked(true);
+ mMenuDevices.setIcon(selectedItem.getIcon());
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ AudioDeviceInfo device = mMenuItems.get(item);
+
+ if (device != null) {
+ UserSession.getInstance().deviceChanged();
+ mDeviceChanging = true;
+ if (item.isCheckable()) {
+ item.setChecked(!item.isChecked());
+ }
+ mSystemDevice = mConfig.getSystemDevice();
+ mUserSelection = device;
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mConfig.setCurrentDevice(mUserSelection, true);
+ }
+ });
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ Bundle savedInstanceState) {
+ if (container == null) {
+ Log.w(TAG, "container is null.");
+ // no longer displaying this fragment
+ return null;
+ }
+
+ View root = inflater.inflate(mConfig.hasMaxxAudio()
+ ? R.layout.fragment_audiofx_maxxaudio
+ : R.layout.fragment_audiofx, container, false);
+
+ final FragmentTransaction fragmentTransaction = getChildFragmentManager()
+ .beginTransaction();
+
+ boolean createNewFrags = true;
+
+ if (savedInstanceState != null) {
+ createNewFrags = showFragments();
+ }
+
+ if (createNewFrags) {
+ fragmentTransaction.add(R.id.equalizer, mEqFragment = new EqualizerFragment(),
+ TAG_EQUALIZER);
+ fragmentTransaction.add(R.id.controls, mControlFragment = new ControlsFragment(),
+ TAG_CONTROLS);
+ }
+
+ fragmentTransaction.commit();
+
+
+ return root;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ // view was destroyed
+ final FragmentTransaction fragmentTransaction = getChildFragmentManager()
+ .beginTransaction();
+
+ if (mEqFragment != null) {
+ fragmentTransaction.remove(mEqFragment);
+ mEqFragment = null;
+ }
+ if (mControlFragment != null) {
+ fragmentTransaction.remove(mControlFragment);
+ mControlFragment = null;
+ }
+
+ fragmentTransaction.commitAllowingStateLoss();
+
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mInterceptLayout = (InterceptableLinearLayout) view.findViewById(R.id.interceptable_layout);
+ }
+
+ public void animateBackgroundColorTo(int colorTo, Animator.AnimatorListener listener,
+ ColorUpdateListener updateListener) {
+ if (mColorChangeAnimator != null) {
+ mColorChangeAnimator.cancel();
+ mColorChangeAnimator = null;
+ }
+ mColorChangeAnimator = ValueAnimator.ofObject(new ArgbEvaluator(),
+ mCurrentBackgroundColor, colorTo);
+ mColorChangeAnimator.setDuration(500);
+ mColorChangeAnimator.addUpdateListener(updateListener != null ? updateListener
+ : mColorUpdateListener);
+ if (listener != null) {
+ mColorChangeAnimator.addListener(listener);
+ }
+ mColorChangeAnimator.start();
+ }
+
+ @Override
+ public void onDeviceChanged(AudioDeviceInfo device, boolean userChange) {
+ updateEnabledState();
+ getActivity().invalidateOptionsMenu();
+ }
+
+ public CompoundButton getGlobalSwitch() {
+ return ((ActivityMusic) getActivity()).getGlobalSwitch();
+ }
+
+ @Override
+ public void onGlobalDeviceToggle(final boolean checked) {
+ final CompoundButton buttonView = getGlobalSwitch();
+ final Animator.AnimatorListener animatorListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ buttonView.setEnabled(false);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateEnabledState();
+ buttonView.setEnabled(true);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ buttonView.setEnabled(true);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ };
+ final Integer colorTo = checked
+ ? mEqManager.getAssociatedPresetColorHex(mEqManager.getCurrentPresetIndex())
+ : mDisabledColor;
+ animateBackgroundColorTo(colorTo, animatorListener, null);
+ }
+
+ private ValueAnimator.AnimatorUpdateListener mColorUpdateListener
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateBackgroundColors((Integer) animation.getAnimatedValue(), false);
+ }
+ };
+
+ public static class ColorUpdateListener implements ValueAnimator.AnimatorUpdateListener {
+
+ final AudioFxBaseFragment mFrag;
+
+ public ColorUpdateListener(AudioFxBaseFragment frag) {
+ this.mFrag = frag;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mFrag.setBackgroundColor((Integer) animation.getAnimatedValue(), false);
+ }
+ }
+
+ public int getDisabledColor() {
+ return mDisabledColor;
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/fragment/ControlsFragment.java b/src/org/cyanogenmod/audiofx/audiofx/fragment/ControlsFragment.java
new file mode 100644
index 0000000..02e7077
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/fragment/ControlsFragment.java
@@ -0,0 +1,103 @@
+package com.cyngn.audiofx.fragment;
+
+import android.annotation.Nullable;
+import android.media.AudioDeviceInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.knobs.KnobCommander;
+import com.cyngn.audiofx.knobs.KnobContainer;
+import com.cyngn.audiofx.stats.UserSession;
+
+public class ControlsFragment extends AudioFxBaseFragment {
+
+ private static final String TAG = ControlsFragment.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ KnobCommander mKnobCommander;
+ KnobContainer mKnobContainer;
+ CheckBox mMaxxVolumeSwitch;
+
+ private CompoundButton.OnCheckedChangeListener mMaxxVolumeListener
+ = new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (mConfig.getMaxxVolumeEnabled() != isChecked) {
+ UserSession.getInstance().maxxVolumeToggled();
+ }
+ mConfig.setMaxxVolumeEnabled(isChecked);
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mKnobCommander = KnobCommander.getInstance(getActivity());
+ }
+
+ @Override
+ public void onPause() {
+ MasterConfigControl.getInstance(getActivity()).getCallbacks().removeDeviceChangedCallback(mKnobContainer);
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ MasterConfigControl.getInstance(getActivity()).getCallbacks().addDeviceChangedCallback(mKnobContainer);
+ }
+
+ @Override
+ public void updateFragmentBackgroundColors(int color) {
+ if (mKnobContainer != null) {
+ mKnobContainer.updateKnobHighlights(color);
+ }
+ }
+
+
+ public void updateEnabledState() {
+ final AudioDeviceInfo device = mConfig.getCurrentDevice();
+ boolean currentDeviceEnabled = mConfig.isCurrentDeviceEnabled();
+
+ if (DEBUG) {
+ Log.d(TAG, "updating with current device: " + device.getType());
+ }
+
+ if (mMaxxVolumeSwitch != null) {
+ mMaxxVolumeSwitch.setChecked(mConfig.getMaxxVolumeEnabled());
+ mMaxxVolumeSwitch.setEnabled(currentDeviceEnabled);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(mConfig.hasMaxxAudio() ? R.layout.controls_maxx_audio
+ : R.layout.controls_generic, container, false);
+ return root;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mKnobContainer = (KnobContainer) view.findViewById(R.id.knob_container);
+ mMaxxVolumeSwitch = (CheckBox) view.findViewById(R.id.maxx_volume_switch);
+
+ updateFragmentBackgroundColors(getCurrentBackgroundColor());
+
+ if (mMaxxVolumeSwitch != null) {
+ mMaxxVolumeSwitch.setOnCheckedChangeListener(mMaxxVolumeListener);
+ }
+ }
+
+
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/fragment/EqualizerFragment.java b/src/org/cyanogenmod/audiofx/audiofx/fragment/EqualizerFragment.java
new file mode 100644
index 0000000..2d958b2
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/fragment/EqualizerFragment.java
@@ -0,0 +1,559 @@
+package com.cyngn.audiofx.fragment;
+
+import android.animation.Animator;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.graphics.drawable.ColorDrawable;
+import android.media.AudioDeviceInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.view.ViewPager;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.cyngn.audiofx.Preset;
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.EqualizerManager;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.activity.StateCallbacks;
+import com.cyngn.audiofx.eq.EqContainerView;
+import com.cyngn.audiofx.preset.InfinitePagerAdapter;
+import com.cyngn.audiofx.preset.InfiniteViewPager;
+import com.cyngn.audiofx.preset.PresetPagerAdapter;
+import com.cyngn.audiofx.stats.UserSession;
+import com.cyngn.audiofx.viewpagerindicator.CirclePageIndicator;
+
+import java.util.Arrays;
+
+public class EqualizerFragment extends AudioFxBaseFragment
+ implements StateCallbacks.DeviceChangedCallback, StateCallbacks.EqUpdatedCallback {
+
+ private static final String TAG = EqualizerFragment.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_VIEWPAGER = true;
+
+ private final ArgbEvaluator mArgbEval = new ArgbEvaluator();
+
+ public EqContainerView mEqContainer;
+ InfiniteViewPager mPresetPager;
+ CirclePageIndicator mPresetPageIndicator;
+ PresetPagerAdapter mDataAdapter;
+ InfinitePagerAdapter mInfiniteAdapter;
+ int mCurrentRealPage;
+
+ private Handler mHandler;
+
+ // whether we are in the middle of animating while switching devices
+ boolean mDeviceChanging;
+
+ private ViewPager mFakePager;
+
+ private int mAnimatingToRealPageTarget = -1;
+
+ /*
+ * this array can hold on to arrays which store preset levels,
+ * so modifying values in here should only be done with extreme care
+ */
+ private float[] mSelectedPositionBands;
+
+ // current selected index
+ public int mSelectedPosition = 0;
+
+ private MasterConfigControl mConfig;
+ private EqualizerManager mEqManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mConfig = MasterConfigControl.getInstance(getActivity());
+ mEqManager = mConfig.getEqualizerManager();
+
+ mHandler = new Handler();
+ }
+
+ @Override
+ public void onPause() {
+ mEqContainer.stopListening();
+ mConfig.getCallbacks().removeDeviceChangedCallback(this);
+ mConfig.getCallbacks().removeEqUpdatedCallback(this);
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mEqContainer.startListening();
+ mConfig.getCallbacks().addEqUpdatedCallback(this);
+ mConfig.getCallbacks().addDeviceChangedCallback(this);
+ mPresetPageIndicator.notifyDataSetChanged();
+ mDataAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void updateFragmentBackgroundColors(int color) {
+ if (getActivity() != null && getActivity().getWindow() != null) {
+ getActivity().getWindow().setBackgroundDrawable(new ColorDrawable(color));
+ }
+ }
+
+ public void jumpToPreset(int index) {
+ int diff = index - (mCurrentRealPage % mDataAdapter.getCount());
+ // double it, short (e.g. 1 hop) distances sometimes bug out??
+ diff += mDataAdapter.getCount();
+ int newPage = mCurrentRealPage + diff;
+ mPresetPager.setCurrentItemAbsolute(newPage, false);
+ }
+
+ private void removeCurrentCustomPreset(boolean showWarning) {
+ if (showWarning) {
+ Preset p = mEqManager.getCurrentPreset();
+ new AlertDialog.Builder(getActivity())
+ .setMessage(String.format(getString(
+ R.string.remove_custom_preset_warning_message), p.getName()))
+ .setNegativeButton(android.R.string.no, null)
+ .setPositiveButton(android.R.string.yes,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ removeCurrentCustomPreset(false);
+ }
+ })
+ .create()
+ .show();
+ return;
+ }
+
+ final int currentIndexBeforeRemove = mEqManager.getCurrentPresetIndex();
+ if (mEqManager.removePreset(currentIndexBeforeRemove)) {
+ mInfiniteAdapter.notifyDataSetChanged();
+ mDataAdapter.notifyDataSetChanged();
+ mPresetPageIndicator.notifyDataSetChanged();
+
+ jumpToPreset(mSelectedPosition - 1);
+ }
+ }
+
+ private void openRenameDialog() {
+ AlertDialog.Builder renameDialog = new AlertDialog.Builder(getActivity());
+ renameDialog.setTitle(R.string.rename);
+ final EditText newName = new EditText(getActivity());
+ newName.setText(mEqManager.getCurrentPreset().getName());
+ renameDialog.setView(newName);
+ renameDialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface d, int which) {
+ mEqManager.renameCurrentPreset(newName.getText().toString());
+ final TextView viewWithTag = (TextView) mPresetPager
+ .findViewWithTag(mEqManager.getCurrentPreset());
+ viewWithTag.setText(newName.getText().toString());
+ mDataAdapter.notifyDataSetChanged();
+ mPresetPager.invalidate();
+ }
+ });
+
+ renameDialog.setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface d, int which) {
+ }
+ });
+
+ // disable ok button if text is empty
+ final AlertDialog dialog = renameDialog.create();
+ newName.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s.length() == 0) {
+ dialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(false);
+ } else {
+ dialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(true);
+ }
+ }
+ });
+
+ dialog.show();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.equalizer, container, false);
+ return root;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mSelectedPositionBands = mEqManager.getPersistedPresetLevels(mEqManager.getCurrentPresetIndex());
+ mSelectedPosition = mEqManager.getCurrentPresetIndex();
+
+ mEqContainer = (EqContainerView) view.findViewById(R.id.eq_container);
+ mPresetPager = (InfiniteViewPager) view.findViewById(R.id.pager);
+ mPresetPageIndicator = (CirclePageIndicator) view.findViewById(R.id.indicator);
+ mFakePager = (ViewPager) view.findViewById(R.id.fake_pager);
+
+ mEqContainer.findViewById(R.id.save).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ final int newidx = mEqManager.addPresetFromCustom();
+ mInfiniteAdapter.notifyDataSetChanged();
+ mDataAdapter.notifyDataSetChanged();
+ mPresetPageIndicator.notifyDataSetChanged();
+
+ jumpToPreset(newidx);
+ }
+ }
+ );
+ mEqContainer.findViewById(R.id.rename).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mEqManager.isUserPreset()) {
+ openRenameDialog();
+ }
+ }
+ }
+ );
+ mEqContainer.findViewById(R.id.remove).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ removeCurrentCustomPreset(true);
+ }
+ }
+ );
+
+ mDataAdapter = new PresetPagerAdapter(getActivity());
+ mInfiniteAdapter = new InfinitePagerAdapter(mDataAdapter);
+
+ mPresetPager.setAdapter(mInfiniteAdapter);
+ mPresetPager.setOnPageChangeListener(mViewPageChangeListener);
+
+ mFakePager.setAdapter(mDataAdapter);
+ mCurrentRealPage = mPresetPager.getCurrentItem();
+
+ mPresetPageIndicator.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // eat all events
+ return true;
+ }
+ });
+ mPresetPageIndicator.setSnap(true);
+
+ mPresetPageIndicator.setViewPager(mFakePager, 0);
+ mPresetPageIndicator.setCurrentItem(mSelectedPosition);
+
+ mFakePager.setCurrentItem(mSelectedPosition);
+ mPresetPager.setCurrentItem(mSelectedPosition);
+ }
+
+ @Override
+ public void onBandLevelChange(int band, float dB, boolean fromSystem) {
+ // call backs we get when bands are changing, check if the user is physically touching them
+ // and set the preset to "custom" and do proper animations.
+ if (!fromSystem) { // from user
+ if (!mEqManager.isCustomPreset() // not on custom already
+ && !mEqManager.isUserPreset() // or not on a user preset
+ && !mEqManager.isAnimatingToCustom()) { // and animation hasn't started
+ if (DEBUG) Log.w(TAG, "met conditions to start an animation to custom trigger");
+ // view pager is infinite, so we can't set the item to 0. find NEXT 0
+ mEqManager.setAnimatingToCustom(true);
+
+ final int newIndex = mEqManager.copyToCustom();
+
+ mInfiniteAdapter.notifyDataSetChanged();
+ mDataAdapter.notifyDataSetChanged();
+ mPresetPager.getAdapter().notifyDataSetChanged();
+ // do background transition manually as viewpager can't handle this bg change
+ final Integer colorTo = !mConfig.isCurrentDeviceEnabled()
+ ? getDisabledColor()
+ : mEqManager.getAssociatedPresetColorHex(newIndex);
+ final Animator.AnimatorListener listener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ int diff = newIndex - (mCurrentRealPage % mDataAdapter.getCount());
+ diff += mDataAdapter.getCount();
+ int newPage = mCurrentRealPage + diff;
+
+ mAnimatingToRealPageTarget = newPage;
+ mPresetPager.setCurrentItemAbsolute(newPage);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+ };
+ animateBackgroundColorTo(colorTo, listener, null);
+
+ }
+ mSelectedPositionBands[band] = dB;
+ }
+ }
+
+ @Override
+ public void onPresetChanged(int newPresetIndex) {
+ }
+
+ @Override
+ public void onPresetsChanged() {
+ mDataAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onDeviceChanged(AudioDeviceInfo device, boolean userChange) {
+ int diff = mEqManager.getCurrentPresetIndex() - mSelectedPosition;
+ final boolean samePage = diff == 0;
+ diff = mDataAdapter.getCount() + diff;
+ if (DEBUG) {
+ Log.d(TAG, "diff: " + diff);
+ }
+ mCurrentRealPage = mPresetPager.getCurrentItem();
+
+ if (DEBUG) Log.d(TAG, "mCurrentRealPage Before: " + mCurrentRealPage);
+ final int newPage = mCurrentRealPage + diff;
+ if (DEBUG) Log.d(TAG, "mCurrentRealPage After: " + newPage);
+
+ mSelectedPositionBands = mEqManager.getPresetLevels(mSelectedPosition);
+ final float[] targetBandLevels = mEqManager.getPresetLevels(mEqManager.getCurrentPresetIndex());
+
+ // do background transition manually as viewpager can't handle this bg change
+ final Integer colorTo = !mConfig.isCurrentDeviceEnabled()
+ ? getDisabledColor()
+ : mEqManager.getAssociatedPresetColorHex(mEqManager.getCurrentPresetIndex());
+
+ final Animator.AnimatorListener animatorListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mEqManager.setChangingPresets(true);
+
+ mDeviceChanging = true;
+
+ if (!samePage) {
+ mPresetPager.setCurrentItemAbsolute(newPage);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mEqManager.setChangingPresets(false);
+
+ mSelectedPosition = mEqManager.getCurrentPresetIndex();
+ mSelectedPositionBands = mEqManager.getPresetLevels(mSelectedPosition);
+
+ mDeviceChanging = false;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ };
+
+ final AudioFxFragment.ColorUpdateListener animatorUpdateListener
+ = new AudioFxFragment.ColorUpdateListener(this) {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ super.onAnimationUpdate(animator);
+
+ final int N = mEqManager.getNumBands();
+ for (int i = 0; i < N; i++) { // animate bands
+ float delta = targetBandLevels[i] - mSelectedPositionBands[i];
+ float newBandLevel = mSelectedPositionBands[i]
+ + (delta * animator.getAnimatedFraction());
+ //if (DEBUG_VIEWPAGER) Log.d(TAG, i + ", delta: " + delta + ", newBandLevel: " + newBandLevel);
+ mEqManager.setLevel(i, newBandLevel, true);
+ }
+ }
+ };
+
+ animateBackgroundColorTo(colorTo, animatorListener, animatorUpdateListener);
+ }
+
+ @Override
+ public void onGlobalDeviceToggle(boolean on) {
+ if (!on) {
+ mFakePager.setCurrentItem(mFakePager.getCurrentItem(), true);
+ }
+ }
+
+
+ private ViewPager.OnPageChangeListener mViewPageChangeListener = new ViewPager.OnPageChangeListener() {
+
+ int mState;
+ float mLastOffset;
+ boolean mJustGotToCustomAndSettling;
+
+ @Override
+ public void onPageScrolled(int newPosition, float positionOffset, int positionOffsetPixels) {
+ if (DEBUG_VIEWPAGER)
+ Log.i(TAG, "onPageScrolled(" + newPosition + ", " + positionOffset + ", "
+ + positionOffsetPixels + ")");
+ Integer colorFrom;
+ Integer colorTo;
+
+ if (newPosition == mAnimatingToRealPageTarget && mEqManager.isAnimatingToCustom()) {
+ if (DEBUG_VIEWPAGER) Log.w(TAG, "settling var set to true");
+ mJustGotToCustomAndSettling = true;
+ mAnimatingToRealPageTarget = -1;
+ }
+
+ newPosition = newPosition % mDataAdapter.getCount();
+
+
+ if (mEqManager.isAnimatingToCustom() || mDeviceChanging) {
+ if (DEBUG_VIEWPAGER)
+ Log.i(TAG, "ignoring onPageScrolled because animating to custom or device is changing");
+ return;
+ }
+
+ int toPos;
+ if (mLastOffset - positionOffset > 0.8) { // this is needed for flings
+ //Log.e(TAG, "OFFSET DIFF > 0.8! Setting selected position from: " + mSelectedPosition + " to " + newPosition);
+ mSelectedPosition = newPosition;
+ // mSelectedPositionBands will be reset by setPreset() below calling back to onPresetChanged()
+
+ mEqManager.setPreset(mSelectedPosition);
+ }
+
+ if (newPosition < mSelectedPosition || (newPosition == mDataAdapter.getCount() - 1)
+ && mSelectedPosition == 0) {
+ // scrolling left <<<<<
+ positionOffset = (1 - positionOffset);
+ //Log.v(TAG, "<<<<<< positionOffset: " + positionOffset + " (last offset: " + mLastOffset + ")");
+ toPos = newPosition;
+ colorTo = mEqManager.getAssociatedPresetColorHex(toPos);
+ } else {
+ // scrolling right >>>>>
+ //Log.v(TAG, ">>>>>>> positionOffset: " + positionOffset + " (last offset: " + mLastOffset + ")");
+ toPos = newPosition + 1 % mDataAdapter.getCount();
+ if (toPos >= mDataAdapter.getCount()) {
+ toPos = 0;
+ }
+
+ colorTo = mEqManager.getAssociatedPresetColorHex(toPos);
+ }
+
+ if (!mDeviceChanging && mConfig.isCurrentDeviceEnabled()) {
+ colorFrom = mEqManager.getAssociatedPresetColorHex(mSelectedPosition);
+ setBackgroundColor((Integer) mArgbEval.evaluate(positionOffset, colorFrom, colorTo),
+ true);
+ }
+
+ if (mSelectedPositionBands == null) {
+ mSelectedPositionBands = mEqManager.getPresetLevels(mSelectedPosition);
+ }
+ // get current bands
+ float[] finalPresetLevels = mEqManager.getPresetLevels(toPos);
+
+ final int N = mEqManager.getNumBands();
+ for (int i = 0; i < N; i++) { // animate bands
+ float delta = finalPresetLevels[i] - mSelectedPositionBands[i];
+ float newBandLevel = mSelectedPositionBands[i] + (delta * positionOffset);
+ //if (DEBUG_VIEWPAGER) Log.d(TAG, i + ", delta: " + delta + ", newBandLevel: " + newBandLevel);
+ mEqManager.setLevel(i, newBandLevel, true);
+ }
+ mLastOffset = positionOffset;
+
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (DEBUG_VIEWPAGER) Log.i(TAG, "onPageSelected(" + position + ")");
+ mCurrentRealPage = position;
+ position = position % mDataAdapter.getCount();
+ if (DEBUG_VIEWPAGER) Log.e(TAG, "onPageSelected(" + position + ")");
+ mFakePager.setCurrentItem(position);
+ mSelectedPosition = position;
+ if (!mDeviceChanging) {
+ mSelectedPositionBands = mEqManager.getPresetLevels(mSelectedPosition);
+ if (UserSession.getInstance() != null) {
+ UserSession.getInstance().presetSelected();
+ }
+ }
+ }
+
+
+ @Override
+ public void onPageScrollStateChanged(int newState) {
+ mState = newState;
+ if (mDeviceChanging) { // avoid setting unwanted presets during custom animations
+ return;
+ }
+ if (DEBUG_VIEWPAGER)
+ Log.w(TAG, "onPageScrollStateChanged(" + stateToString(newState) + ")");
+
+ if (mJustGotToCustomAndSettling && mState == ViewPager.SCROLL_STATE_IDLE) {
+ if (DEBUG_VIEWPAGER)
+ Log.w(TAG, "onPageScrollChanged() setting animating to custom = false");
+ mJustGotToCustomAndSettling = false;
+ mEqManager.setChangingPresets(false);
+ mEqManager.setAnimatingToCustom(false);
+ } else {
+ if (mState == ViewPager.SCROLL_STATE_IDLE) {
+ animateBackgroundColorTo(!mConfig.isCurrentDeviceEnabled()
+ ? getDisabledColor()
+ : mEqManager.getAssociatedPresetColorHex(mSelectedPosition),
+ null, null);
+
+ mEqManager.setChangingPresets(false);
+ mEqManager.setPreset(mSelectedPosition);
+
+ } else {
+ // not idle
+ mEqManager.setChangingPresets(true);
+ }
+ }
+ }
+
+ private String stateToString(int state) {
+ switch (state) {
+ case 0:
+ return "STATE_IDLE";
+ case 1:
+ return "STATE_DRAGGING";
+ case 2:
+ return "STATE_SETTLING";
+ default:
+ return "STATE_WUT";
+ }
+ }
+
+ };
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/knobs/KnobCommander.java b/src/org/cyanogenmod/audiofx/audiofx/knobs/KnobCommander.java
new file mode 100644
index 0000000..b045c16
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/knobs/KnobCommander.java
@@ -0,0 +1,177 @@
+package com.cyngn.audiofx.knobs;
+
+import android.content.Context;
+import com.cyngn.audiofx.Constants;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.service.AudioFxService;
+
+public class KnobCommander {
+
+ public static final int KNOB_TREBLE = 0;
+ public static final int KNOB_BASS = 1;
+ public static final int KNOB_VIRTUALIZER = 2;
+
+ private static KnobCommander sInstance;
+
+ private Context mContext;
+ private MasterConfigControl mConfig;
+
+ private KnobCommander(Context context) {
+ mContext = context;
+ mConfig = MasterConfigControl.getInstance(mContext);
+ }
+
+ public static KnobCommander getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new KnobCommander(context);
+ }
+ return sInstance;
+ }
+
+ public RadialKnob.OnKnobChangeListener getRadialKnobCallback(int whichKnob) {
+ switch (whichKnob) {
+ case KNOB_TREBLE: return mTrebleKnobCallback;
+ case KNOB_BASS: return mBassKnobCallback;
+ case KNOB_VIRTUALIZER: return mVirtualizerCallback;
+ default: return null;
+ }
+ }
+
+ public void updateTrebleKnob(RadialKnob trebleKnob, boolean enabled) {
+ if (trebleKnob != null) {
+ trebleKnob.setValue(getTrebleStrength());
+ trebleKnob.setOn(isTrebleEffectEnabled());
+ trebleKnob.setEnabled(enabled);
+ }
+ }
+
+ public void updateBassKnob(RadialKnob bassKnob, boolean enabled) {
+ if (bassKnob != null) {
+ bassKnob.setValue(getBassStrength());
+ bassKnob.setOn(isBassEffectEnabled());
+ bassKnob.setEnabled(enabled);
+ }
+ }
+
+ public void updateVirtualizerKnob(RadialKnob virtualizerKnob, boolean enabled) {
+ if (virtualizerKnob != null) {
+ virtualizerKnob.setValue(getVirtualizerStrength());
+ virtualizerKnob.setOn(isVirtualizerEffectEnabled());
+ virtualizerKnob.setEnabled(enabled);
+ }
+ }
+
+ public boolean hasBassBoost() {
+ return mConfig.hasBassBoost();
+ }
+
+ public boolean hasTreble() {
+ return mConfig.hasMaxxAudio() || mConfig.hasDts();
+ }
+
+ public boolean hasVirtualizer() {
+ return mConfig.hasVirtualizer();
+ }
+
+ public boolean isBassEffectEnabled() {
+ return mConfig.getPrefs().getBoolean(Constants.DEVICE_AUDIOFX_BASS_ENABLE, false);
+ }
+
+ public boolean isTrebleEffectEnabled() {
+ return mConfig.getPrefs().getBoolean(Constants.DEVICE_AUDIOFX_TREBLE_ENABLE, false);
+ }
+
+ public boolean isVirtualizerEffectEnabled() {
+ return mConfig.getPrefs().getBoolean(Constants.DEVICE_AUDIOFX_VIRTUALIZER_ENABLE, false);
+ }
+
+ public int getVirtualizerStrength() {
+ return Integer.valueOf(mConfig.getPrefs().getString(Constants.DEVICE_AUDIOFX_VIRTUALIZER_STRENGTH, "0")) / 10;
+ }
+
+ public int getBassStrength() {
+ return Integer.valueOf(mConfig.getPrefs().getString(Constants.DEVICE_AUDIOFX_BASS_STRENGTH, "0")) / 10;
+ }
+
+ public int getTrebleStrength() {
+ return Integer.valueOf(mConfig.getPrefs().getString(Constants.DEVICE_AUDIOFX_TREBLE_STRENGTH, "0"));
+ }
+
+ public void setTrebleEnabled(boolean on) {
+ mConfig.getPrefs().edit().putBoolean(Constants.DEVICE_AUDIOFX_TREBLE_ENABLE, on).apply();
+ mConfig.updateService(AudioFxService.TREBLE_BOOST_CHANGED);
+ }
+
+ public void setTrebleStrength(int value) {
+ // set parameter and state
+ mConfig.getPrefs().edit().putString(Constants.DEVICE_AUDIOFX_TREBLE_STRENGTH, String.valueOf(value)).apply();
+ mConfig.updateService(AudioFxService.TREBLE_BOOST_CHANGED);
+ }
+
+ public void setBassEnabled(boolean on) {
+ mConfig.getPrefs().edit().putBoolean(Constants.DEVICE_AUDIOFX_BASS_ENABLE, on).apply();
+ mConfig.updateService(AudioFxService.BASS_BOOST_CHANGED);
+ }
+
+ public void setBassStrength(int value) {
+ // set parameter and state
+ mConfig.getPrefs().edit().putString(Constants.DEVICE_AUDIOFX_BASS_STRENGTH, String.valueOf(value * 10)).apply();
+ mConfig.updateService(AudioFxService.BASS_BOOST_CHANGED);
+ }
+
+ public void setVirtualizerEnabled(boolean on) {
+ mConfig.getPrefs().edit().putBoolean(Constants.DEVICE_AUDIOFX_VIRTUALIZER_ENABLE, on).apply();
+ mConfig.updateService(AudioFxService.VIRTUALIZER_CHANGED);
+ }
+
+ public void setVirtualiserStrength(int value) {
+ // set parameter and state
+ mConfig.getPrefs().edit().putString(Constants.DEVICE_AUDIOFX_VIRTUALIZER_STRENGTH, String.valueOf(value * 10)).apply();
+ mConfig.updateService(AudioFxService.VIRTUALIZER_CHANGED);
+ }
+
+ private RadialKnob.OnKnobChangeListener mTrebleKnobCallback = new RadialKnob.OnKnobChangeListener() {
+ @Override
+ public void onValueChanged(RadialKnob knob, int value, boolean fromUser) {
+ if (fromUser) {
+ setTrebleStrength(value);
+ }
+ }
+
+ @Override
+ public boolean onSwitchChanged(RadialKnob knob, boolean on) {
+ setTrebleEnabled(on);
+ return true;
+ }
+ };
+
+ private RadialKnob.OnKnobChangeListener mBassKnobCallback = new RadialKnob.OnKnobChangeListener() {
+ @Override
+ public void onValueChanged(RadialKnob knob, int value, boolean fromUser) {
+ if (fromUser) {
+ setBassStrength(value);
+ }
+ }
+
+ @Override
+ public boolean onSwitchChanged(RadialKnob knob, boolean on) {
+ setBassEnabled(on);
+ return true;
+ }
+ };
+
+ private RadialKnob.OnKnobChangeListener mVirtualizerCallback = new RadialKnob.OnKnobChangeListener() {
+ @Override
+ public void onValueChanged(RadialKnob knob, int value, boolean fromUser) {
+ if (fromUser) {
+ setVirtualiserStrength(value);
+ }
+ }
+
+ @Override
+ public boolean onSwitchChanged(RadialKnob knob, boolean on) {
+ setVirtualizerEnabled(on);
+ return true;
+ }
+ };
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/knobs/KnobContainer.java b/src/org/cyanogenmod/audiofx/audiofx/knobs/KnobContainer.java
new file mode 100644
index 0000000..91e811b
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/knobs/KnobContainer.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.cyngn.audiofx.knobs;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.media.AudioDeviceInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.activity.StateCallbacks;
+
+public class KnobContainer extends LinearLayout
+ implements StateCallbacks.DeviceChangedCallback {
+
+ private static final String TAG = KnobContainer.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final int NOTIFY_DISABLE_DELAY = 5000;
+
+ private static final int MSG_EXPAND = 0;
+ private static final int MSG_CONTRACT = 1;
+
+ private ViewGroup mTrebleContainer;
+ private ViewGroup mBassContainer;
+ private ViewGroup mVirtualizerContainer;
+ private RadialKnob mTrebleKnob;
+ private RadialKnob mBassKnob;
+ private RadialKnob mVirtualizerKnob;
+
+ private H mHandler;
+
+ private KnobCommander mKnobCommander;
+
+ private long mLastDisabledNotifyTime = -1;
+
+ public KnobContainer(Context context) {
+ super(context);
+ }
+
+ public KnobContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public KnobContainer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ private void init() {
+ mKnobCommander = KnobCommander.getInstance(mContext);
+ mHandler = new H();
+
+ if (!MasterConfigControl.getInstance(mContext).hasMaxxAudio()) {
+ // we must add the proper knobs dynamically.
+ if (mKnobCommander.hasBassBoost()) {
+ mBassContainer = addKnob(KnobCommander.KNOB_BASS);
+ }
+ if (mKnobCommander.hasTreble()) {
+ mTrebleContainer = addKnob(KnobCommander.KNOB_TREBLE);
+ }
+ if (mKnobCommander.hasVirtualizer()) {
+ mVirtualizerContainer = addKnob(KnobCommander.KNOB_VIRTUALIZER);
+ }
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ init();
+
+ if (DEBUG) Log.d(TAG, "onFinishInflate()");
+
+ OnTouchListener knobTouchListener = new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ Message message;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ message = mHandler.obtainMessage(MSG_EXPAND, v.getTag());
+ mHandler.sendMessageDelayed(message, 0);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mHandler.removeMessages(MSG_EXPAND);
+ message = mHandler.obtainMessage(MSG_CONTRACT, v.getTag());
+ mHandler.sendMessageDelayed(message, 10);
+ break;
+ }
+ if (!v.isEnabled()) {
+ notifyDisabled();
+ return true;
+ }
+ return false;
+ }
+ };
+
+ if (MasterConfigControl.getInstance(getContext()).hasMaxxAudio()) {
+ mVirtualizerContainer = (ViewGroup) findViewById(R.id.virtualizer_knob_container);
+ mBassContainer = (ViewGroup) findViewById(R.id.bass_knob_container);
+ mTrebleContainer = (ViewGroup) findViewById(R.id.treble_knob_container);
+ }
+
+ if (mTrebleContainer != null) {
+ mTrebleKnob = (RadialKnob) mTrebleContainer.findViewById(R.id.knob);
+ mTrebleKnob.setTag(new KnobInfo(KnobCommander.KNOB_TREBLE, mTrebleKnob,
+ mTrebleContainer.findViewById(R.id.label)));
+ mTrebleKnob.setOnTouchListener(knobTouchListener);
+ mTrebleKnob.setOnKnobChangeListener(
+ KnobCommander.getInstance(getContext()).getRadialKnobCallback(
+ KnobCommander.KNOB_TREBLE
+ )
+ );
+ mTrebleKnob.setMax(100);
+ }
+ if (mBassContainer != null) {
+ mBassKnob = (RadialKnob) mBassContainer.findViewById(R.id.knob);
+ mBassKnob.setTag(new KnobInfo(KnobCommander.KNOB_BASS, mBassKnob, mBassContainer.findViewById(R.id.label)));
+ mBassKnob.setOnTouchListener(knobTouchListener);
+ mBassKnob.setOnKnobChangeListener(
+ KnobCommander.getInstance(getContext()).getRadialKnobCallback(
+ KnobCommander.KNOB_BASS
+ )
+ );
+ mBassKnob.setMax(100);
+
+
+ }
+ if (mVirtualizerContainer != null) {
+ mVirtualizerKnob = (RadialKnob) mVirtualizerContainer.findViewById(R.id.knob);
+ mVirtualizerKnob.setTag(new KnobInfo(KnobCommander.KNOB_VIRTUALIZER, mVirtualizerKnob,
+ mVirtualizerContainer.findViewById(R.id.label)));
+ mVirtualizerKnob.setOnTouchListener(knobTouchListener);
+ mVirtualizerKnob.setOnKnobChangeListener(
+ KnobCommander.getInstance(getContext()).getRadialKnobCallback(
+ KnobCommander.KNOB_VIRTUALIZER
+ )
+ );
+ mVirtualizerKnob.setMax(100);
+ }
+ updateKnobs(MasterConfigControl.getInstance(mContext).getCurrentDevice());
+
+ if (!MasterConfigControl.getInstance(mContext).hasMaxxAudio()) {
+ setLayoutTransition(null);
+ }
+ }
+
+ private ViewGroup addKnob(int whichKnob) {
+ ViewGroup knobContainer = (ViewGroup) LayoutInflater.from(mContext)
+ .inflate(R.layout.generic_knob_control, this, false);
+ TextView label = (TextView) knobContainer.findViewById(R.id.label);
+
+ int newContainerId = 0;
+ int knobLabelRes = 0;
+ switch (whichKnob) {
+ case KnobCommander.KNOB_BASS:
+ newContainerId = R.id.bass_knob_container;
+ knobLabelRes = R.string.bass;
+ break;
+
+ case KnobCommander.KNOB_TREBLE:
+ newContainerId = R.id.treble_knob_container;
+ knobLabelRes = R.string.treble;
+ break;
+
+ case KnobCommander.KNOB_VIRTUALIZER:
+ newContainerId = R.id.virtualizer_knob_container;
+ knobLabelRes = R.string.virtualizer;
+ break;
+
+ default:
+ return null;
+ }
+
+ knobContainer.setId(newContainerId);
+ label.setText(knobLabelRes);
+
+ addView(knobContainer, getKnobParams());
+ return knobContainer;
+ }
+
+ private LayoutParams getKnobParams() {
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ } else {
+ return new LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1);
+ }
+ }
+
+ public void setKnobVisible(int knob, boolean visible) {
+ final int newMode = visible ? View.VISIBLE : View.GONE;
+ ViewGroup v = null;
+ switch (knob) {
+ case KnobCommander.KNOB_VIRTUALIZER:
+ v = mVirtualizerContainer;
+ break;
+ case KnobCommander.KNOB_BASS:
+ v = mBassContainer;
+ break;
+ case KnobCommander.KNOB_TREBLE:
+ v = mTrebleContainer;
+ break;
+ }
+ if (v == null && visible) {
+ throw new UnsupportedOperationException("no knob container for knob: " + knob);
+ }
+
+ if (newMode == v.getVisibility()) {
+ return;
+ }
+ Log.d(TAG, "setKnobVisible() knob=" + knob + " visible=" + visible);
+ v.setVisibility(newMode);
+
+ // only used on maxx audio layout
+ if (MasterConfigControl.getInstance(mContext).hasMaxxAudio()) {
+ /* ensure spacing looks ok!
+ *
+ * it goes like, Space, knob layout, Space, knob layout, Space, etc.....
+ * starting with the first knob (skipping the first space), ensure the pairs have the
+ * same visibility so there's no extra space at the end.
+ */
+ for (int i = 1; i < getChildCount() - 1; i += 2) {
+ View layout = getChildAt(i);
+ View space = getChildAt(i + 1);
+ if (space.getVisibility() != layout.getVisibility()) {
+ space.setVisibility(layout.getVisibility());
+ }
+ }
+ }
+ }
+
+ public void updateKnobHighlights(int color) {
+ if (mTrebleKnob != null) {
+ mTrebleKnob.setHighlightColor(color);
+ }
+ if (mBassKnob != null) {
+ mBassKnob.setHighlightColor(color);
+ }
+ if (mVirtualizerKnob != null) {
+ mVirtualizerKnob.setHighlightColor(color);
+ }
+ }
+
+ private void notifyDisabled() {
+ final long now = System.currentTimeMillis();
+ if (mLastDisabledNotifyTime == -1 || now - mLastDisabledNotifyTime > NOTIFY_DISABLE_DELAY) {
+ mLastDisabledNotifyTime = now;
+ Toast.makeText(mContext, R.string.effect_unavalable_for_speaker,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ private void resize(RadialKnob knob, View label, boolean makeBig) {
+ if (knob.isEnabled()) {
+ label.animate()
+ .alpha(makeBig ? 0 : 1)
+ .setInterpolator(new AccelerateInterpolator())
+ .setDuration(100);
+
+ /*
+ if (makeBig) {
+ ResizeAnimation resizeAnimation = new ResizeAnimation(this);
+ resizeAnimation.setHeightParams(getHeight(), mExpandedHeight);
+ resizeAnimation.setDuration(100);
+ startAnimation(resizeAnimation);
+ } else {
+ ResizeAnimation resizeAnimation = new ResizeAnimation(this);
+ resizeAnimation.setHeightParams(getHeight(), mRegularHeight);
+ resizeAnimation.setDuration(100);
+ startAnimation(resizeAnimation);
+ }
+ */
+ knob.resize(makeBig);
+ }
+ }
+
+ @Override
+ public void onDeviceChanged(AudioDeviceInfo device, boolean userChange) {
+ if (device != null) {
+ updateKnobs(device);
+ }
+ }
+
+ @Override
+ public void onGlobalDeviceToggle(boolean on) {
+
+ }
+
+ private void updateKnobs(AudioDeviceInfo device) {
+ if (device == null) {
+ return;
+ }
+ final boolean speaker = device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+ final boolean maxxAudio = MasterConfigControl.getInstance(mContext).hasMaxxAudio();
+ final boolean dts = MasterConfigControl.getInstance(mContext).hasDts();
+ final boolean effectsEnabled = !speaker || maxxAudio || dts;
+
+ mKnobCommander.updateTrebleKnob(mTrebleKnob, effectsEnabled);
+ mKnobCommander.updateBassKnob(mBassKnob, effectsEnabled);
+ mKnobCommander.updateVirtualizerKnob(mVirtualizerKnob, effectsEnabled);
+ if (maxxAudio) {
+ // speaker? hide virtualizer
+ setKnobVisible(KnobCommander.KNOB_VIRTUALIZER, !speaker);
+ } else if (dts) {
+ // same for DTS
+ setKnobVisible(KnobCommander.KNOB_VIRTUALIZER, !speaker);
+ } else {
+ setKnobVisible(KnobCommander.KNOB_VIRTUALIZER, true);
+ }
+ }
+
+ public static class KnobInfo {
+ int whichKnob;
+ RadialKnob knob;
+ View label;
+
+ public KnobInfo(int whichKnob, RadialKnob knob, View label) {
+ this.knob = knob;
+ this.label = label;
+ this.whichKnob = whichKnob;
+ }
+ }
+
+ private class H extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_EXPAND:
+ case MSG_CONTRACT:
+ RadialKnob knob = ((KnobInfo) msg.obj).knob;
+ View label = ((KnobInfo) msg.obj).label;
+ boolean expand = msg.what == MSG_EXPAND;
+ resize(knob, label, expand);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/knobs/RadialKnob.java b/src/org/cyanogenmod/audiofx/audiofx/knobs/RadialKnob.java
new file mode 100644
index 0000000..0e4cbb2
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/knobs/RadialKnob.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2015, The CyanogenMod Project. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.cyngn.audiofx.knobs;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.Toast;
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.stats.UserSession;
+
+public class RadialKnob extends View {
+
+ private static final String TAG = RadialKnob.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final float REGULAR_SCALE = 0.8f;
+
+ public static final float TOUCHING_SCALE = 1f;
+ private static final int DO_NOT_VIBRATE_THRESHOLD = 100;
+
+ private static final int DEGREE_OFFSET = -225;
+ private static final int START_ANGLE = 360 + DEGREE_OFFSET;
+ private static final int MAX_DEGREES = 270;
+
+ private final Paint mPaint, mTextPaint;
+
+ ValueAnimator mAnimator;
+ float mOffProgress;
+ boolean mAnimating = false;
+ long mDownTime;
+ long mUpTime;
+ private OnKnobChangeListener mOnKnobChangeListener = null;
+ private float mProgress = 0.0f;
+ private float mTouchProgress = 0.0f;
+ private int mMax = 100;
+ private boolean mOn = false;
+ private boolean mEnabled = true;
+ private float mLastX;
+ private float mLastY;
+ private boolean mMoved;
+ private int mWidth = 0;
+ private RectF mRectF, mOuterRect = new RectF(), mInnerRect = new RectF();
+ private float mLastAngle;
+ private Long mLastVibrateTime;
+ private int mHighlightColor;
+ private int mBackgroundArcColor;
+ private int mBackgroundArcColorDisabled;
+ private int mRectPadding;
+ private int mStrokeWidth;
+ private float mHandleWidth; // little square indicator where user touches
+ private float mTextOffset;
+
+ Path mPath = new Path();
+ PathMeasure mPathMeasure = new PathMeasure();
+ float[] mTmp = new float[2];
+ float mStartX, mStopX, mStartY, mStopY;
+
+ public RadialKnob(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ Resources res = getResources();
+ mBackgroundArcColor = res.getColor(R.color.radial_knob_arc_bg);
+ mBackgroundArcColorDisabled = res.getColor(R.color.radial_knob_arc_bg_disabled);
+ mHighlightColor = res.getColor(R.color.highlight);
+
+ mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setElegantTextHeight(true);
+ mTextPaint.setFakeBoldText(true);
+ mTextPaint.setTextSize(res.getDimension(R.dimen.radial_text_size));
+ mTextPaint.setColor(Color.LTGRAY);
+
+ mTextOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2,
+ getResources().getDisplayMetrics());
+
+ mHandleWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5,
+ getResources().getDisplayMetrics());
+
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaint.setColor(mHighlightColor);
+ mPaint.setStrokeWidth(mStrokeWidth = res.getDimensionPixelSize(R.dimen.radial_knob_stroke));
+ mPaint.setStrokeCap(Paint.Cap.BUTT);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setShadowLayer(2, 1, -2, getResources().getColor(R.color.black));
+
+ setScaleX(REGULAR_SCALE);
+ setScaleY(REGULAR_SCALE);
+
+ mRectPadding = res.getDimensionPixelSize(R.dimen.radial_rect_padding);
+ invalidate();
+ }
+
+ public RadialKnob(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RadialKnob(Context context) {
+ this(context, null);
+ }
+
+ public void setValue(int value) {
+ if (mMax != 0) {
+ setProgress(((float) value) / mMax);
+ mTouchProgress = mProgress;
+ mLastAngle = mProgress * MAX_DEGREES;
+ }
+ }
+
+ public void setProgress(float progress, boolean fromUser) {
+ if (progress > 1.0f) {
+ progress = 1.0f;
+ }
+ if (progress < 0.0f) {
+ progress = 0.0f;
+ }
+
+ mProgress = progress;
+
+ invalidate();
+
+ if (mOnKnobChangeListener != null) {
+ mOnKnobChangeListener.onValueChanged(this, (int) (progress * mMax), fromUser);
+ }
+ }
+
+ public void setMax(int max) {
+ mMax = max;
+ }
+
+ public float getProgress() {
+ return mProgress;
+ }
+
+ public void setProgress(float progress) {
+ setProgress(progress, false);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ if (enabled) {
+ setOn(mOn);
+ }
+ invalidate();
+ }
+
+ public void setOn(final boolean on) {
+ mOn = on;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ invalidate();
+ }
+
+ public void setHighlightColor(int color) {
+ mPaint.setColor(mHighlightColor = color);
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ mPaint.setStrokeWidth(mStrokeWidth);
+
+ mPaint.setColor(mEnabled ? mBackgroundArcColor : mBackgroundArcColorDisabled);
+ canvas.drawArc(mRectF, START_ANGLE, MAX_DEGREES, false, mPaint);
+
+ final float sweepAngle = mEnabled ? mProgress * MAX_DEGREES : 0;
+ if (mOn) {
+ mPaint.setColor(mHighlightColor);
+ canvas.drawArc(mRectF, START_ANGLE, sweepAngle, false, mPaint);
+ }
+
+ final float indicatorSweepAngle = Math.max(1f, sweepAngle);
+
+ // render the indicator
+ mPath.reset();
+ mPath.arcTo(mInnerRect, START_ANGLE, indicatorSweepAngle, true);
+
+ mPathMeasure.setPath(mPath, false);
+ mPathMeasure.getPosTan(mPathMeasure.getLength(), mTmp, null);
+
+ mStartX = mTmp[0];
+ mStartY = mTmp[1];
+
+ mPath.reset();
+ mPath.arcTo(mOuterRect, START_ANGLE, indicatorSweepAngle, true);
+
+ mPathMeasure.setPath(mPath, false);
+ mPathMeasure.getPosTan(mPathMeasure.getLength(), mTmp, null);
+
+ mStopX = mTmp[0];
+ mStopY = mTmp[1];
+
+ mPaint.setStrokeWidth(mHandleWidth);
+ mPaint.setColor(Color.WHITE);
+ canvas.drawLine(mStartX, mStartY, mStopX, mStopY, mPaint);
+
+ canvas.drawText(getProgressText(),
+ mOuterRect.centerX(),
+ mOuterRect.centerY() + (mTextPaint.getTextSize() / 2.f) - mTextOffset,
+ mTextPaint);
+ }
+
+ private String getProgressText() {
+ if (mEnabled) {
+ return ((int) (mProgress * 100)) + "%";
+ } else {
+ return "--";
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldW, int oldH) {
+ super.onSizeChanged(w, h, oldW, oldH);
+
+ int size = w > h ? h : w;
+ mWidth = size;
+ int diff;
+ if (w > h) {
+ diff = (w - h) / 2;
+ mRectF = new RectF(mRectPadding + diff, mRectPadding,
+ w - mRectPadding - diff, h - mRectPadding);
+ } else {
+ diff = (h - w) / 2;
+ mRectF = new RectF(mRectPadding, mRectPadding + diff,
+ w - mRectPadding, h - mRectPadding - diff);
+ }
+ mOuterRect.set(mRectF);
+ mOuterRect.inset(-mRectPadding, -mRectPadding);
+ mInnerRect.set(mRectF);
+ mInnerRect.inset(mRectPadding, mRectPadding);
+ }
+
+ private boolean isUserSelected() {
+ return getScaleX() == TOUCHING_SCALE && getScaleY() == TOUCHING_SCALE;
+ }
+
+ private void animateTo(float progress) {
+ if (DEBUG) Log.w(TAG, "animateTo(" + progress + ")");
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ mAnimator = ValueAnimator.ofFloat(mProgress, progress);
+ mAnimator.setDuration(100);
+ mAnimator.setInterpolator(new AccelerateInterpolator());
+ mAnimator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mAnimating = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimating = false;
+ postInvalidate();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mAnimating = false;
+ postInvalidate();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+
+ }
+ });
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float progress = (Float) animation.getAnimatedValue();
+ mProgress = progress;
+ mLastAngle = mProgress * MAX_DEGREES;
+ if (DEBUG) Log.i(TAG, "onAnimationUpdate(): mProgress: "
+ + mProgress + ", mLastAngle: " + mLastAngle);
+
+ setProgress(mProgress);
+ if (mOnKnobChangeListener != null) {
+ mOnKnobChangeListener.onValueChanged(RadialKnob.this,
+ (int) (progress * mMax), true);
+ }
+ postInvalidate();
+ }
+ });
+ mAnimator.start();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ if (!mEnabled) {
+ return false;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownTime = System.currentTimeMillis();
+ mOffProgress = 0;
+
+ getParent().requestDisallowInterceptTouchEvent(true);
+ vibrate();
+ mLastX = event.getX();
+ mLastY = event.getY();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // we can be animating while moving
+ if (mAnimating) {
+ return true;
+ }
+ final float center = getWidth() / 2;
+ final float radius = (center / 2) - (mRectPadding * 2);
+
+ final boolean inDeadzone = inCircle(x, y, center, center, radius);
+ final boolean inOuterCircle = inCircle(x, y, center, center, radius + 70);
+ if (DEBUG)
+ Log.d(TAG, "inOuterCircle: " + inOuterCircle + ", inDeadzone: " + inDeadzone);
+ final float delta = getDelta(x, y);
+ final float angle = angleWithOffset(x, y, DEGREE_OFFSET);
+
+ if (mOn) {
+ if (isUserSelected() && (!inDeadzone)) {
+ float angleDiff = Math.abs(mLastAngle - angle);
+ if (mProgress == 1 && angle < (MAX_DEGREES / 2)) {
+ // oh jeez. no jumping from 100!
+ return true;
+ }
+ if (angleDiff < 90) {
+ // jump!
+ //Log.w(TAG, "using angle");
+ mLastAngle = angle;
+ mTouchProgress = angle / MAX_DEGREES;
+ mMoved = true;
+ if (DEBUG) Log.v(TAG, "ANGLE setProgress: " + mTouchProgress);
+ setProgress(mTouchProgress, true);
+ } else if (angle > 0 && angle < MAX_DEGREES) {
+ if (DEBUG) Log.v(TAG, "ANGLE animateTo: " + angle);
+ mMoved = true;
+ animateTo(angle / MAX_DEGREES);
+ }
+ }
+ // if it's less than one degree, turn it off
+ // 1% ~= 2.7 degrees, pick something slightly higher
+ if (mTouchProgress < (2.71f / MAX_DEGREES) && mOn && mMoved) {
+ mTouchProgress = (2.71f / MAX_DEGREES);
+ if (mOnKnobChangeListener != null) {
+ mOnKnobChangeListener.onSwitchChanged(this, !mOn);
+ }
+ setOn(!mOn);
+ }
+ } else {
+ // off
+ if (isUserSelected() && (!inDeadzone)) {
+ if (delta > 0) {
+ mOffProgress += delta;
+ } else if (angle > 90) {
+ mOffProgress = 0;
+ }
+ if (DEBUG)
+ Log.d(TAG, "OFF, touching angle: " + angle +
+ ", mOffProgress: " + mOffProgress + ", delta " + delta);
+ // we want at least 1%, how many degrees = 1%? + a little padding
+ final float onePercentInDegrees = (MAX_DEGREES / 100) + 1f;
+ if (mOffProgress > 15 && angle < MAX_DEGREES
+ && angle >= onePercentInDegrees) {
+ if (DEBUG) Log.w(TAG, "delta: " + delta);
+ if (angle <= MAX_DEGREES) {
+ if (mOnKnobChangeListener != null) {
+ mOnKnobChangeListener.onSwitchChanged(this, !mOn);
+ }
+
+ setOn(!mOn);
+ if (angle > 30) {
+ animateTo(angle / MAX_DEGREES);
+ } else {
+ setProgress(angle / MAX_DEGREES, true);
+ }
+ mLastAngle = angle;
+ mMoved = false;
+ } else {
+ if (DEBUG) Log.w(TAG, "off, angle > 300, ignoring");
+ }
+ }
+ }
+ mLastX = x;
+ mLastY = y;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mUpTime = System.currentTimeMillis();
+ final float finalAngle = angleWithOffset(x, y, DEGREE_OFFSET);
+ if (DEBUG) Log.d(TAG, "angle at death: " + finalAngle);
+ if (mUpTime - mDownTime < 100 && mMoved && finalAngle < MAX_DEGREES) {
+ if (mOn) {
+ animateTo(finalAngle / MAX_DEGREES);
+ } else {
+ if (mOnKnobChangeListener != null) {
+ mOnKnobChangeListener.onSwitchChanged(this, !mOn);
+ }
+
+ setOn(!mOn);
+ }
+ }
+ if (mMoved) {
+ UserSession.getInstance()
+ .knobOptionsAdjusted(((KnobContainer.KnobInfo)getTag()).whichKnob);
+ }
+ mLastX = -1;
+ mLastY = -1;
+ mOffProgress = 0;
+ mMoved = false;
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ private void vibrate() {
+ if (mLastVibrateTime == null || System.currentTimeMillis() - mLastVibrateTime
+ > DO_NOT_VIBRATE_THRESHOLD) {
+ Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ vibrator.vibrate(40);
+ mLastVibrateTime = System.currentTimeMillis();
+ }
+ }
+
+ public void resize(boolean selected) {
+ if (!mEnabled) {
+ return;
+ }
+ if (selected) {
+ animate()
+ .scaleY(RadialKnob.TOUCHING_SCALE)
+ .scaleX(RadialKnob.TOUCHING_SCALE)
+ .setDuration(100);
+ } else {
+ animate()
+ .scaleY(RadialKnob.REGULAR_SCALE)
+ .scaleX(RadialKnob.REGULAR_SCALE)
+ .setDuration(100);
+ }
+ }
+
+ private float getDelta(float x, float y) {
+ float angle = angle(x, y);
+ float oldAngle = angle(mLastX, mLastY);
+ float delta = angle - oldAngle;
+ if (delta >= 180.0f) {
+ delta = -oldAngle;
+ } else if (delta <= -180.0f) {
+ delta = 360 - oldAngle;
+ }
+ return delta;
+ }
+
+ private float angle(float x, float y) {
+ float center = mWidth / 2.0f;
+ x -= center;
+ y -= center;
+
+ if (x == 0.0f) {
+ if (y > 0.0f) {
+ return 180.0f;
+ } else {
+ return 0.0f;
+ }
+ }
+
+ float angle = (float) (Math.atan(y / x) / Math.PI * 180.0);
+ if (x > 0.0f) {
+ angle += 90;
+ } else {
+ angle += 270;
+ }
+ return angle;
+ }
+
+ private float angleWithOffset(float x, float y, int degreeOffset) {
+ float angle = angle(x, y);
+ if (angle > 180) {
+ angle += degreeOffset;
+ } else {
+ angle += (360 + degreeOffset);
+ }
+ return angle;
+ }
+
+
+ private static boolean inCircle(float x, float y, float circleCenterX, float circleCenterY,
+ float circleRadius) {
+ double dx = Math.pow(x - circleCenterX, 2);
+ double dy = Math.pow(y - circleCenterY, 2);
+
+ if ((dx + dy) < Math.pow(circleRadius, 2)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ public void setOnKnobChangeListener(OnKnobChangeListener l) {
+ mOnKnobChangeListener = l;
+ }
+
+ public interface OnKnobChangeListener {
+ void onValueChanged(RadialKnob knob, int value, boolean fromUser);
+
+ boolean onSwitchChanged(RadialKnob knob, boolean on);
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/preset/InfinitePagerAdapter.java b/src/org/cyanogenmod/audiofx/audiofx/preset/InfinitePagerAdapter.java
new file mode 100644
index 0000000..71811f4
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/preset/InfinitePagerAdapter.java
@@ -0,0 +1,95 @@
+package com.cyngn.audiofx.preset;
+
+import android.os.Parcelable;
+import android.support.v4.view.PagerAdapter;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A PagerAdapter that wraps around another PagerAdapter to handle paging wrap-around.
+ */
+public class InfinitePagerAdapter extends PagerAdapter {
+
+ private static final String TAG = "InfinitePagerAdapter";
+ private static final boolean DEBUG = false;
+
+ private PagerAdapter adapter;
+
+ public InfinitePagerAdapter(PagerAdapter adapter) {
+ this.adapter = adapter;
+ }
+
+ @Override
+ public int getCount() {
+ // warning: scrolling to very high values (1,000,000+) results in
+ // strange drawing behaviour
+ return Integer.MAX_VALUE;
+ }
+
+ /**
+ * @return the {@link #getCount()} result of the wrapped adapter
+ */
+ public int getRealCount() {
+ return adapter.getCount();
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ int virtualPosition = position % getRealCount();
+ debug("instantiateItem: real position: " + position);
+ debug("instantiateItem: virtual position: " + virtualPosition);
+
+ // only expose virtual position to the inner adapter
+ return adapter.instantiateItem(container, virtualPosition);
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ int virtualPosition = position % getRealCount();
+ debug("destroyItem: real position: " + position);
+ debug("destroyItem: virtual position: " + virtualPosition);
+
+ // only expose virtual position to the inner adapter
+ adapter.destroyItem(container, virtualPosition, object);
+ }
+
+ /*
+ * Delegate rest of methods directly to the inner adapter.
+ */
+
+ @Override
+ public void finishUpdate(ViewGroup container) {
+ adapter.finishUpdate(container);
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return adapter.isViewFromObject(view, object);
+ }
+
+ @Override
+ public void restoreState(Parcelable bundle, ClassLoader classLoader) {
+ adapter.restoreState(bundle, classLoader);
+ }
+
+ @Override
+ public Parcelable saveState() {
+ return adapter.saveState();
+ }
+
+ @Override
+ public void startUpdate(ViewGroup container) {
+ adapter.startUpdate(container);
+ }
+
+ /*
+ * End delegation
+ */
+
+ private void debug(String message) {
+ if (DEBUG) {
+ Log.d(TAG, message);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/cyanogenmod/audiofx/audiofx/preset/InfiniteViewPager.java b/src/org/cyanogenmod/audiofx/audiofx/preset/InfiniteViewPager.java
new file mode 100644
index 0000000..8a4ed38
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/preset/InfiniteViewPager.java
@@ -0,0 +1,106 @@
+package com.cyngn.audiofx.preset;
+
+
+import android.content.Context;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.EqualizerManager;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+
+/**
+ * A {@link ViewPager} that allows pseudo-infinite paging with a wrap-around effect. Should be used with an {@link
+ * InfinitePagerAdapter}.
+ */
+public class InfiniteViewPager extends ViewPager {
+
+ private final EqualizerManager mEqManager;
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ public InfiniteViewPager(Context context) {
+ super(context);
+ mEqManager = MasterConfigControl.getInstance(context).getEqualizerManager();
+ }
+
+ public InfiniteViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mEqManager = MasterConfigControl.getInstance(context).getEqualizerManager();
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mEqManager.isAnimatingToCustom()) {
+ return false;
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int textSize = getResources().getDimensionPixelSize(R.dimen.preset_text_size)
+ + getResources().getDimensionPixelSize(R.dimen.preset_text_padding);
+ super.onMeasure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(textSize, MeasureSpec.EXACTLY));
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mEqManager.isAnimatingToCustom()) {
+ return false;
+ }
+ boolean result;
+ try {
+ result = super.onTouchEvent(ev);
+ } catch (IllegalArgumentException e) {
+ /* There's a bug with the support library where it doesn't check
+ * the proper pointer index, so when multi touching the container,
+ * this can sometimes be thrown. Supposedly there are no downsides to just
+ * catching the exception and moving along, so let's do that....
+ */
+ result = false;
+ }
+ return result;
+ }
+
+ @Override
+ public void setAdapter(PagerAdapter adapter) {
+ super.setAdapter(adapter);
+ // offset first element so that we can scroll to the left
+ setCurrentItem(0);
+ }
+
+ @Override
+ public void setCurrentItem(int item) {
+ // offset the current item to ensure there is space to scroll
+ item = getOffsetAmount() + (item % getAdapter().getCount());
+ super.setCurrentItem(item);
+ }
+
+ public void setCurrentItemAbsolute(int item) {
+ super.setCurrentItem(item);
+ }
+
+ private int getOffsetAmount() {
+ if (getAdapter() instanceof InfinitePagerAdapter) {
+ InfinitePagerAdapter infAdapter = (InfinitePagerAdapter) getAdapter();
+ // allow for 100 back cycles from the beginning
+ // should be enough to create an illusion of infinity
+ // warning: scrolling to very high values (1,000,000+) results in
+ // strange drawing behaviour
+ return infAdapter.getRealCount() * 100;
+ } else {
+ return 0;
+ }
+ }
+
+ public void setCurrentItemAbsolute(int newPage, boolean b) {
+ super.setCurrentItem(newPage, b);
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/preset/PresetPagerAdapter.java b/src/org/cyanogenmod/audiofx/audiofx/preset/PresetPagerAdapter.java
new file mode 100644
index 0000000..f6875f7
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/preset/PresetPagerAdapter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.cyngn.audiofx.preset;
+
+import android.content.Context;
+import android.support.v4.view.PagerAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.cyngn.audiofx.Preset;
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.EqualizerManager;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+
+public class PresetPagerAdapter extends PagerAdapter {
+
+ private final Context mContext;
+ private final EqualizerManager mEqManager;
+
+ public PresetPagerAdapter(Context context) {
+ super();
+ mContext = context;
+ mEqManager = MasterConfigControl.getInstance(mContext).getEqualizerManager();
+ }
+
+ @Override
+ public int getItemPosition(Object object) {
+ View v = (View) object;
+ int index = mEqManager.indexOf(((Preset) v.getTag()));
+ if (index == -1) {
+ return POSITION_NONE;
+ } else {
+ return index;
+ }
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ View view = LayoutInflater.from(mContext)
+ .inflate(R.layout.preset_adapter_row, container, false);
+ TextView tv = (TextView) view;
+ tv.setText(mEqManager.getLocalizedPresetName(position));
+ tv.setTag(mEqManager.getPreset(position));
+ container.addView(tv);
+ return view;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ if (object instanceof View) {
+ container.removeView((View) object);
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mEqManager.getPresetCount();
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object o) {
+ return view == o;
+ }
+
+
+
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/receiver/QuickSettingsTileReceiver.java b/src/org/cyanogenmod/audiofx/audiofx/receiver/QuickSettingsTileReceiver.java
new file mode 100644
index 0000000..c27d44a
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/receiver/QuickSettingsTileReceiver.java
@@ -0,0 +1,36 @@
+package com.cyngn.audiofx.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.service.AudioFxService;
+
+/**
+ * Created by roman on 1/13/16.
+ */
+public class QuickSettingsTileReceiver extends BroadcastReceiver {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "QSTileReceiver";
+
+ public static final String ACTION_TOGGLE_CURRENT_DEVICE
+ = "com.cyngn.audiofx.action.TOGGLE_DEVICE";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Log.i(TAG, "onReceive() called with " + "context = [" + context + "], intent = [" + intent + "]");
+ }
+ if (ACTION_TOGGLE_CURRENT_DEVICE.equals(intent.getAction())) {
+ final MasterConfigControl config = MasterConfigControl.getInstance(context);
+
+ config.setCurrentDeviceEnabled(!config.isCurrentDeviceEnabled());
+
+ // tell service explicitly to update the qs tile in case UI isn't up to let it know
+ context.startService(new Intent(AudioFxService.ACTION_UPDATE_TILE)
+ .setClass(context, AudioFxService.class));
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/ServiceDispatcher.java b/src/org/cyanogenmod/audiofx/audiofx/receiver/ServiceDispatcher.java
index 7a838b1..7581ba2 100644
--- a/src/org/cyanogenmod/audiofx/ServiceDispatcher.java
+++ b/src/org/cyanogenmod/audiofx/audiofx/receiver/ServiceDispatcher.java
@@ -1,19 +1,5 @@
-/*
- * Copyright (C) 2016 The CyanogenMod Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.cyanogenmod.audiofx;
+
+package com.cyngn.audiofx.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -21,6 +7,8 @@ import android.content.Intent;
import android.media.audiofx.AudioEffect;
import android.util.Log;
+import com.cyngn.audiofx.service.AudioFxService;
+
import cyanogenmod.media.AudioSessionInfo;
import cyanogenmod.media.CMAudioManager;
@@ -28,7 +16,7 @@ public class ServiceDispatcher extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- Intent service = new Intent(context.getApplicationContext(), HeadsetService.class);
+ Intent service = new Intent(context.getApplicationContext(), AudioFxService.class);
String action = intent.getAction();
// We can also get AUDIO_BECOMING_NOISY, which means a device change is
@@ -39,10 +27,11 @@ public class ServiceDispatcher extends BroadcastReceiver {
String pkg = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
service.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId);
service.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, pkg);
+
} else if (action.equals(CMAudioManager.ACTION_AUDIO_SESSIONS_CHANGED)) {
// callback from CMAudioService
- final AudioSessionInfo info = (AudioSessionInfo)intent.getParcelableExtra(
+ final AudioSessionInfo info = (AudioSessionInfo) intent.getParcelableExtra(
CMAudioManager.EXTRA_SESSION_INFO);
boolean added = intent.getBooleanExtra(CMAudioManager.EXTRA_SESSION_ADDED, false);
service.putExtra(CMAudioManager.EXTRA_SESSION_INFO, info);
@@ -51,5 +40,9 @@ public class ServiceDispatcher extends BroadcastReceiver {
service.setAction(action);
context.startService(service);
+ if (AudioFxService.DEBUG) {
+ Log.d("AudioFX-Dispatcher", "Received " + action);
+ }
+
}
}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/service/AudioFxService.java b/src/org/cyanogenmod/audiofx/audiofx/service/AudioFxService.java
new file mode 100644
index 0000000..ce158b5
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/service/AudioFxService.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (C) 2016 Cyanogen Inc.
+ *
+ * Proprietary and confidential.
+ */
+package com.cyngn.audiofx.service;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.audiofx.AudioEffect;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.ActivityMusic;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.backends.EffectSet;
+import com.cyngn.audiofx.receiver.QuickSettingsTileReceiver;
+
+import java.lang.ref.WeakReference;
+import java.util.Locale;
+
+import cyanogenmod.app.CMStatusBarManager;
+import cyanogenmod.app.CustomTile;
+import cyanogenmod.media.AudioSessionInfo;
+import cyanogenmod.media.CMAudioManager;
+
+/**
+ * This service is responsible for applying all requested effects from the AudioFX UI.
+ *
+ * Since the AudioFX UI allows for different configurations based on the current output device,
+ * the service is also responsible for applying the effects properly based on user configuration,
+ * and the current device output state.
+ */
+public class AudioFxService extends Service
+ implements AudioOutputChangeListener.AudioOutputChangedCallback {
+
+ static final String TAG = AudioFxService.class.getSimpleName();
+
+ public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final boolean ENABLE_REVERB = false;
+
+ public static final String ACTION_DEVICE_OUTPUT_CHANGED
+ = "org.cyanogenmod.audiofx.ACTION_DEVICE_OUTPUT_CHANGED";
+
+ public static final String ACTION_UPDATE_TILE = "com.cyngn.audiofx.action.UPDATE_TILE";
+
+ public static final String EXTRA_DEVICE = "device";
+
+ // flags for updateService to minimize DSP traffic
+ public static final int EQ_CHANGED = 0x1;
+ public static final int BASS_BOOST_CHANGED = 0x2;
+ public static final int VIRTUALIZER_CHANGED = 0x4;
+ public static final int TREBLE_BOOST_CHANGED = 0x8;
+ public static final int VOLUME_BOOST_CHANGED = 0x10;
+ public static final int REVERB_CHANGED = 0x20;
+ public static final int ALL_CHANGED = 0xFF;
+
+ // flags from audio.h, used by session callbacks
+ static final int AUDIO_OUTPUT_FLAG_FAST = 0x4;
+ static final int AUDIO_OUTPUT_FLAG_DEEP_BUFFER = 0x8;
+ static final int AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD = 0x10;
+
+ private static final int TILE_ID = 555;
+
+ private Locale mLastLocale;
+
+ private CustomTile mTile;
+ private CustomTile.Builder mTileBuilder;
+
+ private AudioOutputChangeListener mOutputListener;
+ private DevicePreferenceManager mDevicePrefs;
+ private SessionManager mSessionManager;
+ private Handler mHandler;
+
+ private AudioDeviceInfo mCurrentDevice;
+
+ public static class LocalBinder extends Binder {
+
+ final WeakReference<AudioFxService> mService;
+
+ public LocalBinder(AudioFxService service) {// added a constructor for Stub here
+ mService = new WeakReference<AudioFxService>(service);
+ }
+
+ private boolean checkService() {
+ if (mService.get() == null) {
+ Log.e("AudioFx-LocalBinder", "Service was null!");
+ }
+ return mService.get() != null;
+ }
+
+ public void update(int flags) {
+ if (checkService()) {
+ mService.get().update(flags);
+ }
+ }
+
+ public void setOverrideLevels(short band, float level) {
+ if (checkService()) {
+ mService.get().mSessionManager.setOverrideLevels(band, level);
+ }
+ }
+
+ public EffectSet getEffect(Integer id) {
+ if (checkService()) {
+ return mService.get().mSessionManager.getEffectForSession(id);
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (DEBUG) Log.i(TAG, "Starting service.");
+
+ HandlerThread handlerThread = new HandlerThread("AudioFx-Backend");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+
+ mOutputListener = new AudioOutputChangeListener(getApplicationContext(), mHandler);
+ mOutputListener.addCallback(this);
+
+ mDevicePrefs = new DevicePreferenceManager(getApplicationContext(), mCurrentDevice);
+ if (!mDevicePrefs.initDefaults()) {
+ stopSelf();
+ return;
+ }
+
+ mSessionManager = new SessionManager(getApplicationContext(), mHandler, mDevicePrefs,
+ mCurrentDevice);
+ mOutputListener.addCallback(mDevicePrefs, mSessionManager);
+
+ final CMAudioManager cma = CMAudioManager.getInstance(getApplicationContext());
+ for (AudioSessionInfo asi : cma.listAudioSessions(AudioManager.STREAM_MUSIC)) {
+ mSessionManager.addSession(asi);
+ }
+
+ updateQsTile();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (DEBUG) {
+ Log.i(TAG, "onStartCommand() called with " + "intent = [" + intent + "], flags = ["
+ + flags + "], startId = [" + startId + "]");
+ }
+ if (intent != null && intent.getAction() != null) {
+ if (ACTION_UPDATE_TILE.equals(intent.getAction())) {
+ update(ALL_CHANGED);
+ } else {
+ String action = intent.getAction();
+ int sessionId = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION, 0);
+ String pkg = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME);
+ int stream = mapContentTypeToStream(
+ intent.getIntExtra(AudioEffect.EXTRA_CONTENT_TYPE,
+ AudioEffect.CONTENT_TYPE_MUSIC));
+
+ if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) {
+ if (DEBUG) {
+ Log.i(TAG, String.format("New audio session: %d package: %s contentType=%d",
+ sessionId, pkg, stream));
+ }
+ AudioSessionInfo info = new AudioSessionInfo(sessionId, stream, -1, -1, -1);
+ mSessionManager.addSession(info);
+
+ } else if (action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
+
+ AudioSessionInfo info = new AudioSessionInfo(sessionId, stream, -1, -1, -1);
+ mSessionManager.removeSession(info);
+
+ } else if (action.equals(CMAudioManager.ACTION_AUDIO_SESSIONS_CHANGED)) {
+
+ final AudioSessionInfo info = (AudioSessionInfo) intent.getParcelableExtra(
+ CMAudioManager.EXTRA_SESSION_INFO);
+ if (info != null && info.getSessionId() > 0) {
+ boolean added = intent.getBooleanExtra(CMAudioManager.EXTRA_SESSION_ADDED,
+ false);
+ if (added) {
+ mSessionManager.addSession(info);
+ } else {
+ mSessionManager.removeSession(info);
+ }
+ }
+
+ }
+ }
+ }
+ return START_STICKY;
+ }
+
+ /**
+ * maps {@link AudioEffect#EXTRA_CONTENT_TYPE} to an AudioManager.STREAM_* item
+ */
+ private static int mapContentTypeToStream(int contentType) {
+ switch (contentType) {
+ case AudioEffect.CONTENT_TYPE_VOICE:
+ return AudioManager.STREAM_VOICE_CALL;
+ case AudioEffect.CONTENT_TYPE_GAME:
+ // explicitly don't support game effects right now
+ return -1;
+ case AudioEffect.CONTENT_TYPE_MOVIE:
+ case AudioEffect.CONTENT_TYPE_MUSIC:
+ default:
+ return AudioManager.STREAM_MUSIC;
+ }
+ }
+
+ @Override
+ public synchronized void onAudioOutputChanged(boolean firstChange,
+ AudioDeviceInfo outputDevice) {
+ if (outputDevice == null) {
+ return;
+ }
+
+ mCurrentDevice = outputDevice;
+
+ if (DEBUG)
+ Log.d(TAG, "Broadcasting device changed event");
+
+ // Update the UI with the change
+ Intent intent = new Intent(ACTION_DEVICE_OUTPUT_CHANGED);
+ intent.putExtra("device", outputDevice.getId());
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
+
+ updateQsTile();
+ }
+
+ private void updateQsTile() {
+ if (mCurrentDevice == null || mDevicePrefs == null) {
+ // too early
+ return;
+ }
+ if (mTileBuilder == null) {
+ mTileBuilder = new CustomTile.Builder(this);
+ }
+
+ mLastLocale = getResources().getConfiguration().locale;
+ final PendingIntent pi = PendingIntent.getBroadcast(this, 0,
+ new Intent(QuickSettingsTileReceiver.ACTION_TOGGLE_CURRENT_DEVICE)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setClass(this, QuickSettingsTileReceiver.class), 0);
+
+ final PendingIntent longPress = PendingIntent.getActivity(this, 0,
+ new Intent(this, ActivityMusic.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
+
+ String label = getString(R.string.qs_tile_label,
+ MasterConfigControl.getDeviceDisplayString(this, mCurrentDevice));
+
+ mTileBuilder
+ .hasSensitiveData(false)
+ .setIcon(mDevicePrefs.isGlobalEnabled() ? R.drawable.ic_qs_visualizer_on
+ : R.drawable.ic_qs_visualizer_off)
+ .setLabel(label)
+ .setContentDescription(R.string.qs_tile_content_description)
+ .shouldCollapsePanel(false)
+ .setOnClickIntent(pi)
+ .setOnLongClickIntent(longPress);
+
+ mTile = mTileBuilder.build();
+
+ CMStatusBarManager.getInstance(this).publishTile(TILE_ID, mTile);
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.i(TAG, "Stopping service.");
+
+ mOutputListener.removeCallback(this, mSessionManager, mDevicePrefs);
+ mSessionManager.onDestroy();
+
+ CMStatusBarManager.getInstance(this).removeTile(TILE_ID);
+
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new LocalBinder(this);
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ if (DEBUG) Log.d(TAG, "onTrimMemory: level=" + level);
+ switch (level) {
+ case TRIM_MEMORY_BACKGROUND:
+ case TRIM_MEMORY_MODERATE:
+ case TRIM_MEMORY_RUNNING_MODERATE:
+ case TRIM_MEMORY_COMPLETE:
+ if (DEBUG) Log.d(TAG, "killing service if no effects active.");
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (!mSessionManager.hasActiveSessions()) {
+ stopSelf();
+ Log.w(TAG, "self destructing, no sessions active and nothing to do.");
+ }
+ }
+ }, 1000);
+ break;
+ }
+ }
+
+ /**
+ * Queue up a backend update.
+ */
+ private void update(int flags) {
+ mSessionManager.update(flags);
+
+ if ((flags & ALL_CHANGED) == ALL_CHANGED) {
+ updateQsTile();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (!mLastLocale.equals(newConfig.locale)) {
+ updateQsTile();
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/service/AudioOutputChangeListener.java b/src/org/cyanogenmod/audiofx/audiofx/service/AudioOutputChangeListener.java
new file mode 100644
index 0000000..f85d9c1
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/service/AudioOutputChangeListener.java
@@ -0,0 +1,131 @@
+
+package com.cyngn.audiofx.service;
+
+import static android.media.AudioDeviceInfo.convertDeviceTypeToInternalDevice;
+
+import android.content.Context;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class AudioOutputChangeListener extends AudioDeviceCallback {
+
+ private static final String TAG = "AudioFx-" + AudioOutputChangeListener.class.getSimpleName();
+
+ private boolean mInitial = true;
+
+ private final Context mContext;
+ private final AudioManager mAudioManager;
+ private final Handler mHandler;
+ private int mLastDevice = -1;
+
+ private final ArrayList<AudioOutputChangedCallback> mCallbacks = new ArrayList<AudioOutputChangedCallback>();
+
+ public interface AudioOutputChangedCallback {
+ public void onAudioOutputChanged(boolean firstChange, AudioDeviceInfo outputDevice);
+ }
+
+ public AudioOutputChangeListener(Context context, Handler handler) {
+ mContext = context;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mHandler = handler;
+ }
+
+ public void addCallback(AudioOutputChangedCallback... callbacks) {
+ synchronized (mCallbacks) {
+ boolean initial = mCallbacks.size() == 0;
+ mCallbacks.addAll(Arrays.asList(callbacks));
+ if (initial) {
+ mAudioManager.registerAudioDeviceCallback(this, mHandler);
+ }
+ }
+ }
+
+ public void removeCallback(AudioOutputChangedCallback... callbacks) {
+ synchronized (mCallbacks) {
+ mCallbacks.removeAll(Arrays.asList(callbacks));
+ if (mCallbacks.size() == 0) {
+ mAudioManager.unregisterAudioDeviceCallback(this);
+ }
+ }
+ }
+
+ private void callback() {
+ synchronized (mCallbacks) {
+ final AudioDeviceInfo device = getCurrentDevice();
+
+ if (device == null) {
+ Log.w(TAG, "Unable to determine audio device!");
+ return;
+ }
+
+ if (mInitial || device.getId() != mLastDevice) {
+ Log.d(TAG, "onAudioOutputChanged id: " + device.getId() +
+ " type: " + device.getType() +
+ " name: " + device.getProductName() +
+ " address: " + device.getAddress() +
+ " [" + device.toString() + "]");
+ mLastDevice = device.getId();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mCallbacks) {
+ for (AudioOutputChangedCallback callback : mCallbacks) {
+ callback.onAudioOutputChanged(mInitial, device);
+ }
+ }
+ }
+ });
+
+ if (mInitial) {
+ mInitial = false;
+ }
+ }
+ }
+ }
+
+ public void refresh() {
+ callback();
+ }
+
+ @Override
+ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ callback();
+ }
+
+ @Override
+ public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ callback();
+ }
+
+ public List<AudioDeviceInfo> getConnectedOutputs() {
+ final List<AudioDeviceInfo> outputs = new ArrayList<AudioDeviceInfo>();
+ final int forMusic = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
+ for (AudioDeviceInfo ai : mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
+ if ((convertDeviceTypeToInternalDevice(ai.getType()) & forMusic) > 0) {
+ outputs.add(ai);
+ }
+ }
+ return outputs;
+ }
+
+ public AudioDeviceInfo getCurrentDevice() {
+ final List<AudioDeviceInfo> devices = getConnectedOutputs();
+ return devices.size() > 0 ? devices.get(0) : null;
+ }
+
+ public AudioDeviceInfo getDeviceById(int id) {
+ for (AudioDeviceInfo ai : mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
+ if (ai.getId() == id) {
+ return ai;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/service/BootReceiver.java b/src/org/cyanogenmod/audiofx/audiofx/service/BootReceiver.java
new file mode 100644
index 0000000..df853a5
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/service/BootReceiver.java
@@ -0,0 +1,14 @@
+package com.cyngn.audiofx.service;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import com.cyngn.audiofx.Constants;
+
+public class BootReceiver extends BroadcastReceiver {
+ public void onReceive(Context context, Intent intent) {
+ final Intent service = new Intent(context.getApplicationContext(), AudioFxService.class);
+ context.startService(service);
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/service/DevicePreferenceManager.java b/src/org/cyanogenmod/audiofx/audiofx/service/DevicePreferenceManager.java
new file mode 100644
index 0000000..928209e
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/service/DevicePreferenceManager.java
@@ -0,0 +1,294 @@
+package com.cyngn.audiofx.service;
+
+import static com.cyngn.audiofx.Constants.AUDIOFX_GLOBAL_FILE;
+import static com.cyngn.audiofx.Constants.AUDIOFX_GLOBAL_HAS_BASSBOOST;
+import static com.cyngn.audiofx.Constants.AUDIOFX_GLOBAL_HAS_DTS;
+import static com.cyngn.audiofx.Constants.AUDIOFX_GLOBAL_HAS_MAXXAUDIO;
+import static com.cyngn.audiofx.Constants.AUDIOFX_GLOBAL_HAS_VIRTUALIZER;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_BASS_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_BASS_STRENGTH;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_EQ_PRESET;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_GLOBAL_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_MAXXVOLUME_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_TREBLE_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_TREBLE_STRENGTH;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_VIRTUALIZER_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_VIRTUALIZER_STRENGTH;
+import static com.cyngn.audiofx.Constants.DEVICE_HEADSET;
+import static com.cyngn.audiofx.Constants.DEVICE_SPEAKER;
+import static com.cyngn.audiofx.Constants.EQUALIZER_BAND_LEVEL_RANGE;
+import static com.cyngn.audiofx.Constants.EQUALIZER_CENTER_FREQS;
+import static com.cyngn.audiofx.Constants.EQUALIZER_NUMBER_OF_BANDS;
+import static com.cyngn.audiofx.Constants.EQUALIZER_NUMBER_OF_PRESETS;
+import static com.cyngn.audiofx.Constants.EQUALIZER_PRESET;
+import static com.cyngn.audiofx.Constants.EQUALIZER_PRESET_NAMES;
+import static com.cyngn.audiofx.Constants.SAVED_DEFAULTS;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.media.AudioDeviceInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.cyngn.audiofx.Constants;
+import com.cyngn.audiofx.R;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.backends.EffectSet;
+import com.cyngn.audiofx.backends.EffectsFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+class DevicePreferenceManager implements AudioOutputChangeListener.AudioOutputChangedCallback {
+
+ private static final String TAG = AudioFxService.TAG;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+
+ private AudioDeviceInfo mCurrentDevice;
+
+ public DevicePreferenceManager(Context context, AudioDeviceInfo device) {
+ mContext = context;
+ mCurrentDevice = device;
+ }
+
+ public boolean initDefaults() {
+ try {
+ saveAndApplyDefaults(false);
+ } catch (Exception e) {
+ SharedPreferences prefs = Constants.getGlobalPrefs(mContext);
+ prefs.edit().clear().commit();
+ Log.e(TAG, "Failed to initialize defaults!", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onAudioOutputChanged(boolean firstChange, AudioDeviceInfo outputDevice) {
+ mCurrentDevice = outputDevice;
+ }
+
+ public SharedPreferences getCurrentDevicePrefs() {
+ return mContext.getSharedPreferences(
+ MasterConfigControl.getDeviceIdentifierString(mCurrentDevice), 0);
+ }
+
+ public SharedPreferences prefsFor(final String name) {
+ return mContext.getSharedPreferences(name, 0);
+ }
+
+ private boolean hasPrefs(final String name) {
+ return mContext.getSharedPrefsFile(name).exists();
+ }
+
+ public boolean isGlobalEnabled() {
+ return getCurrentDevicePrefs().getBoolean(DEVICE_AUDIOFX_GLOBAL_ENABLE, false);
+ }
+
+ /**
+ * This method sets some sane defaults for presets, device defaults, etc
+ * <p/>
+ * First we read presets from the system, then adjusts some setting values
+ * for some better defaults!
+ */
+ private void saveAndApplyDefaults(boolean overridePrevious) {
+ if (DEBUG) {
+ Log.d(TAG, "saveAndApplyDefaults() called with overridePrevious = " +
+ "[" + overridePrevious + "]");
+ }
+ SharedPreferences prefs = Constants.getGlobalPrefs(mContext);
+
+ final int currentPrefVer = prefs.getInt(Constants.AUDIOFX_GLOBAL_PREFS_VERSION_INT, 0);
+ boolean needsPrefsUpdate = currentPrefVer < Constants.CURRENT_PREFS_INT_VERSION
+ || overridePrevious;
+
+ if (needsPrefsUpdate) {
+ Log.d(TAG, "rebuilding presets due to preference upgrade from " + currentPrefVer
+ + " to " + Constants.CURRENT_PREFS_INT_VERSION);
+ }
+
+ if (prefs.getBoolean(SAVED_DEFAULTS, false) && !needsPrefsUpdate) {
+ if (DEBUG) {
+ Log.e(TAG, "we've already saved defaults and don't need a pref update. aborting.");
+ }
+ return;
+ }
+ EffectSet temp = new EffectsFactory().createEffectSet(mContext, 0, null);
+
+ final int numBands = temp.getNumEqualizerBands();
+ final int numPresets = temp.getNumEqualizerPresets();
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(EQUALIZER_NUMBER_OF_PRESETS, String.valueOf(numPresets));
+ editor.putString(EQUALIZER_NUMBER_OF_BANDS, String.valueOf(numBands));
+
+ // range
+ short[] rangeShortArr = temp.getEqualizerBandLevelRange();
+ editor.putString(EQUALIZER_BAND_LEVEL_RANGE, rangeShortArr[0]
+ + ";" + rangeShortArr[1]);
+
+ // center freqs
+ StringBuilder centerFreqs = new StringBuilder();
+ // audiofx.global.centerfreqs
+ for (short i = 0; i < numBands; i++) {
+ centerFreqs.append(temp.getCenterFrequency(i));
+ centerFreqs.append(";");
+
+ }
+ centerFreqs.deleteCharAt(centerFreqs.length() - 1);
+ editor.putString(EQUALIZER_CENTER_FREQS, centerFreqs.toString());
+
+ // populate preset names
+ StringBuilder presetNames = new StringBuilder();
+ for (int i = 0; i < numPresets; i++) {
+ String presetName = temp.getEqualizerPresetName((short) i);
+ presetNames.append(presetName);
+ presetNames.append("|");
+
+ // populate preset band values
+ StringBuilder presetBands = new StringBuilder();
+ temp.useEqualizerPreset((short) i);
+
+ for (int j = 0; j < numBands; j++) {
+ // loop through preset bands
+ presetBands.append(temp.getEqualizerBandLevel((short) j));
+ presetBands.append(";");
+ }
+ presetBands.deleteCharAt(presetBands.length() - 1);
+ editor.putString(EQUALIZER_PRESET + i, presetBands.toString());
+ }
+ if (presetNames.length() > 0) {
+ presetNames.deleteCharAt(presetNames.length() - 1);
+ }
+ editor.putString(EQUALIZER_PRESET_NAMES, presetNames.toString());
+
+ editor.putBoolean(AUDIOFX_GLOBAL_HAS_VIRTUALIZER, temp.hasVirtualizer());
+ editor.putBoolean(AUDIOFX_GLOBAL_HAS_BASSBOOST, temp.hasBassBoost());
+ editor.putBoolean(AUDIOFX_GLOBAL_HAS_MAXXAUDIO, temp.getBrand() == Constants.EFFECT_TYPE_MAXXAUDIO);
+ editor.putBoolean(AUDIOFX_GLOBAL_HAS_DTS, temp.getBrand() == Constants.EFFECT_TYPE_DTS);
+ editor.commit();
+ temp.release();
+
+ applyDefaults(needsPrefsUpdate);
+
+ prefs
+ .edit()
+ .putInt(Constants.AUDIOFX_GLOBAL_PREFS_VERSION_INT,
+ Constants.CURRENT_PREFS_INT_VERSION)
+ .putBoolean(Constants.SAVED_DEFAULTS, true)
+ .commit();
+ }
+
+ private static int findInList(String needle, List<String> haystack) {
+ for (int i = 0; i < haystack.size(); i++) {
+ if (haystack.get(i).equalsIgnoreCase(needle)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * This method sets up some *persisted* defaults.
+ * Prereq: saveDefaults() must have been run before this can apply its defaults properly.
+ */
+ private void applyDefaults(boolean overridePrevious) {
+ if (DEBUG) {
+ Log.d(TAG, "applyDefaults() called with overridePrevious = [" + overridePrevious + "]");
+ }
+
+ if (!(overridePrevious || !hasPrefs(DEVICE_SPEAKER) ||
+ !hasPrefs(AUDIOFX_GLOBAL_FILE))) {
+ return;
+ }
+
+ final SharedPreferences globalPrefs = Constants.getGlobalPrefs(mContext);
+
+ // Nothing to see here for EFFECT_TYPE_DTS
+ if (globalPrefs.getBoolean(AUDIOFX_GLOBAL_HAS_DTS, false)) {
+ return;
+ }
+
+ // set up the builtin speaker configuration
+ final String smallSpeakers = getNonLocalizedString(R.string.small_speakers);
+ final List<String> presetNames = new ArrayList<String>(Arrays.asList(
+ globalPrefs.getString(EQUALIZER_PRESET_NAMES, "").split("\\|")));
+ final SharedPreferences speakerPrefs = prefsFor(DEVICE_SPEAKER);
+
+ if (globalPrefs.getBoolean(AUDIOFX_GLOBAL_HAS_MAXXAUDIO, false)) {
+ // MaxxAudio defaults for builtin speaker:
+ // maxxvolume: on maxxbass: 40% maxxtreble: 32%
+ speakerPrefs.edit()
+ .putBoolean(DEVICE_AUDIOFX_GLOBAL_ENABLE, true)
+ .putBoolean(DEVICE_AUDIOFX_MAXXVOLUME_ENABLE, true)
+ .putBoolean(DEVICE_AUDIOFX_BASS_ENABLE, true)
+ .putString(DEVICE_AUDIOFX_BASS_STRENGTH, "400")
+ .putBoolean(DEVICE_AUDIOFX_TREBLE_ENABLE, true)
+ .putString(DEVICE_AUDIOFX_TREBLE_STRENGTH, "32")
+ .commit();
+
+ // Defaults for headphones
+ // maxxvolume: on maxxbass: 20% maxxtreble: 40% maxxspace: 20%
+ prefsFor(DEVICE_HEADSET).edit()
+ .putBoolean(DEVICE_AUDIOFX_GLOBAL_ENABLE, true)
+ .putBoolean(DEVICE_AUDIOFX_MAXXVOLUME_ENABLE, true)
+ .putBoolean(DEVICE_AUDIOFX_BASS_ENABLE, true)
+ .putString(DEVICE_AUDIOFX_BASS_STRENGTH, "200")
+ .putBoolean(DEVICE_AUDIOFX_TREBLE_ENABLE, true)
+ .putString(DEVICE_AUDIOFX_TREBLE_STRENGTH, "40")
+ .putBoolean(DEVICE_AUDIOFX_VIRTUALIZER_ENABLE, true)
+ .putString(DEVICE_AUDIOFX_VIRTUALIZER_STRENGTH, "200")
+ .commit();
+ } else {
+ // Defaults for headphones
+ // bass boost: 15% virtualizer: 20% preset: FLAT
+ int flat = findInList(getNonLocalizedString(R.string.flat), presetNames);
+ prefsFor(DEVICE_HEADSET).edit()
+ .putBoolean(DEVICE_AUDIOFX_GLOBAL_ENABLE, true)
+ .putBoolean(DEVICE_AUDIOFX_BASS_ENABLE, true)
+ .putString(DEVICE_AUDIOFX_BASS_STRENGTH, "150")
+ .putBoolean(DEVICE_AUDIOFX_VIRTUALIZER_ENABLE, true)
+ .putString(DEVICE_AUDIOFX_VIRTUALIZER_STRENGTH, "200")
+ .putString(DEVICE_AUDIOFX_EQ_PRESET, (flat >= 0 ? String.valueOf(flat) : "0"))
+ .commit();
+ }
+
+ // for 5 band configs, let's add a `Small Speaker` configuration if one
+ // doesn't exist ( from oss AudioFX: -170;270;50;-220;200 )
+ if (Integer.parseInt(globalPrefs.getString(EQUALIZER_NUMBER_OF_BANDS, "0")) == 5 &&
+ findInList(smallSpeakers, presetNames) < 0) {
+
+ int currentPresets = Integer.parseInt(
+ globalPrefs.getString(EQUALIZER_NUMBER_OF_PRESETS, "0"));
+
+ presetNames.add(smallSpeakers);
+ String newPresetNames = TextUtils.join("|", presetNames);
+ globalPrefs.edit()
+ .putString(EQUALIZER_PRESET + currentPresets, "-170;270;50;-220;200")
+ .putString(EQUALIZER_PRESET_NAMES, newPresetNames)
+ .putString(EQUALIZER_NUMBER_OF_PRESETS, Integer.toString(++currentPresets))
+ .commit();
+
+ }
+
+ // set the small speakers preset as the default
+ int idx = findInList(smallSpeakers, presetNames);
+ if (idx >= 0) {
+ speakerPrefs.edit()
+ .putBoolean(DEVICE_AUDIOFX_GLOBAL_ENABLE, true)
+ .putString(DEVICE_AUDIOFX_EQ_PRESET, String.valueOf(idx))
+ .commit();
+ }
+ }
+
+ private String getNonLocalizedString(int res) {
+ Configuration config = new Configuration(mContext.getResources().getConfiguration());
+ config.setLocale(Locale.ROOT);
+ return mContext.createConfigurationContext(config).getString(res);
+ }
+}
+
diff --git a/src/org/cyanogenmod/audiofx/audiofx/service/SessionManager.java b/src/org/cyanogenmod/audiofx/audiofx/service/SessionManager.java
new file mode 100644
index 0000000..c744686
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/service/SessionManager.java
@@ -0,0 +1,422 @@
+package com.cyngn.audiofx.service;
+
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_BASS_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_BASS_STRENGTH;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_EQ_PRESET_LEVELS;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_GLOBAL_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_MAXXVOLUME_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_REVERB_PRESET;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_TREBLE_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_TREBLE_STRENGTH;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_VIRTUALIZER_ENABLE;
+import static com.cyngn.audiofx.Constants.DEVICE_AUDIOFX_VIRTUALIZER_STRENGTH;
+import static com.cyngn.audiofx.Constants.DEVICE_DEFAULT_GLOBAL_ENABLE;
+import static com.cyngn.audiofx.activity.MasterConfigControl.getDeviceIdentifierString;
+import static com.cyngn.audiofx.service.AudioFxService.ALL_CHANGED;
+import static com.cyngn.audiofx.service.AudioFxService.BASS_BOOST_CHANGED;
+import static com.cyngn.audiofx.service.AudioFxService.ENABLE_REVERB;
+import static com.cyngn.audiofx.service.AudioFxService.EQ_CHANGED;
+import static com.cyngn.audiofx.service.AudioFxService.REVERB_CHANGED;
+import static com.cyngn.audiofx.service.AudioFxService.TREBLE_BOOST_CHANGED;
+import static com.cyngn.audiofx.service.AudioFxService.VIRTUALIZER_CHANGED;
+import static com.cyngn.audiofx.service.AudioFxService.VOLUME_BOOST_CHANGED;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.audiofx.PresetReverb;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.cyngn.audiofx.backends.EffectSet;
+import com.cyngn.audiofx.backends.EffectsFactory;
+import com.cyngn.audiofx.eq.EqUtils;
+
+import cyanogenmod.media.AudioSessionInfo;
+import cyanogenmod.media.CMAudioManager;
+
+class SessionManager implements AudioOutputChangeListener.AudioOutputChangedCallback {
+
+ private static final String TAG = AudioFxService.TAG;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final DevicePreferenceManager mDevicePrefs;
+ private final CMAudioManager mCMAudio;
+
+ /**
+ * All fields ending with L should be locked on {@link #mAudioSessionsL}
+ */
+ private final SparseArray<EffectSet> mAudioSessionsL = new SparseArray<EffectSet>();
+
+
+ private AudioDeviceInfo mCurrentDevice = null;
+
+ // audio priority handler messages
+ private static final int MSG_UPDATE_DSP = 100;
+ private static final int MSG_ADD_SESSION = 101;
+ private static final int MSG_REMOVE_SESSION = 102;
+ private static final int MSG_UPDATE_FOR_SESSION = 103;
+ private static final int MSG_UPDATE_EQ_OVERRIDE = 104;
+
+ public SessionManager(Context context, Handler handler, DevicePreferenceManager devicePrefs,
+ AudioDeviceInfo outputDevice) {
+ mContext = context;
+ mCMAudio = CMAudioManager.getInstance(context);
+ mDevicePrefs = devicePrefs;
+ mCurrentDevice = outputDevice;
+ mHandler = new Handler(handler.getLooper(), new AudioServiceHandler());
+ }
+
+ public void onDestroy() {
+ synchronized (mAudioSessionsL) {
+ mHandler.removeCallbacksAndMessages(null);
+ mHandler.getLooper().quit();
+ }
+ }
+
+ public void update(int flags) {
+ if (mHandler == null) {
+ return;
+ }
+ synchronized (mAudioSessionsL) {
+ mHandler.obtainMessage(MSG_UPDATE_DSP, flags, 0).sendToTarget();
+ }
+ }
+
+ public void setOverrideLevels(short band, float level) {
+ synchronized (mAudioSessionsL) {
+ mHandler.obtainMessage(MSG_UPDATE_EQ_OVERRIDE, band, 0, level).sendToTarget();
+ }
+ }
+
+ /**
+ * Callback which listens for session updates from AudioPolicyManager. This is a
+ * feature added by CM which notifies when sessions are created or
+ * destroyed on a particular stream. This is independent of the standard control
+ * intents and should not conflict with them. This feature may not be available on
+ * all devices.
+ *
+ * Default logic is to do our best to only attach to music streams. We never attach
+ * to low-latency streams automatically, and we don't attach to mono streams by default
+ * either since these are usually notifications/ringtones/etc.
+ */
+ public boolean shouldHandleSession(AudioSessionInfo info) {
+ final boolean music = info.getStream() == AudioManager.STREAM_MUSIC;
+ final boolean offloaded = (info.getFlags() < 0)
+ || (info.getFlags() & AudioFxService.AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) > 0
+ || (info.getFlags() & AudioFxService.AUDIO_OUTPUT_FLAG_DEEP_BUFFER) > 0;
+ final boolean stereo = info.getChannelMask() < 0 || info.getChannelMask() > 1;
+
+ return music && offloaded && stereo && info.getSessionId() > 0;
+ }
+
+ public void addSession(AudioSessionInfo info) {
+ synchronized (mAudioSessionsL) {
+ // Never auto-attach is someone is recording! We don't want to interfere
+ // with any sort of loopback mechanisms.
+ final boolean recording = AudioSystem.isSourceActive(0) || AudioSystem.isSourceActive(6);
+ if (recording) {
+ Log.w(TAG, "Recording in progress, not performing auto-attach!");
+ return;
+ }
+ if (shouldHandleSession(info) &&
+ !mHandler.hasMessages(MSG_ADD_SESSION, info.getSessionId())) {
+ mHandler.removeMessages(MSG_REMOVE_SESSION, info.getSessionId());
+ mHandler.obtainMessage(MSG_ADD_SESSION, info.getSessionId()).sendToTarget();
+ if (DEBUG) Log.i(TAG, "New audio session: " + info.toString());
+ }
+ }
+ }
+
+ public void removeSession(AudioSessionInfo info) {
+ synchronized (mAudioSessionsL) {
+ if (shouldHandleSession(info) &&
+ !mHandler.hasMessages(MSG_REMOVE_SESSION, info.getSessionId())) {
+ int sid = info.getSessionId();
+ final EffectSet effects = mAudioSessionsL.get(sid);
+ if (effects != null) {
+ effects.setMarkedForDeath(true);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_REMOVE_SESSION, sid),
+ effects.getReleaseDelay());
+ if (DEBUG) Log.i(TAG, "Audio session queued for removal: " + info.toString());
+ }
+ }
+ }
+ }
+
+ public String getCurrentDeviceIdentifier() {
+ return getDeviceIdentifierString(mCurrentDevice);
+ }
+
+ public boolean hasActiveSessions() {
+ synchronized (mAudioSessionsL) {
+ return mAudioSessionsL.size() > 0;
+ }
+ }
+
+ EffectSet getEffectForSession(int sessionId) {
+ synchronized (mAudioSessionsL) {
+ return mAudioSessionsL.get(sessionId);
+ }
+ }
+
+ /**
+ * Update the backend with our changed preferences.
+ *
+ * This must only be called from the HandlerThread!
+ */
+ private void updateBackendLocked(int flags, EffectSet session) {
+ if (Looper.getMainLooper().equals(Looper.myLooper())) {
+ throw new IllegalStateException("updateBackend must not be called on the UI thread!");
+ }
+
+ final SharedPreferences prefs = mDevicePrefs.getCurrentDevicePrefs();
+
+ if (DEBUG) {
+ Log.i(TAG, "+++ updateBackend() called with flags=[" + flags + "], session=[" + session + "]");
+ }
+
+ if (session == null) {
+ return;
+ }
+
+ final boolean globalEnabled = prefs.getBoolean(DEVICE_AUDIOFX_GLOBAL_ENABLE,
+ DEVICE_DEFAULT_GLOBAL_ENABLE);
+
+ if ((flags & ALL_CHANGED) > 0) {
+ // global bypass toggle
+ session.setGlobalEnabled(globalEnabled);
+ }
+
+ if (globalEnabled) {
+ // tell the backend it's time to party
+ if (!session.beginUpdate()) {
+ Log.e(TAG, "session " + session + " failed to beginUpdate()");
+ return;
+ }
+
+ // equalizer
+ try {
+ if ((flags & EQ_CHANGED) > 0) {
+ // equalizer is always on unless bypassed
+ session.enableEqualizer(true);
+ String savedPreset = prefs.getString(DEVICE_AUDIOFX_EQ_PRESET_LEVELS, null);
+ if (savedPreset != null) {
+ session.setEqualizerLevelsDecibels(EqUtils.stringBandsToFloats(savedPreset));
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error enabling equalizer!", e);
+ }
+
+ // bass
+ try {
+ if ((flags & BASS_BOOST_CHANGED) > 0 && session.hasBassBoost()) {
+ boolean enable = prefs.getBoolean(DEVICE_AUDIOFX_BASS_ENABLE, false);
+ session.enableBassBoost(enable);
+ session.setBassBoostStrength(Short.valueOf(prefs
+ .getString(DEVICE_AUDIOFX_BASS_STRENGTH, "0")));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error enabling bass boost!", e);
+ }
+
+ // reverb
+ if (ENABLE_REVERB) {
+ try {
+ if ((flags & REVERB_CHANGED) > 0 && session.hasReverb()) {
+ short preset = Short.decode(prefs.getString(DEVICE_AUDIOFX_REVERB_PRESET,
+ String.valueOf(PresetReverb.PRESET_NONE)));
+ session.enableReverb(preset > 0);
+ session.setReverbPreset(preset);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error enabling reverb preset", e);
+ }
+ }
+
+ // virtualizer
+ try {
+ if ((flags & VIRTUALIZER_CHANGED) > 0 && session.hasVirtualizer()) {
+ boolean enable = prefs.getBoolean(DEVICE_AUDIOFX_VIRTUALIZER_ENABLE, false);
+ session.enableVirtualizer(enable);
+ session.setVirtualizerStrength(Short.valueOf(prefs.getString(
+ DEVICE_AUDIOFX_VIRTUALIZER_STRENGTH, "0")));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error enabling virtualizer!");
+ }
+
+ // extended audio effects
+ try {
+ if ((flags & TREBLE_BOOST_CHANGED) > 0 && session.hasTrebleBoost()) {
+ // treble
+ boolean enable = prefs.getBoolean(DEVICE_AUDIOFX_TREBLE_ENABLE, false);
+ session.enableTrebleBoost(enable);
+ session.setTrebleBoostStrength(Short.valueOf(
+ prefs.getString(DEVICE_AUDIOFX_TREBLE_STRENGTH, "0")));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error enabling treble boost!", e);
+ }
+
+ try {
+ if ((flags & VOLUME_BOOST_CHANGED) > 0 && session.hasVolumeBoost()) {
+ // maxx volume
+ session.enableVolumeBoost(prefs.getBoolean(DEVICE_AUDIOFX_MAXXVOLUME_ENABLE, false));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error enabling volume boost!", e);
+ }
+
+ // mic drop
+ if (!session.commitUpdate()) {
+ Log.e(TAG, "session " + session + " failed to commitUpdate()");
+ }
+ }
+ if (DEBUG) {
+ Log.i(TAG, "--- updateBackend() called with flags=[" + flags + "], session=[" + session + "]");
+ }
+ }
+
+ private class AudioServiceHandler implements Handler.Callback {
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ synchronized (mAudioSessionsL) {
+ EffectSet session = null;
+ Integer sessionId = 0;
+ int flags = 0;
+
+ switch (msg.what) {
+ case MSG_ADD_SESSION:
+ /**
+ * msg.obj = sessionId
+ */
+ sessionId = (Integer) msg.obj;
+ if (sessionId == null || sessionId <= 0) {
+ break;
+ }
+
+ session = mAudioSessionsL.get(sessionId);
+ if (session == null) {
+ try {
+ session = new EffectsFactory()
+ .createEffectSet(mContext, sessionId, mCurrentDevice);
+ } catch (Exception e) {
+ Log.e(TAG, "couldn't create effects for session id: " + sessionId, e);
+ break;
+ }
+ mAudioSessionsL.put(sessionId, session);
+ if (DEBUG) Log.w(TAG, "added new EffectSet for sessionId=" + sessionId);
+ updateBackendLocked(ALL_CHANGED, session);
+ } else {
+ session.setMarkedForDeath(false);
+ }
+ break;
+
+ case MSG_REMOVE_SESSION:
+ /**
+ * msg.obj = sessionId
+ */
+ sessionId = (Integer) msg.obj;
+ if (sessionId == null || sessionId <= 0) {
+ break;
+ }
+
+ session = mAudioSessionsL.get(sessionId);
+ if (session != null && session.isMarkedForDeath()) {
+ mHandler.removeMessages(MSG_UPDATE_FOR_SESSION, sessionId);
+ session.release();
+ mAudioSessionsL.remove(sessionId);
+ if (DEBUG) Log.w(TAG, "removed and released sessionId=" + sessionId);
+ }
+
+ break;
+
+ case MSG_UPDATE_DSP:
+ /**
+ * msg.arg1 = update what flags
+ */
+ flags = msg.arg1;
+
+ final String mode = getCurrentDeviceIdentifier();
+ if (DEBUG) Log.i(TAG, "Updating to configuration: " + mode);
+
+ final int N = mAudioSessionsL.size();
+ for (int i = 0; i < N; i++) {
+ sessionId = mAudioSessionsL.keyAt(i);
+ mHandler.obtainMessage(MSG_UPDATE_FOR_SESSION, flags, 0, sessionId).sendToTarget();
+ }
+ break;
+
+ case MSG_UPDATE_FOR_SESSION:
+ /**
+ * msg.arg1 = update what flags
+ * msg.arg2 = unused
+ * msg.obj = session id integer (for consistency)
+ */
+ sessionId = (Integer) msg.obj;
+ flags = msg.arg1;
+
+ if (sessionId == null || sessionId <= 0) {
+ break;
+ }
+
+ String device = getCurrentDeviceIdentifier();
+ if (DEBUG) Log.i(TAG, "updating DSP for sessionId=" + sessionId +
+ ", device=" + device + " flags=" + flags);
+
+ session = mAudioSessionsL.get(sessionId);
+ if (session != null) {
+ updateBackendLocked(flags, session);
+ }
+ break;
+
+ case MSG_UPDATE_EQ_OVERRIDE:
+ for (int i = 0; i < mAudioSessionsL.size(); i++) {
+ sessionId = mAudioSessionsL.keyAt(i);
+ session = mAudioSessionsL.get(sessionId);
+ if (session != null) {
+ session.setEqualizerBandLevel((short) msg.arg1, (float) msg.obj);
+ }
+ }
+ break;
+ }
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Updates the backend and notifies the frontend when the output device has changed
+ */
+ @Override
+ public void onAudioOutputChanged(boolean firstChange, AudioDeviceInfo outputDevice) {
+ synchronized (mAudioSessionsL) {
+ if (mCurrentDevice == null ||
+ (outputDevice != null && mCurrentDevice.getId() != outputDevice.getId())) {
+ mCurrentDevice = outputDevice;
+ }
+
+ EffectSet session = null;
+
+ // Update all the sessions for this output which are moving
+ final int N = mAudioSessionsL.size();
+ for (int i = 0; i < N; i++) {
+ session = mAudioSessionsL.valueAt(i);
+
+ session.setDevice(mCurrentDevice);
+ updateBackendLocked(ALL_CHANGED, session);
+ }
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/stats/AppState.java b/src/org/cyanogenmod/audiofx/audiofx/stats/AppState.java
new file mode 100644
index 0000000..5417bcd
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/stats/AppState.java
@@ -0,0 +1,41 @@
+package com.cyngn.audiofx.stats;
+
+import com.cyanogen.ambient.analytics.Event;
+import com.cyngn.audiofx.Preset;
+import com.cyngn.audiofx.activity.MasterConfigControl;
+import com.cyngn.audiofx.eq.EqUtils;
+import com.cyngn.audiofx.knobs.KnobCommander;
+
+/**
+ * Created by roman on 9/29/15.
+ */
+public class AppState {
+ public static void appendState(MasterConfigControl control,
+ KnobCommander knobs, Event.Builder builder) {
+ // what's the current output device?
+ builder.addField("state_current_device", control.getCurrentDeviceIdentifier());
+
+ // what preset? if custom, what name/values?
+ builder.addField("state_preset_name", control.getEqualizerManager().getCurrentPreset().getName());
+
+ if (control.getEqualizerManager().getCurrentPreset() instanceof Preset.CustomPreset) {
+ builder.addField("state_custom_preset_values",
+ EqUtils.floatLevelsToString(control.getEqualizerManager().getCurrentPreset().getLevels()));
+ }
+
+ // knob states
+ if (control.hasMaxxAudio()) {
+ builder.addField("state_maxx_volume", control.getMaxxVolumeEnabled());
+ }
+
+ if (knobs.hasBassBoost()) {
+ builder.addField("state_knob_bass", knobs.getBassStrength());
+ }
+ if (knobs.hasTreble()) {
+ builder.addField("state_knob_treble", knobs.getTrebleStrength());
+ }
+ if (knobs.hasVirtualizer()) {
+ builder.addField("state_knob_virtualizer", knobs.getVirtualizerStrength());
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/stats/UserSession.java b/src/org/cyanogenmod/audiofx/audiofx/stats/UserSession.java
new file mode 100644
index 0000000..bb8787c
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/stats/UserSession.java
@@ -0,0 +1,202 @@
+package com.cyngn.audiofx.stats;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.cyanogen.ambient.analytics.Event;
+import com.cyngn.audiofx.Preset;
+import com.cyngn.audiofx.knobs.KnobCommander;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class UserSession implements Parcelable {
+
+ private static final String SOURCE_NONE = "none";
+
+ private static UserSession sSession;
+ public static final UserSession getInstance() {
+ return sSession;
+ }
+
+ private String mSource;
+ private int mDevicesChanged;
+ private int mEnabledDisabledToggles;
+ private int mPresetsSelected;
+ private int mPresetsCreated;
+ private int mPresetsRemoved;
+ private int mPresetsRenamed;
+ private int mMaxxVolumeToggled;
+ private int mTrebleKnobAdjusted;
+ private int mBassKnobAdjusted;
+ private int mVirtualizerKnobAdjusted;
+
+ public UserSession(String incomingPackageSource) {
+ if (incomingPackageSource == null) {
+ mSource = SOURCE_NONE;
+ } else {
+ mSource = incomingPackageSource;
+ }
+ sSession = this;
+ }
+
+ public void deviceChanged() {
+ mDevicesChanged++;
+ }
+
+ public void deviceEnabledDisabled() {
+ mEnabledDisabledToggles++;
+ }
+
+ public void presetSelected() {
+ mPresetsSelected++;
+ }
+
+ public void presetRemoved() {
+ mPresetsRemoved++;
+ }
+
+ public void presetRenamed() {
+ mPresetsRenamed++;
+ }
+
+ public void presetCreated() {
+ mPresetsCreated++;
+ }
+
+ public void maxxVolumeToggled() {
+ mMaxxVolumeToggled++;
+ }
+
+ public void knobOptionsAdjusted(int knob) {
+ switch (knob) {
+ case KnobCommander.KNOB_BASS:
+ mBassKnobAdjusted++;
+ break;
+ case KnobCommander.KNOB_TREBLE:
+ mTrebleKnobAdjusted++;
+ break;
+ case KnobCommander.KNOB_VIRTUALIZER:
+ mVirtualizerKnobAdjusted++;
+ break;
+ }
+ }
+
+ public void append(Event.Builder builder) {
+ builder.addField("session_source", mSource);
+ if (mDevicesChanged > 0)
+ builder.addField("session_devices_changed_count", mDevicesChanged);
+ if (mEnabledDisabledToggles > 0)
+ builder.addField("session_devices_enabled_disabled_count", mEnabledDisabledToggles);
+ if (mPresetsSelected > 0)
+ builder.addField("session_presets_changed_count", mPresetsSelected);
+ if (mPresetsCreated > 0)
+ builder.addField("session_presets_created_count", mPresetsCreated);
+ if (mPresetsRemoved > 0)
+ builder.addField("session_presets_removed_count", mPresetsRemoved);
+ if (mPresetsRenamed > 0)
+ builder.addField("session_presets_renamed_count", mPresetsRenamed);
+ if (mMaxxVolumeToggled > 0)
+ builder.addField("session_maxx_volume_toggled", mMaxxVolumeToggled);
+ if (mBassKnobAdjusted > 0)
+ builder.addField("session_knobs_bass_adjusted_count", mBassKnobAdjusted);
+ if (mVirtualizerKnobAdjusted > 0)
+ builder.addField("session_knobs_virtualizer_adjusted_count", mVirtualizerKnobAdjusted);
+ if (mTrebleKnobAdjusted > 0)
+ builder.addField("session_knobs_treble_adjusted_count", mTrebleKnobAdjusted);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder(getClass().getName() + "[");
+ if (mSource != null) {
+ s.append("mSource=").append(mSource).append(", ");
+ }
+ if (mDevicesChanged > 0) {
+ s.append("mDevicesChanged=").append(mDevicesChanged).append(", ");
+ }
+ if (mEnabledDisabledToggles > 0) {
+ s.append("mEnabledDisabledToggles=").append(mEnabledDisabledToggles).append(", ");
+ }
+ if (mPresetsSelected > 0) {
+ s.append("mPresetsSelected=").append(mPresetsSelected).append(", ");
+ }
+ if (mPresetsCreated > 0) {
+ s.append("mPresetsCreated=").append(mPresetsCreated).append(", ");
+ }
+ if (mPresetsRemoved > 0) {
+ s.append("mPresetsRemoved=").append(mPresetsRemoved).append(", ");
+ }
+ if (mPresetsRenamed > 0) {
+ s.append("mPresetsRenamed=").append(mPresetsRenamed).append(", ");
+ }
+ if (mMaxxVolumeToggled > 0) {
+ s.append("mMaxxVolumeToggled=").append(mMaxxVolumeToggled).append(", ");
+ }
+ if (mBassKnobAdjusted > 0) {
+ s.append("mBassKnobAdjusted=").append(mBassKnobAdjusted).append(", ");
+ }
+ if (mVirtualizerKnobAdjusted > 0) {
+ s.append("mVirtualizerKnobAdjusted=").append(mVirtualizerKnobAdjusted).append(", ");
+ }
+ if (mTrebleKnobAdjusted > 0) {
+ s.append("mTrebleKnobAdjusted=").append(mTrebleKnobAdjusted).append(", ");
+ }
+ if (s.charAt(s.length() - 2) == ',') {
+ s.delete(s.length() - 2, s.length());
+ }
+ s.append("]");
+
+ return s.toString();
+ }
+
+ public static final Creator<UserSession> CREATOR = new Creator<UserSession>() {
+ @Override
+ public UserSession createFromParcel(Parcel in) {
+ return new UserSession(in);
+ }
+
+ @Override
+ public UserSession[] newArray(int size) {
+ return new UserSession[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ protected UserSession(Parcel in) {
+ mSource = in.readString();
+ mDevicesChanged = in.readInt();
+ mEnabledDisabledToggles = in.readInt();
+ mPresetsSelected = in.readInt();
+ mPresetsCreated = in.readInt();
+ mPresetsRemoved = in.readInt();
+ mPresetsRenamed = in.readInt();
+ mBassKnobAdjusted = in.readInt();
+ mVirtualizerKnobAdjusted = in.readInt();
+ mTrebleKnobAdjusted = in.readInt();
+ mMaxxVolumeToggled = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mSource);
+ dest.writeInt(mDevicesChanged);
+ dest.writeInt(mEnabledDisabledToggles);
+ dest.writeInt(mPresetsSelected);
+ dest.writeInt(mPresetsCreated);
+ dest.writeInt(mPresetsRemoved);
+ dest.writeInt(mPresetsRenamed);
+ dest.writeInt(mBassKnobAdjusted);
+ dest.writeInt(mVirtualizerKnobAdjusted);
+ dest.writeInt(mTrebleKnobAdjusted);
+ dest.writeInt(mMaxxVolumeToggled);
+ }
+
+ private static class State {
+ private String mOutputDevice;
+ private Preset mPreset;
+ private String mKnobsOpts;
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/viewpagerindicator/CirclePageIndicator.java b/src/org/cyanogenmod/audiofx/audiofx/viewpagerindicator/CirclePageIndicator.java
new file mode 100644
index 0000000..b4b4a6b
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/viewpagerindicator/CirclePageIndicator.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2011 Patrik Akerfeldt
+ * Copyright (C) 2011 Jake Wharton
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.cyngn.audiofx.viewpagerindicator;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.drawable.Drawable;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewConfigurationCompat;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import com.cyngn.audiofx.R;
+
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+import static android.widget.LinearLayout.HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
+
+/**
+ * Draws circles (one for each view). The current view position is filled and
+ * others are only stroked.
+ */
+public class CirclePageIndicator extends View implements PageIndicator {
+ private static final int INVALID_POINTER = -1;
+
+ private float mRadius;
+ private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG);
+ private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG);
+ private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG);
+ private ViewPager mViewPager;
+ private ViewPager.OnPageChangeListener mListener;
+ private int mCurrentPage;
+ private int mSnapPage;
+ private float mPageOffset;
+ private int mScrollState;
+ private int mOrientation;
+ private boolean mCentered;
+ private boolean mSnap;
+
+ private int mTouchSlop;
+ private float mLastMotionX = -1;
+ private int mActivePointerId = INVALID_POINTER;
+ private boolean mIsDragging;
+
+
+ public CirclePageIndicator(Context context) {
+ this(context, null);
+ }
+
+ public CirclePageIndicator(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.vpiCirclePageIndicatorStyle);
+ }
+
+ public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (isInEditMode()) return;
+
+ //Load defaults from resources
+ final Resources res = getResources();
+ final int defaultPageColor = res.getColor(R.color.default_circle_indicator_page_color);
+ final int defaultFillColor = res.getColor(R.color.default_circle_indicator_fill_color);
+ final int defaultOrientation = res.getInteger(R.integer.default_circle_indicator_orientation);
+ final int defaultStrokeColor = res.getColor(R.color.default_circle_indicator_stroke_color);
+ final float defaultStrokeWidth = res.getDimension(R.dimen.default_circle_indicator_stroke_width);
+ final float defaultRadius = res.getDimension(R.dimen.default_circle_indicator_radius);
+ final boolean defaultCentered = res.getBoolean(R.bool.default_circle_indicator_centered);
+ final boolean defaultSnap = res.getBoolean(R.bool.default_circle_indicator_snap);
+
+ //Retrieve styles attributes
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePageIndicator, defStyle, 0);
+
+ mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered);
+ mOrientation = a.getInt(R.styleable.CirclePageIndicator_android_orientation, defaultOrientation);
+ mPaintPageFill.setStyle(Style.FILL);
+ mPaintPageFill.setColor(a.getColor(R.styleable.CirclePageIndicator_pageColor, defaultPageColor));
+ mPaintStroke.setStyle(Style.STROKE);
+ mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor));
+ mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth));
+ mPaintFill.setStyle(Style.FILL);
+ mPaintFill.setColor(a.getColor(R.styleable.CirclePageIndicator_fillColor, defaultFillColor));
+ mRadius = a.getDimension(R.styleable.CirclePageIndicator_radius, defaultRadius);
+ mSnap = a.getBoolean(R.styleable.CirclePageIndicator_snap, defaultSnap);
+
+ Drawable background = a.getDrawable(R.styleable.CirclePageIndicator_android_background);
+ if (background != null) {
+ setBackgroundDrawable(background);
+ }
+
+ a.recycle();
+
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
+ }
+
+
+ public void setCentered(boolean centered) {
+ mCentered = centered;
+ invalidate();
+ }
+
+ public boolean isCentered() {
+ return mCentered;
+ }
+
+ public void setPageColor(int pageColor) {
+ mPaintPageFill.setColor(pageColor);
+ invalidate();
+ }
+
+ public int getPageColor() {
+ return mPaintPageFill.getColor();
+ }
+
+ public void setFillColor(int fillColor) {
+ mPaintFill.setColor(fillColor);
+ invalidate();
+ }
+
+ public int getFillColor() {
+ return mPaintFill.getColor();
+ }
+
+ public void setOrientation(int orientation) {
+ switch (orientation) {
+ case HORIZONTAL:
+ case VERTICAL:
+ mOrientation = orientation;
+ requestLayout();
+ break;
+
+ default:
+ throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL.");
+ }
+ }
+
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ public void setStrokeColor(int strokeColor) {
+ mPaintStroke.setColor(strokeColor);
+ invalidate();
+ }
+
+ public int getStrokeColor() {
+ return mPaintStroke.getColor();
+ }
+
+ public void setStrokeWidth(float strokeWidth) {
+ mPaintStroke.setStrokeWidth(strokeWidth);
+ invalidate();
+ }
+
+ public float getStrokeWidth() {
+ return mPaintStroke.getStrokeWidth();
+ }
+
+ public void setRadius(float radius) {
+ mRadius = radius;
+ invalidate();
+ }
+
+ public float getRadius() {
+ return mRadius;
+ }
+
+ public void setSnap(boolean snap) {
+ mSnap = snap;
+ invalidate();
+ }
+
+ public boolean isSnap() {
+ return mSnap;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mViewPager == null) {
+ return;
+ }
+ final int count = mViewPager.getAdapter().getCount();
+ if (count == 0) {
+ return;
+ }
+
+ if (mCurrentPage >= count) {
+ setCurrentItem(count - 1);
+ return;
+ }
+
+ int longSize;
+ int longPaddingBefore;
+ int longPaddingAfter;
+ int shortPaddingBefore;
+ if (mOrientation == HORIZONTAL) {
+ longSize = getWidth();
+ longPaddingBefore = getPaddingLeft();
+ longPaddingAfter = getPaddingRight();
+ shortPaddingBefore = getPaddingTop();
+ } else {
+ longSize = getHeight();
+ longPaddingBefore = getPaddingTop();
+ longPaddingAfter = getPaddingBottom();
+ shortPaddingBefore = getPaddingLeft();
+ }
+
+ final float threeRadius = mRadius * 3;
+ final float shortOffset = shortPaddingBefore + mRadius;
+ float longOffset = longPaddingBefore + mRadius;
+ if (mCentered) {
+ longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f);
+ }
+
+ float dX;
+ float dY;
+
+ float pageFillRadius = mRadius;
+ if (mPaintStroke.getStrokeWidth() > 0) {
+ pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
+ }
+
+ //Draw stroked circles
+ for (int iLoop = 0; iLoop < count; iLoop++) {
+ float drawLong = longOffset + (iLoop * threeRadius);
+ if (mOrientation == HORIZONTAL) {
+ dX = drawLong;
+ dY = shortOffset;
+ } else {
+ dX = shortOffset;
+ dY = drawLong;
+ }
+ // Only paint fill if not completely transparent
+ if (mPaintPageFill.getAlpha() > 0) {
+ canvas.drawCircle(dX, dY, (float) (pageFillRadius/1.5f), mPaintPageFill);
+ }
+
+ // Only paint stroke if a stroke width was non-zero
+ if (pageFillRadius != mRadius) {
+ canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
+ }
+ }
+
+ //Draw the filled circle according to the current scroll
+ float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
+ if (!mSnap) {
+ cx += mPageOffset * threeRadius;
+ }
+ if (mOrientation == HORIZONTAL) {
+ dX = longOffset + cx;
+ dY = shortOffset;
+ } else {
+ dX = shortOffset;
+ dY = longOffset + cx;
+ }
+ canvas.drawCircle(dX, dY, mRadius, mPaintFill);
+ }
+
+ public boolean onTouchEvent(android.view.MotionEvent ev) {
+ if (super.onTouchEvent(ev)) {
+ return true;
+ }
+ if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
+ return false;
+ }
+
+ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+ mLastMotionX = ev.getX();
+ break;
+
+ case MotionEvent.ACTION_MOVE: {
+ final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+ final float x = MotionEventCompat.getX(ev, activePointerIndex);
+ final float deltaX = x - mLastMotionX;
+
+ if (!mIsDragging) {
+ if (Math.abs(deltaX) > mTouchSlop) {
+ mIsDragging = true;
+ }
+ }
+
+ if (mIsDragging) {
+ mLastMotionX = x;
+ if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
+ mViewPager.fakeDragBy(deltaX);
+ }
+ }
+
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ if (!mIsDragging) {
+ final int count = mViewPager.getAdapter().getCount();
+ final int width = getWidth();
+ final float halfWidth = width / 2f;
+ final float sixthWidth = width / 6f;
+
+ if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
+ if (action != MotionEvent.ACTION_CANCEL) {
+ mViewPager.setCurrentItem(mCurrentPage - 1);
+ }
+ return true;
+ } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
+ if (action != MotionEvent.ACTION_CANCEL) {
+ mViewPager.setCurrentItem(mCurrentPage + 1);
+ }
+ return true;
+ }
+ }
+
+ mIsDragging = false;
+ mActivePointerId = INVALID_POINTER;
+ if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
+ break;
+
+ case MotionEventCompat.ACTION_POINTER_DOWN: {
+ final int index = MotionEventCompat.getActionIndex(ev);
+ mLastMotionX = MotionEventCompat.getX(ev, index);
+ mActivePointerId = MotionEventCompat.getPointerId(ev, index);
+ break;
+ }
+
+ case MotionEventCompat.ACTION_POINTER_UP:
+ final int pointerIndex = MotionEventCompat.getActionIndex(ev);
+ final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
+ if (pointerId == mActivePointerId) {
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
+ }
+ mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
+ break;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void setViewPager(ViewPager view) {
+ if (mViewPager == view) {
+ return;
+ }
+ if (mViewPager != null) {
+ mViewPager.setOnPageChangeListener(null);
+ }
+ if (view.getAdapter() == null) {
+ throw new IllegalStateException("ViewPager does not have adapter instance.");
+ }
+ mViewPager = view;
+ mViewPager.setOnPageChangeListener(this);
+ invalidate();
+ }
+
+ @Override
+ public void setViewPager(ViewPager view, int initialPosition) {
+ setViewPager(view);
+ setCurrentItem(initialPosition);
+ }
+
+ @Override
+ public void setCurrentItem(int item) {
+ if (mViewPager == null) {
+ throw new IllegalStateException("ViewPager has not been bound.");
+ }
+ mViewPager.setCurrentItem(item);
+ mCurrentPage = item;
+ invalidate();
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ invalidate();
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mScrollState = state;
+
+ if (mListener != null) {
+ mListener.onPageScrollStateChanged(state);
+ }
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ mCurrentPage = position;
+ mPageOffset = positionOffset;
+ invalidate();
+
+ if (mListener != null) {
+ mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
+ mCurrentPage = position;
+ mSnapPage = position;
+ invalidate();
+ }
+
+ if (mListener != null) {
+ mListener.onPageSelected(position);
+ }
+ }
+
+ @Override
+ public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
+ mListener = listener;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View#onMeasure(int, int)
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mOrientation == HORIZONTAL) {
+ setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
+ } else {
+ setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
+ }
+ }
+
+ /**
+ * Determines the width of this view
+ *
+ * @param measureSpec
+ * A measureSpec packed into an int
+ * @return The width of the view, honoring constraints from measureSpec
+ */
+ private int measureLong(int measureSpec) {
+ int result;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
+ //We were told how big to be
+ result = specSize;
+ } else {
+ //Calculate the width according the views count
+ final int count = mViewPager.getAdapter().getCount();
+ result = (int)(getPaddingLeft() + getPaddingRight()
+ + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
+ //Respect AT_MOST value if that was what is called for by measureSpec
+ if (specMode == MeasureSpec.AT_MOST) {
+ result = Math.min(result, specSize);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Determines the height of this view
+ *
+ * @param measureSpec
+ * A measureSpec packed into an int
+ * @return The height of the view, honoring constraints from measureSpec
+ */
+ private int measureShort(int measureSpec) {
+ int result;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ if (specMode == MeasureSpec.EXACTLY) {
+ //We were told how big to be
+ result = specSize;
+ } else {
+ //Measure the height
+ result = (int)(2 * mRadius + getPaddingTop() + getPaddingBottom() + 1);
+ //Respect AT_MOST value if that was what is called for by measureSpec
+ if (specMode == MeasureSpec.AT_MOST) {
+ result = Math.min(result, specSize);
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/org/cyanogenmod/audiofx/audiofx/viewpagerindicator/PageIndicator.java b/src/org/cyanogenmod/audiofx/audiofx/viewpagerindicator/PageIndicator.java
new file mode 100644
index 0000000..131d53f
--- /dev/null
+++ b/src/org/cyanogenmod/audiofx/audiofx/viewpagerindicator/PageIndicator.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 Patrik Akerfeldt
+ * Copyright (C) 2011 Jake Wharton
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyngn.audiofx.viewpagerindicator;
+
+import android.support.v4.view.ViewPager;
+
+/**
+ * A PageIndicator is responsible to show an visual indicator on the total views
+ * number and the current visible view.
+ */
+public interface PageIndicator extends ViewPager.OnPageChangeListener {
+ /**
+ * Bind the indicator to a ViewPager.
+ *
+ * @param view
+ */
+ void setViewPager(ViewPager view);
+
+ /**
+ * Bind the indicator to a ViewPager.
+ *
+ * @param view
+ * @param initialPosition
+ */
+ void setViewPager(ViewPager view, int initialPosition);
+
+ /**
+ * <p>Set the current page of both the ViewPager and indicator.</p>
+ *
+ * <p>This <strong>must</strong> be used if you need to set the page before
+ * the views are drawn on screen (e.g., default start page).</p>
+ *
+ * @param item
+ */
+ void setCurrentItem(int item);
+
+ /**
+ * Set a page change listener which will receive forwarded events.
+ *
+ * @param listener
+ */
+ void setOnPageChangeListener(ViewPager.OnPageChangeListener listener);
+
+ /**
+ * Notify the indicator that the fragment list has changed.
+ */
+ void notifyDataSetChanged();
+}
diff --git a/src/org/cyanogenmod/audiofx/widget/Biquad.java b/src/org/cyanogenmod/audiofx/audiofx/widget/Biquad.java
index f90153d..4486dfe 100644
--- a/src/org/cyanogenmod/audiofx/widget/Biquad.java
+++ b/src/org/cyanogenmod/audiofx/audiofx/widget/Biquad.java
@@ -1,14 +1,14 @@
-package org.cyanogenmod.audiofx.widget;
+package com.cyngn.audiofx.widget;
/**
* Evaluate transfer functions of biquad filters in direct form 1.
*
* @author alankila
*/
-class Biquad {
+public class Biquad {
private Complex mB0, mB1, mB2, mA0, mA1, mA2;
- protected void setHighShelf(double centerFrequency, double samplingFrequency,
+ public void setHighShelf(double centerFrequency, double samplingFrequency,
double dbGain, double slope) {
double w0 = 2 * Math.PI * centerFrequency / samplingFrequency;
double a = Math.pow(10, dbGain/40);
@@ -22,7 +22,7 @@ class Biquad {
mA2 = new Complex((a+1) - (a-1) *Math.cos(w0) - 2*Math.sqrt(a)*alpha, 0);
}
- protected Complex evaluateTransfer(Complex z) {
+ public Complex evaluateTransfer(Complex z) {
Complex zSquared = z.mul(z);
Complex nom = mB0.add(mB1.div(z)).add(mB2.div(zSquared));
Complex den = mA0.add(mA1.div(z)).add(mA2.div(zSquared));
diff --git a/src/org/cyanogenmod/audiofx/widget/Complex.java b/src/org/cyanogenmod/audiofx/audiofx/widget/Complex.java
index b4691a3..dbbaaf2 100644
--- a/src/org/cyanogenmod/audiofx/widget/Complex.java
+++ b/src/org/cyanogenmod/audiofx/audiofx/widget/Complex.java
@@ -1,14 +1,14 @@
-package org.cyanogenmod.audiofx.widget;
+package com.cyngn.audiofx.widget;
/**
* Java support for complex numbers.
*
* @author alankila
*/
-class Complex {
+public class Complex {
private final double mReal, mIm;
- protected Complex(double real, double im) {
+ public Complex(double real, double im) {
mReal = real;
mIm = im;
}
@@ -18,7 +18,7 @@ class Complex {
*
* @return length
*/
- protected double rho() {
+ public double rho() {
return Math.sqrt(mReal * mReal + mIm * mIm);
}
@@ -27,7 +27,7 @@ class Complex {
*
* @return angle in radians
*/
- protected double theta() {
+ public double theta() {
return Math.atan2(mIm, mReal);
}
@@ -36,7 +36,7 @@ class Complex {
*
* @return conjugate
*/
- protected Complex con() {
+ public Complex con() {
return new Complex(mReal, -mIm);
}
@@ -46,7 +46,7 @@ class Complex {
* @param other
* @return sum
*/
- protected Complex add(Complex other) {
+ public Complex add(Complex other) {
return new Complex(mReal + other.mReal, mIm + other.mIm);
}
@@ -56,7 +56,7 @@ class Complex {
* @param other
* @return multiplication result
*/
- protected Complex mul(Complex other) {
+ public Complex mul(Complex other) {
return new Complex(mReal * other.mReal - mIm * other.mIm,
mReal * other.mIm + mIm * other.mReal);
}
@@ -67,7 +67,7 @@ class Complex {
* @param a
* @return multiplication result
*/
- protected Complex mul(double a) {
+ public Complex mul(double a) {
return new Complex(mReal * a, mIm * a);
}
@@ -77,7 +77,7 @@ class Complex {
* @param other
* @return division result
*/
- protected Complex div(Complex other) {
+ public Complex div(Complex other) {
double lengthSquared = other.mReal * other.mReal + other.mIm * other.mIm;
return mul(other.con()).div(lengthSquared);
}
@@ -88,7 +88,7 @@ class Complex {
* @param a
* @return division result
*/
- protected Complex div(double a) {
+ public Complex div(double a) {
return new Complex(mReal / a, mIm / a);
}
}
diff --git a/src/org/cyanogenmod/audiofx/widget/EqualizerSurface.java b/src/org/cyanogenmod/audiofx/audiofx/widget/EqualizerSurface.java
index ff44c43..2b3c637 100644
--- a/src/org/cyanogenmod/audiofx/widget/EqualizerSurface.java
+++ b/src/org/cyanogenmod/audiofx/audiofx/widget/EqualizerSurface.java
@@ -17,10 +17,9 @@
* - Modified extensively by cyanogen for multi-band support
*/
-package org.cyanogenmod.audiofx.widget;
+package com.cyngn.audiofx.widget;
import android.animation.Animator;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
@@ -38,10 +37,8 @@ import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
-import org.cyanogenmod.audiofx.R;
+import com.cyngn.audiofx.R;
import java.util.Arrays;
@@ -77,7 +74,7 @@ public class EqualizerSurface extends SurfaceView implements ValueAnimator.Anima
setWillNotDraw(false);
mWhite = new Paint();
- mWhite.setColor(getResources().getColor(R.color.white));
+ mWhite.setColor(getResources().getColor(R.color.color_grey));
mWhite.setStyle(Style.STROKE);
mWhite.setTextSize(mTextSize = context.getResources().getDimensionPixelSize(R.dimen.eq_label_text_size));
mWhite.setTypeface(Typeface.DEFAULT_BOLD);
@@ -386,13 +383,17 @@ public class EqualizerSurface extends SurfaceView implements ValueAnimator.Anima
for (float dB = mMinDB + 3; dB <= mMaxDB - 3; dB += 3) {
float y = projectY(dB) * mHeight;
// canvas.drawLine(0, y, mWidth - 1, y, mGridLines);
- canvas.drawText(String.format("%+d", (int)dB), 1, (y - 1), mWhite);
+// canvas.drawText(String.format("%+d", (int)dB), 1, (y - 1), mWhite);
}
for (int i = 0; i < mNumBands; i ++) {
float freq = mCenterFreqs[i];
float x = projectX(freq) * mWidth;
+
float y = projectY(mLevels[i]) * (mHeight);
+
+ Log.i("eqsurface", i + " level: " + mLevels[i] + ", y: " + y);
+
String frequencyText = String.format(freq < 1000 ? "%.0f" : "%.0fk",
freq < 1000 ? freq : freq / 1000);
diff --git a/src/org/cyanogenmod/audiofx/widget/InterceptableLinearLayout.java b/src/org/cyanogenmod/audiofx/audiofx/widget/InterceptableLinearLayout.java
index c8d838a..ae0e8a0 100644
--- a/src/org/cyanogenmod/audiofx/widget/InterceptableLinearLayout.java
+++ b/src/org/cyanogenmod/audiofx/audiofx/widget/InterceptableLinearLayout.java
@@ -26,7 +26,7 @@
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.cyanogenmod.audiofx.widget;
+package com.cyngn.audiofx.widget;
import android.content.Context;
import android.util.AttributeSet;
@@ -53,6 +53,11 @@ public class InterceptableLinearLayout extends LinearLayout {
return mIntercept;
}
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
public void setInterception(boolean intercept) {
mIntercept = intercept;
}
diff --git a/src/org/cyanogenmod/audiofx/widget/Gallery.java b/src/org/cyanogenmod/audiofx/widget/Gallery.java
deleted file mode 100644
index 8bb5f2a..0000000
--- a/src/org/cyanogenmod/audiofx/widget/Gallery.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (c) 2013, The Linux Foundation. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- * * Neither the name of The Linux Foundation nor the names of its
- * contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
- * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
- * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.cyanogenmod.audiofx.widget;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.TextView;
-
-import org.cyanogenmod.audiofx.R;
-
-public class Gallery extends android.widget.Gallery {
- public interface OnItemSelectedListener {
- public void onItemSelected(int position);
- }
-
- private boolean mEnabled = false;
-
- private int mHighlightColor;
- private int mLowlightColor;
- private int mDisabledColor;
-
- private TextView mLastView = null;
- private OnItemSelectedListener mOnItemSelectedListener = null;
-
- public Gallery(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- Resources res = getResources();
- mHighlightColor = res.getColor(R.color.highlight);
- mLowlightColor = res.getColor(R.color.grey);
- mDisabledColor = res.getColor(R.color.disabled_gallery);
-
- setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- TextView tv = (TextView) view;
- if (tv != null) {
- tv.setTextColor(mEnabled ? mHighlightColor : mDisabledColor);
- }
- if (mLastView != null && mLastView != tv) {
- mLastView.setTextColor(mEnabled ? mLowlightColor : mDisabledColor);
- }
- mLastView = tv;
- if (mEnabled && mOnItemSelectedListener != null) {
- mOnItemSelectedListener.onItemSelected(position);
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- }
- });
- }
-
- public Gallery(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public Gallery(Context context) {
- this(context, null);
- }
-
- public void setOnItemSelectedListener(OnItemSelectedListener listener) {
- mOnItemSelectedListener = listener;
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- final int count = getChildCount();
- for (int i = 0; i < count; ++i) {
- final View view = getChildAt(i);
- if (view instanceof TextView) {
- ((TextView) view).setTextColor(enabled ? mLowlightColor : mDisabledColor);
- }
- }
-
- if (enabled) {
- final TextView tv = (TextView) getSelectedView();
- if (tv != null) {
- tv.setTextColor(mHighlightColor);
- }
- }
- }
-
- @Override
- public boolean onDown(MotionEvent e) {
- return mEnabled ? super.onDown(e) : false;
- }
-}
diff --git a/src/org/cyanogenmod/audiofx/widget/Knob.java b/src/org/cyanogenmod/audiofx/widget/Knob.java
deleted file mode 100644
index ec3312d..0000000
--- a/src/org/cyanogenmod/audiofx/widget/Knob.java
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- * Copyright (c) 2013, The Linux Foundation. All rights reserved.
- * Copyright (c) 2014, The CyanogenMod Project. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- * * Neither the name of The Linux Foundation nor the names of its
- * contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
- * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
- * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.cyanogenmod.audiofx.widget;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.lang.Math;
-
-import org.cyanogenmod.audiofx.R;
-
-public class Knob extends FrameLayout {
- private static final String TAG = Knob.class.getSimpleName();
-
- private static final int STROKE_WIDTH = 35;
- private static final float TEXT_SIZE = 0.20f;
- private static final float TEXT_PADDING = 0.31f;
- private static final float LABEL_PADDING = 0.02f;
- private static final float LABEL_SIZE = 0.08f;
- private static final float LABEL_WIDTH = 0.45f;
- private static final float INDICATOR_RADIUS = 0.38f;
- private ValueAnimator mAnimator;
-
- public interface OnKnobChangeListener {
- void onValueChanged(Knob knob, int value, boolean fromUser);
-
- boolean onSwitchChanged(Knob knob, boolean on);
-
- void onAnimationFinished(boolean endValue);
- }
-
- private OnKnobChangeListener mOnKnobChangeListener = null;
-
- private float mOriginalProgress = 0.0f;
- private float mProgress = 0.0f;
- private int mMax = 100;
- private boolean mOn = false;
- private boolean mEnabled = false;
-
- private int mHighlightColor;
- private int mLowlightColor;
- private int mDisabledColor;
-
- private final Paint mPaint;
-
- private final TextView mLabelTV;
- private final TextView mProgressTV;
-
- private final ImageView mKnobOn;
-
- private float mLastX;
- private float mLastY;
- private boolean mMoved;
-
- private int mWidth = 0;
- private int mIndicatorWidth = 0;
-
- private RectF mRectF;
-
- public Knob(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Knob, 0, 0);
-
- String label;
- int foreground;
- try {
- label = a.getString(R.styleable.Knob_label);
- foreground = a.getResourceId(R.styleable.Knob_foreground, R.drawable.knob);
- } finally {
- a.recycle();
- }
-
- LayoutInflater li = (LayoutInflater)
- context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- li.inflate(R.layout.knob, this, true);
-
- Resources res = getResources();
- mHighlightColor = res.getColor(R.color.highlight);
- mLowlightColor = res.getColor(R.color.lowlight);
- mDisabledColor = res.getColor(R.color.disabled_knob);
-
- ImageView fg = (ImageView) findViewById(R.id.knob_foreground);
- fg.setImageResource(R.drawable.knob);
-
- mLabelTV = (TextView) findViewById(R.id.knob_label);
- mLabelTV.setText(label);
- mProgressTV = (TextView) findViewById(R.id.knob_value);
-
- mKnobOn = (ImageView) findViewById(R.id.knob_toggle_on);
-
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setColor(mHighlightColor);
- mPaint.setStrokeWidth(65);
- mPaint.setStrokeCap(Paint.Cap.BUTT);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setShadowLayer(2, 1, -2, getResources().getColor(R.color.black));
-
- setWillNotDraw(false);
- }
-
- public Knob(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public Knob(Context context) {
- this(context, null);
- }
-
- public void setOnKnobChangeListener(OnKnobChangeListener l) {
- mOnKnobChangeListener = l;
- }
-
- public void setValue(int value) {
- if (mMax != 0) {
- mOriginalProgress = ((float) value) / mMax;
- if (mOriginalProgress > 100) {
- mOriginalProgress = 100;
- } else if (mOriginalProgress < 0) {
- mOriginalProgress = 0;
- }
-
- setProgress(mOriginalProgress);
- }
- }
-
- public void setProgress(float progress) {
- setProgress(progress, false);
- }
-
- public void updateProgressText(boolean showText, float progress) {
- if (showText) {
- mProgressTV.setText((int) (progress * 100) + "%");
- } else {
- mProgressTV.setText("--%");
- }
- }
-
- public void setProgress(float progress, boolean fromUser) {
- if (progress > 1.0f) {
- progress = 1.0f;
- } else if (progress < 0.0f) {
- progress = 0.0f;
- }
-
- mProgress = progress;
-
- updateProgressText(mEnabled && mOn, progress);
-
- invalidate();
-
- if (mOnKnobChangeListener != null) {
- mOnKnobChangeListener.onValueChanged(this, (int) (progress * mMax), fromUser);
- }
- }
-
- public void setMax(int max) {
- mMax = max;
- }
-
- public float getProgress() {
- return mProgress;
- }
-
- private void drawIndicator() {
- float r = mWidth * INDICATOR_RADIUS;
-// ImageView view = mEnabled ? mKnobOn : mKnobOff;
- mKnobOn.setTranslationX((float) Math.sin(mProgress * 2 * Math.PI) * r - mIndicatorWidth / 2);
- mKnobOn.setTranslationY((float) -Math.cos(mProgress * 2 * Math.PI) * r - mIndicatorWidth / 2);
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
-
- mLabelTV.setTextColor(mEnabled ? mHighlightColor : mDisabledColor);
- mProgressTV.setTextColor(mEnabled ? mHighlightColor : mDisabledColor);
- mPaint.setColor(mEnabled ? mHighlightColor : mDisabledColor);
-
-// if (enabled) {
-// mOn = true;
-// }
- if (enabled) {
- setOn(mOn, false);
- }
-// updateProgressText(mEnabled && mOn, mOriginalProgress);
-// } else {
-// }
-// invalidate();
- }
-
- public void setOn(final boolean on, boolean animate) {
- mOn = on;
-
- if (mAnimator != null) {
- mAnimator.cancel();
- }
- if (mOriginalProgress > 1) {
- mOriginalProgress = 1;
- }
- if (animate) {
- if (on) {
- mAnimator = ValueAnimator.ofFloat(mProgress, mOriginalProgress);
- } else {
- mAnimator = ValueAnimator.ofFloat(mProgress, 0f);
- }
- mAnimator.setDuration(500);
- mAnimator.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimator = null;
- updateProgressText(mOn && mEnabled, mOriginalProgress);
- if (mOnKnobChangeListener != null) {
- mOnKnobChangeListener.onAnimationFinished(on);
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
-
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
-
- }
- });
- mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float progress = (Float) animation.getAnimatedValue();
- if (progress < 0) {
- progress = 0;
- } else if (progress > 1) {
- progress = 1;
- }
- mProgress = progress;
- if (mOnKnobChangeListener != null) {
- mOnKnobChangeListener.onValueChanged(Knob.this, (int) (progress * mMax), true);
- }
- updateProgressText(true, mProgress);
- invalidate();
- }
- });
- mAnimator.start();
- } else {
- updateProgressText(mEnabled && mOn, mOriginalProgress);
-
- // make progress correct value
- mProgress = mOn ? mOriginalProgress : 0f;
-
- invalidate();
-
- if (mOnKnobChangeListener != null) {
- mOnKnobChangeListener.onAnimationFinished(on);
- }
- }
-
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- drawIndicator();
- if (mEnabled) {
- canvas.drawArc(mRectF, -90, mProgress * 360, false, mPaint);
- }
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldW, int oldH) {
- int size = w > h ? h : w;
- mWidth = size;
- mIndicatorWidth = mKnobOn.getWidth();
-
- int diff;
- if (w > h) {
- diff = (w - h) / 2;
- mRectF = new RectF(STROKE_WIDTH + diff, STROKE_WIDTH,
- w - STROKE_WIDTH - diff, h - STROKE_WIDTH);
- } else {
- diff = (h - w) / 2;
- mRectF = new RectF(STROKE_WIDTH, STROKE_WIDTH + diff,
- w - STROKE_WIDTH, h - STROKE_WIDTH - diff);
- }
-
- mProgressTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, size * TEXT_SIZE);
- mProgressTV.setPadding(0, (int) (size * TEXT_PADDING), 0, 0);
- mProgressTV.setVisibility(View.VISIBLE);
- mLabelTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, size * LABEL_SIZE);
- mLabelTV.setPadding(0, (int) (size * LABEL_PADDING), 0, 0);
- mLabelTV.setLayoutParams(new LinearLayout.LayoutParams((int) (w * LABEL_WIDTH),
- LayoutParams.WRAP_CONTENT));
- mLabelTV.setVisibility(View.VISIBLE);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- if (mOn) {
- mLastX = event.getX();
- mLastY = event.getY();
- getParent().requestDisallowInterceptTouchEvent(true);
- }
- break;
- case MotionEvent.ACTION_MOVE:
- if (mOn) {
- float x = event.getX();
- float y = event.getY();
- float center = mWidth / 2;
- if (mMoved || (x - center) * (x - center) + (y - center) * (y - center)
- > center * center / 4) {
- float delta = getDelta(x, y);
- mOriginalProgress = mProgress + delta / 360;
- if (mOriginalProgress < 0) {
- mOriginalProgress = 0;
- } else if (mOriginalProgress > 100) {
- mOriginalProgress = 100;
- }
- setProgress(mOriginalProgress, true);
- mMoved = true;
- }
- mLastX = x;
- mLastY = y;
- }
- break;
- case MotionEvent.ACTION_UP:
- if (!mMoved) {
- if (mOnKnobChangeListener == null
- || mOnKnobChangeListener.onSwitchChanged(this, !mOn)) {
- if (mEnabled) {
- setOn(!mOn, true);
- invalidate();
- }
- }
- }
- mMoved = false;
- break;
- default:
- break;
- }
- return true;
- }
-
- private float getDelta(float x, float y) {
- float angle = angle(x, y);
- float oldAngle = angle(mLastX, mLastY);
- float delta = angle - oldAngle;
- if (delta >= 180.0f) {
- delta = -oldAngle;
- } else if (delta <= -180.0f) {
- delta = 360 - oldAngle;
- }
- return delta;
- }
-
- private float angle(float x, float y) {
- float center = mWidth / 2.0f;
- x -= center;
- y -= center;
-
- if (x == 0.0f) {
- if (y > 0.0f) {
- return 180.0f;
- } else {
- return 0.0f;
- }
- }
-
- float angle = (float) (Math.atan(y / x) / Math.PI * 180.0);
- if (x > 0.0f) {
- angle += 90;
- } else {
- angle += 270;
- }
- return angle;
- }
-}