Allow user to configure BesLoudness

* Audio HAL turns it on every boot but there's no way to configure it
* Because it tends to cause worse sound quality on devices with bad
  speakers, allow user to configure it
* Apply saved setting value every boot
* Configurable from "Settings > Sound > BesLoudness"

Change-Id: Ife31c0eb307d4926d32c7bebe84e0251ec143bf9
diff --git a/BesLoudness/Android.bp b/BesLoudness/Android.bp
new file mode 100644
index 0000000..ca6d23c
--- /dev/null
+++ b/BesLoudness/Android.bp
@@ -0,0 +1,21 @@
+android_app {
+    name: "BesLoudness",
+    defaults: ["SettingsLibDefaults"],
+
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+
+    certificate: "platform",
+    platform_apis: true,
+    system_ext_specific: true,
+
+    static_libs: [
+        "androidx.core_core",
+        "androidx.preference_preference",
+        "org.lineageos.settings.resources",
+    ],
+
+    optimize: {
+        proguard_flags_files: ["proguard.flags"],
+    },
+}
diff --git a/BesLoudness/AndroidManifest.xml b/BesLoudness/AndroidManifest.xml
new file mode 100644
index 0000000..58cda94
--- /dev/null
+++ b/BesLoudness/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.lineageos.settings.besloudness">
+    <!-- NOT org.lineageos.settings.device because that's reserved for device trees -->
+
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+
+    <application
+        android:allowBackup="true"
+        android:label="@string/besloudness"
+        android:persistent="true">
+
+        <receiver
+            android:name="org.lineageos.settings.device.BootCompletedReceiver"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
+
+        <activity
+            android:name="org.lineageos.settings.device.PreferenceActivity"
+            android:exported="false"
+            android:label="@string/besloudness"
+            android:theme="@style/Theme.SubSettingsBase">
+            <intent-filter>
+                <action android:name="com.android.settings.action.IA_SETTINGS" />
+            </intent-filter>
+            <meta-data
+                android:name="com.android.settings.category"
+                android:value="com.android.settings.category.ia.sound" />
+        </activity>
+
+    </application>
+
+</manifest>
diff --git a/BesLoudness/proguard.flags b/BesLoudness/proguard.flags
new file mode 100644
index 0000000..e68ca6b
--- /dev/null
+++ b/BesLoudness/proguard.flags
@@ -0,0 +1,9 @@
+-keep class org.lineageos.settings.device.* {
+  *;
+}
+
+-keepclasseswithmembers class * {
+    public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keep class ** extends androidx.preference.PreferenceFragment
diff --git a/BesLoudness/res/values/strings.xml b/BesLoudness/res/values/strings.xml
new file mode 100644
index 0000000..c780bdc
--- /dev/null
+++ b/BesLoudness/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The LineageOS 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.
+-->
+<resources>
+    <string name="besloudness">BesLoudness</string>
+    <string name="besloudness_info">BesLoudness boosts the speaker volume and enhances the audio quality of your device automatically.</string>
+</resources>
diff --git a/BesLoudness/res/xml/besloudness_panel.xml b/BesLoudness/res/xml/besloudness_panel.xml
new file mode 100644
index 0000000..05f3d52
--- /dev/null
+++ b/BesLoudness/res/xml/besloudness_panel.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The LineageOS 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.
+-->
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/besloudness">
+
+    <com.android.settingslib.widget.MainSwitchPreference
+        android:key="besloudness"
+        android:title="@string/besloudness" />
+
+    <com.android.settingslib.widget.FooterPreference
+        android:key="more_info"
+        android:title="@string/besloudness_info"
+        android:selectable="false" />
+</PreferenceScreen>
diff --git a/BesLoudness/src/org/lineageos/settings/device/BesLoudnessManager.java b/BesLoudness/src/org/lineageos/settings/device/BesLoudnessManager.java
new file mode 100644
index 0000000..199e4ae
--- /dev/null
+++ b/BesLoudness/src/org/lineageos/settings/device/BesLoudnessManager.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023 The LineageOS 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.lineageos.settings.device;
+
+import android.content.SharedPreferences;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.util.Log;
+
+public class BesLoudnessManager {
+    public static final String TAG = "BesLoudness";
+    public static final String SHARED_PREFS_NAME = "besloudness";
+    public static final String KEY_BESLOUDNESS = "besloudness";
+    public static final boolean DEFAULT_VALUE = true; // Matching with MTK Audio HAL
+
+    public static void set(final Context context, Boolean value) {
+        final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, 0);
+        if (value != null) {
+            prefs.edit().putBoolean(KEY_BESLOUDNESS, value).apply();
+        }
+        boolean newValue = prefs.getBoolean(KEY_BESLOUDNESS, DEFAULT_VALUE);
+        final AudioManager amgr = (AudioManager) context.getSystemService("audio");
+        amgr.setParameters("SetBesLoudnessStatus=" + (newValue ? "1" : "0"));
+    }
+
+    public static boolean get(final Context context) {
+        final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, 0);
+        boolean expectedValue = prefs.getBoolean(KEY_BESLOUDNESS, DEFAULT_VALUE);
+        final AudioManager amgr = (AudioManager) context.getSystemService("audio");
+        boolean actualValue = !("GetBesLoudnessStatus=0".equals(
+                amgr.getParameters("GetBesLoudnessStatus")));
+        if (actualValue != expectedValue) {
+            Log.e(TAG, "value mismatch, expected " + expectedValue + ", got " + actualValue);
+        }
+        return actualValue;
+    }
+}
diff --git a/BesLoudness/src/org/lineageos/settings/device/BootCompletedReceiver.java b/BesLoudness/src/org/lineageos/settings/device/BootCompletedReceiver.java
new file mode 100644
index 0000000..ddac333
--- /dev/null
+++ b/BesLoudness/src/org/lineageos/settings/device/BootCompletedReceiver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2023 The LineageOS 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.lineageos.settings.device;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class BootCompletedReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(final Context context, Intent intent) {
+        Log.i(BesLoudnessManager.TAG, "Booting");
+        BesLoudnessManager.set(context, null); // Apply saved preference
+    }
+}
diff --git a/BesLoudness/src/org/lineageos/settings/device/PreferenceActivity.java b/BesLoudness/src/org/lineageos/settings/device/PreferenceActivity.java
new file mode 100644
index 0000000..6fd0cf9
--- /dev/null
+++ b/BesLoudness/src/org/lineageos/settings/device/PreferenceActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2023 The LineageOS 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.lineageos.settings.device;
+
+import android.os.Bundle;
+import android.widget.Switch;
+
+import androidx.preference.PreferenceFragment;
+
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+import com.android.settingslib.widget.MainSwitchPreference;
+import com.android.settingslib.widget.OnMainSwitchChangeListener;
+
+public class PreferenceActivity extends CollapsingToolbarBaseActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getFragmentManager()
+                .beginTransaction()
+                .replace(com.android.settingslib.widget.R.id.content_frame,
+                        new SoundPreferenceFragment())
+                .commit();
+    }
+
+    public static class SoundPreferenceFragment extends PreferenceFragment
+        implements OnMainSwitchChangeListener {
+
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            addPreferencesFromResource(org.lineageos.settings.besloudness.R.xml.besloudness_panel);
+            MainSwitchPreference toggle = (MainSwitchPreference)
+                    findPreference(BesLoudnessManager.KEY_BESLOUDNESS);
+            assert toggle != null;
+            toggle.updateStatus(BesLoudnessManager.get(getContext()));
+            toggle.addOnSwitchChangeListener(this);
+        }
+
+        @Override
+        public void onSwitchChanged(Switch switchView, boolean isChecked) {
+            BesLoudnessManager.set(getContext(), isChecked);
+        }
+    }
+}