diff --git a/DeviceSettings/AndroidManifest.xml b/DeviceSettings/AndroidManifest.xml index c4e3c20d..15b9d43c 100644 --- a/DeviceSettings/AndroidManifest.xml +++ b/DeviceSettings/AndroidManifest.xml @@ -80,6 +80,17 @@ + + + + + + + + diff --git a/DeviceSettings/res/drawable/ic_speaker_cleaner_icon.xml b/DeviceSettings/res/drawable/ic_speaker_cleaner_icon.xml new file mode 100644 index 00000000..4ef1f6d9 --- /dev/null +++ b/DeviceSettings/res/drawable/ic_speaker_cleaner_icon.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/DeviceSettings/res/raw/clear_speaker_sound.mp3 b/DeviceSettings/res/raw/clear_speaker_sound.mp3 new file mode 100644 index 00000000..79093f33 Binary files /dev/null and b/DeviceSettings/res/raw/clear_speaker_sound.mp3 differ diff --git a/DeviceSettings/res/values/strings.xml b/DeviceSettings/res/values/strings.xml index 744e1663..78528107 100644 --- a/DeviceSettings/res/values/strings.xml +++ b/DeviceSettings/res/values/strings.xml @@ -55,6 +55,14 @@ Hue Grayscale Display + + Speaker + + + Clear speaker + Play a 30-second audio to clear the speaker + Run this feature once or twice if you find that your speaker is lightly blocked by dust. Set media volume to maximum.\n\nIf the speaker is blocked heavily, run this feature 2-5 times while shaking your device with the speaker facing downwards. + Select Preset Default diff --git a/DeviceSettings/res/xml/clear_speaker_settings.xml b/DeviceSettings/res/xml/clear_speaker_settings.xml new file mode 100644 index 00000000..eb7ce777 --- /dev/null +++ b/DeviceSettings/res/xml/clear_speaker_settings.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/DeviceSettings/res/xml/preferences_xiaomi_parts.xml b/DeviceSettings/res/xml/preferences_xiaomi_parts.xml index d748a815..5b023279 100644 --- a/DeviceSettings/res/xml/preferences_xiaomi_parts.xml +++ b/DeviceSettings/res/xml/preferences_xiaomi_parts.xml @@ -159,4 +159,20 @@ android:title="@string/hall_title" android:icon="@drawable/ic_flip" /> + + + + + + + + diff --git a/DeviceSettings/src/org/lineageos/settings/device/DeviceSettings.java b/DeviceSettings/src/org/lineageos/settings/device/DeviceSettings.java index e996d196..2eb3f7f7 100644 --- a/DeviceSettings/src/org/lineageos/settings/device/DeviceSettings.java +++ b/DeviceSettings/src/org/lineageos/settings/device/DeviceSettings.java @@ -24,6 +24,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import org.lineageos.settings.device.kcal.KCalSettingsActivity; +import org.lineageos.settings.device.speaker.ClearSpeakerActivity; import org.lineageos.settings.device.preferences.SecureSettingListPreference; import org.lineageos.settings.device.preferences.SecureSettingSwitchPreference; import org.lineageos.settings.device.preferences.VibrationSeekBarPreference; @@ -78,6 +79,9 @@ public class DeviceSettings extends PreferenceFragment implements private static final String DEVICE_JASON_PACKAGE_NAME = "org.lineageos.settings.devicex"; private static final String PREF_DEVICE_JASON = "device_jason"; + + private static final String PREF_CLEAR_SPEAKER = "clear_speaker_settings"; + private Preference mClearSpeakerPref; private SecureSettingSwitchPreference mEnableDirac; private SecureSettingListPreference mHeadsetType; @@ -86,6 +90,14 @@ public class DeviceSettings extends PreferenceFragment implements @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.preferences_xiaomi_parts, rootKey); + + mClearSpeakerPref = (Preference) findPreference(PREF_CLEAR_SPEAKER); + mClearSpeakerPref.setOnPreferenceClickListener(preference -> { + Intent intent = new Intent(getActivity().getApplicationContext(), ClearSpeakerActivity.class); + startActivity(intent); + return true; + }); + // LED Brightness if (FileUtils.fileWritable(NOTIF_LED_PATH)) { diff --git a/DeviceSettings/src/org/lineageos/settings/device/speaker/ClearSpeakerActivity.java b/DeviceSettings/src/org/lineageos/settings/device/speaker/ClearSpeakerActivity.java new file mode 100644 index 00000000..c249dc14 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/settings/device/speaker/ClearSpeakerActivity.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Paranoid Android + * + * 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.lineageos.settings.device.speaker; + +import android.app.Activity; +import android.app.Fragment; +import android.os.Bundle; +import android.view.MenuItem; + +public class ClearSpeakerActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getActionBar().setDisplayHomeAsUpEnabled(true); + + Fragment fragment = getFragmentManager().findFragmentById(android.R.id.content); + ClearSpeakerFragment clearSpeakerFragment; + if (fragment == null) { + clearSpeakerFragment = new ClearSpeakerFragment(); + getFragmentManager().beginTransaction() + .add(android.R.id.content, clearSpeakerFragment) + .commit(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/DeviceSettings/src/org/lineageos/settings/device/speaker/ClearSpeakerFragment.java b/DeviceSettings/src/org/lineageos/settings/device/speaker/ClearSpeakerFragment.java new file mode 100644 index 00000000..3dfd6651 --- /dev/null +++ b/DeviceSettings/src/org/lineageos/settings/device/speaker/ClearSpeakerFragment.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 Paranoid Android + * + * 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.lineageos.settings.device.speaker; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.media.AudioManager; +import android.media.AudioAttributes; +import android.media.MediaPlayer; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import androidx.preference.Preference; +import androidx.preference.PreferenceFragment; +import androidx.preference.SwitchPreference; + +import org.lineageos.settings.device.R; + +import java.io.IOException; + +public class ClearSpeakerFragment extends PreferenceFragment implements + Preference.OnPreferenceChangeListener { + + private static final String TAG = ClearSpeakerFragment.class.getSimpleName(); + + private static final String PREF_CLEAR_SPEAKER = "clear_speaker_pref"; + + private AudioManager mAudioManager; + private Handler mHandler; + private MediaPlayer mMediaPlayer; + private SwitchPreference mClearSpeakerPref; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.clear_speaker_settings); + + mClearSpeakerPref = (SwitchPreference) findPreference(PREF_CLEAR_SPEAKER); + mClearSpeakerPref.setOnPreferenceChangeListener(this); + + mHandler = new Handler(); + mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mClearSpeakerPref) { + boolean value = (Boolean) newValue; + if (value) { + if (startPlaying()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(() -> { + stopPlaying(); + }, 30000); + return true; + } + } + } + return false; + } + + @Override + public void onStop() { + stopPlaying(); + super.onStop(); + } + + public boolean startPlaying() { + mAudioManager.setParameters("status_earpiece_clean=on"); + mMediaPlayer = new MediaPlayer(); + getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mMediaPlayer.setLooping(true); + try { + AssetFileDescriptor file = getResources().openRawResourceFd(R.raw.clear_speaker_sound); + try { + mMediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); + } finally { + file.close(); + } + mClearSpeakerPref.setEnabled(false); + mMediaPlayer.setVolume(1.0f, 1.0f); + mMediaPlayer.prepare(); + mMediaPlayer.start(); + } catch (IOException ioe) { + Log.e(TAG, "Failed to play speaker clean sound!", ioe); + return false; + } + return true; + } + + public void stopPlaying() { + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.reset(); + mMediaPlayer.release(); + } + mAudioManager.setParameters("status_earpiece_clean=off"); + mClearSpeakerPref.setEnabled(true); + mClearSpeakerPref.setChecked(false); + } +}