diff options
Diffstat (limited to 'src/org/cyanogenmod/audiofx/activity')
5 files changed, 1539 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/audiofx/activity/ActivityMusic.java b/src/org/cyanogenmod/audiofx/activity/ActivityMusic.java new file mode 100644 index 0000000..bd12702 --- /dev/null +++ b/src/org/cyanogenmod/audiofx/activity/ActivityMusic.java @@ -0,0 +1,213 @@ +/* + * 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.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 org.cyanogenmod.audiofx.AudioFxApplication; +import org.cyanogenmod.audiofx.Constants; +import org.cyanogenmod.audiofx.R; +import org.cyanogenmod.audiofx.fragment.AudioFxFragment; +import org.cyanogenmod.audiofx.knobs.KnobCommander; +import org.cyanogenmod.audiofx.service.AudioFxService; +import org.cyanogenmod.audiofx.stats.AppState; +import org.cyanogenmod.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_name); + 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/activity/ControlPanelPicker.java b/src/org/cyanogenmod/audiofx/activity/ControlPanelPicker.java new file mode 100644 index 0000000..03e6b06 --- /dev/null +++ b/src/org/cyanogenmod/audiofx/activity/ControlPanelPicker.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 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.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; +import org.cyanogenmod.audiofx.Compatibility.Service; + +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.media.audiofx.AudioEffect; +import android.os.Bundle; +import android.widget.ListView; +import org.cyanogenmod.audiofx.R; + +import java.util.List; + +/** + * shows a dialog that lets the user switch between control panels + */ +public class ControlPanelPicker extends AlertActivity implements OnClickListener, OnPrepareListViewListener { + + + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String [] cols = new String [] { "_id", "title", "package", "name" }; + MatrixCursor c = new MatrixCursor(cols); + + PackageManager pmgr = getPackageManager(); + Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL); + List<ResolveInfo> ris = pmgr.queryIntentActivities(i, PackageManager.GET_DISABLED_COMPONENTS); + SharedPreferences pref = getSharedPreferences("musicfx", MODE_PRIVATE); + String savedDefPackage = pref.getString("defaultpanelpackage", null); + String savedDefName = pref.getString("defaultpanelname", null); + int cnt = -1; + int defpanelidx = 0; + for (ResolveInfo foo: ris) { + if (foo.activityInfo.name.equals(Compatibility.Redirector.class.getName())) { + continue; + } + CharSequence name = pmgr.getApplicationLabel(foo.activityInfo.applicationInfo); + c.addRow(new Object [] { 0, name, foo.activityInfo.packageName, foo.activityInfo.name }); + cnt += 1; + if (foo.activityInfo.name.equals(savedDefName) && + foo.activityInfo.packageName.equals(savedDefPackage) && + foo.activityInfo.enabled) { + // mark as default in the list + defpanelidx = cnt; + } + } + + final AlertController.AlertParams p = mAlertParams; + p.mCursor = c; + p.mOnClickListener = mItemClickListener; + p.mLabelColumn = "title"; + p.mIsSingleChoice = true; + p.mPositiveButtonText = getString(getOkStringResId()); + p.mPositiveButtonListener = this; + p.mNegativeButtonText = getString(getCancelStringResId()); + p.mOnPrepareListViewListener = this; + p.mTitle = getString(R.string.picker_title); + p.mCheckedItem = defpanelidx; + + 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() { + + public void onClick(DialogInterface dialog, int which) { + // Save the position of most recently clicked item + mAlertParams.mCheckedItem = which; + } + + }; + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + // set new default + Intent updateIntent = new Intent(this, Service.class); + Cursor c = mAlertParams.mCursor; + c.moveToPosition(mAlertParams.mCheckedItem); + updateIntent.putExtra("defPackage", c.getString(2)); + updateIntent.putExtra("defName", c.getString(3)); + startService(updateIntent); + } + } + + @Override + public void onPrepareListView(ListView listView) { + //mAlertParams.mCheckedItem = mDefPanelPos; + } +} diff --git a/src/org/cyanogenmod/audiofx/activity/EqualizerManager.java b/src/org/cyanogenmod/audiofx/activity/EqualizerManager.java new file mode 100644 index 0000000..b040b71 --- /dev/null +++ b/src/org/cyanogenmod/audiofx/activity/EqualizerManager.java @@ -0,0 +1,649 @@ +/* + * 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.activity; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.widget.CompoundButton; + +import org.cyanogenmod.audiofx.Constants; +import org.cyanogenmod.audiofx.Preset; +import org.cyanogenmod.audiofx.R; +import org.cyanogenmod.audiofx.eq.EqUtils; +import org.cyanogenmod.audiofx.service.AudioFxService; +import org.cyanogenmod.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/activity/MasterConfigControl.java b/src/org/cyanogenmod/audiofx/activity/MasterConfigControl.java new file mode 100644 index 0000000..1c5ac0c --- /dev/null +++ b/src/org/cyanogenmod/audiofx/activity/MasterConfigControl.java @@ -0,0 +1,382 @@ +/* + * 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.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 org.cyanogenmod.audiofx.Constants; +import org.cyanogenmod.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(org.cyanogenmod.audiofx.R.string.device_headset); + case TYPE_LINE_ANALOG: + case TYPE_LINE_DIGITAL: + return context.getString(org.cyanogenmod.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(org.cyanogenmod.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/activity/StateCallbacks.java b/src/org/cyanogenmod/audiofx/activity/StateCallbacks.java new file mode 100644 index 0000000..0196b15 --- /dev/null +++ b/src/org/cyanogenmod/audiofx/activity/StateCallbacks.java @@ -0,0 +1,168 @@ +/* + * 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.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); + } + } + } +} |