Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e6e67a60b | ||
|
|
ea228a83fe | ||
|
|
5af4457253 | ||
|
|
e73db17e01 | ||
|
|
48f0028833 | ||
|
|
644e3d1535 | ||
|
|
2f541dfbfc | ||
|
|
10cf15c402 | ||
|
|
0323f7b0e6 | ||
|
|
872e163ae9 | ||
|
|
bdf015820e | ||
|
|
5ff2752c40 | ||
|
|
8cb5e216a8 | ||
|
|
c3061b6b82 | ||
|
|
2407c73977 | ||
|
|
c12f2b7f19 | ||
|
|
fbb6973d74 | ||
|
|
4d300dc97e | ||
|
|
a8f7cfdf39 | ||
|
|
1e0db9dfa6 | ||
|
|
9854381e8a | ||
|
|
3808a4ae68 | ||
|
|
6b582cabb8 | ||
|
|
3c023d81a5 | ||
|
|
b84903b8ae | ||
|
|
8ed00feb62 | ||
|
|
aac59d1bb9 | ||
|
|
e2a08fae39 | ||
|
|
a55e7da413 | ||
|
|
9e64f1bfa4 | ||
|
|
ac49d682fc | ||
|
|
1c85c69ecd | ||
|
|
58fa6a7535 | ||
|
|
4ab4637909 | ||
|
|
a445c78924 | ||
|
|
fcb804fe98 | ||
|
|
3ca0074fef | ||
|
|
a43373294b | ||
|
|
d080d60171 | ||
|
|
ab8b987d02 | ||
|
|
1fdea7dc97 | ||
|
|
99c749a64c | ||
|
|
649443ae12 | ||
|
|
87ec3c12c3 | ||
|
|
beea2617ad | ||
|
|
d3353c6ab3 | ||
|
|
040e50155c | ||
|
|
91d15fa7af | ||
|
|
dbf0dc9428 | ||
|
|
2c14234077 | ||
|
|
f18dcb8e17 | ||
|
|
aae52ffce4 | ||
|
|
f677ebecd8 | ||
|
|
40ef19ab76 | ||
|
|
e400c781e8 | ||
|
|
b0a11aa8b4 | ||
|
|
5bb09cf6fa | ||
|
|
2eaa37731a | ||
|
|
04c945f5b3 | ||
|
|
15e7009f6f | ||
|
|
94a8ea7957 |
@@ -1 +0,0 @@
|
||||
../../build/soong/scripts/system-clang-format
|
||||
13
.clang-format
Normal file
13
.clang-format
Normal file
@@ -0,0 +1,13 @@
|
||||
BasedOnStyle: Google
|
||||
Standard: Cpp11
|
||||
AccessModifierOffset: -2
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: NOLINT:.*
|
||||
DerivePointerAlignment: false
|
||||
IncludeBlocks: Preserve
|
||||
IndentWidth: 4
|
||||
ContinuationIndentWidth: 8
|
||||
PointerAlignment: Left
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
13
.github/workflows/build/action.yml
vendored
Normal file
13
.github/workflows/build/action.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: build
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pip install pre-commit
|
||||
|
||||
- name: Lint
|
||||
shell: bash
|
||||
run: pre-commit run --all
|
||||
38
.github/workflows/gerrit.yml
vendored
Normal file
38
.github/workflows/gerrit.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: gerrit checks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
type: string
|
||||
gerrit-ref:
|
||||
type: string
|
||||
change:
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lineageos-infra/fetch-gerrit-change@main
|
||||
with:
|
||||
gerrit-ref: ${{ inputs.gerrit-ref }}
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
- name: Check if build/action.yml exists
|
||||
id: check
|
||||
run: |
|
||||
if [ -f ./.github/workflows/build/action.yml ]; then
|
||||
echo "run=1" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
if: ${{ steps.check.outputs.run }}
|
||||
uses: ./.github/workflows/build
|
||||
|
||||
- uses: lineageos-infra/gerrit-vote@main
|
||||
if: ${{ steps.check.outputs.run && always() }}
|
||||
with:
|
||||
auth: ${{ secrets.GERRIT_VOTE_CREDS }}
|
||||
change: ${{ inputs.change }}
|
||||
ref: ${{ inputs.ref }}
|
||||
20
.pre-commit-config.yaml
Normal file
20
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v21.1.8
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types_or: [c, c++]
|
||||
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
|
||||
rev: v2.15.0
|
||||
hooks:
|
||||
- id: pretty-format-kotlin
|
||||
args: [--autofix, --ktfmt, --ktfmt-style=kotlinlang]
|
||||
48
DSPVolumeSynchronizer/Android.bp
Normal file
48
DSPVolumeSynchronizer/Android.bp
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Copyright (C) 2024-2025 The LineageOS Project
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
android_app {
|
||||
name: "DSPVolumeSynchronizer",
|
||||
certificate: "platform",
|
||||
srcs: ["src/**/*.java"],
|
||||
platform_apis: true,
|
||||
privileged: true,
|
||||
system_ext_specific: true,
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
"SettingsLib",
|
||||
],
|
||||
|
||||
required: [
|
||||
"privapp-permissions-dspvolume",
|
||||
"config-dspvolume",
|
||||
"preinstalled-packages-platform-dspvolume",
|
||||
],
|
||||
}
|
||||
|
||||
prebuilt_etc {
|
||||
name: "privapp-permissions-dspvolume",
|
||||
relative_install_path: "permissions",
|
||||
src: "privapp-permissions-dspvolume.xml",
|
||||
system_ext_specific: true,
|
||||
filename_from_src: true,
|
||||
}
|
||||
|
||||
prebuilt_etc {
|
||||
name: "config-dspvolume",
|
||||
relative_install_path: "sysconfig",
|
||||
src: "config-dspvolume.xml",
|
||||
system_ext_specific: true,
|
||||
filename_from_src: true,
|
||||
}
|
||||
|
||||
prebuilt_etc {
|
||||
name: "preinstalled-packages-platform-dspvolume",
|
||||
relative_install_path: "sysconfig",
|
||||
src: "preinstalled-packages-platform-dspvolume.xml",
|
||||
system_ext_specific: true,
|
||||
filename_from_src: true,
|
||||
}
|
||||
30
DSPVolumeSynchronizer/AndroidManifest.xml
Normal file
30
DSPVolumeSynchronizer/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.lineageos.dspvolume.xiaomi"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0"
|
||||
android:sharedUserId="android.uid.system">
|
||||
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
|
||||
<application
|
||||
android:label="@string/app_name"
|
||||
android:persistent="true"
|
||||
android:defaultToDeviceProtectedStorage="true"
|
||||
android:directBootAware="true">
|
||||
|
||||
<receiver
|
||||
android:name=".BootReceiver"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
|
||||
<intent-filter android:priority="999">
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".VolumeListenerService" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
5
DSPVolumeSynchronizer/config-dspvolume.xml
Normal file
5
DSPVolumeSynchronizer/config-dspvolume.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<config>
|
||||
<allow-in-power-save package="org.lineageos.dspvolume.xiaomi" />
|
||||
<hidden-api-whitelisted-app package="org.lineageos.dspvolume.xiaomi" />
|
||||
</config>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2025 crDroid Android 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.
|
||||
-->
|
||||
<config>
|
||||
|
||||
<!-- Dolby -->
|
||||
<install-in-user-type package="org.lineageos.dspvolume.xiaomi">
|
||||
<install-in user-type="FULL" />
|
||||
<install-in user-type="PROFILE" />
|
||||
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
|
||||
<do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
|
||||
</install-in-user-type>
|
||||
</config>
|
||||
9
DSPVolumeSynchronizer/privapp-permissions-dspvolume.xml
Normal file
9
DSPVolumeSynchronizer/privapp-permissions-dspvolume.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<permissions>
|
||||
<privapp-permissions package="org.lineageos.dspvolume.xiaomi">
|
||||
<permission name="android.permission.INTERACT_ACROSS_USERS" />
|
||||
<permission name="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
<permission name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<permission name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
</privapp-permissions>
|
||||
</permissions>
|
||||
5
DSPVolumeSynchronizer/res/values/strings.xml
Normal file
5
DSPVolumeSynchronizer/res/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- App Name -->
|
||||
<string name="app_name">DSP Volume Synchronizer</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.lineageos.dspvolume.xiaomi;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
context.startService(new Intent(context, VolumeListenerService.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.lineageos.dspvolume.xiaomi;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public class VolumeListenerReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_TYPE", 0) == AudioManager.STREAM_MUSIC) {
|
||||
AudioManager audioManager = context.getSystemService(AudioManager.class);
|
||||
int current = intent.getIntExtra(
|
||||
"android.media.EXTRA_VOLUME_STREAM_VALUE",
|
||||
0
|
||||
);
|
||||
audioManager.setParameters("volume_change=" + current + ";flags=8");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.lineageos.dspvolume.xiaomi;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class VolumeListenerService extends Service {
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction("android.media.VOLUME_CHANGED_ACTION");
|
||||
registerReceiver(new VolumeListenerReceiver(), intentFilter);
|
||||
|
||||
AudioManager audioManager = getSystemService(AudioManager.class);
|
||||
int current = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
|
||||
audioManager.setParameters("volume_change=" + current + ";flags=8");
|
||||
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 The LineageOS Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.ifaa.aidl.manager
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IHwBinder
|
||||
import android.os.SystemProperties
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import org.ifaa.aidl.manager.IfaaManagerService
|
||||
import org.json.JSONObject
|
||||
import vendor.xiaomi.hardware.mlipay.V1_1.IMlipayService
|
||||
|
||||
class IfaaService : Service() {
|
||||
private var _mlipayService: IMlipayService? = null
|
||||
|
||||
private val mlipayServiceDeathRecipient = IHwBinder.DeathRecipient {
|
||||
Log.i(LOG_TAG, "mlipay service died")
|
||||
_mlipayService = null
|
||||
}
|
||||
|
||||
private val mBinder = object : IfaaManagerService.Stub() {
|
||||
override fun getSupportBIOTypes(): Int {
|
||||
val fpVendor = SystemProperties.get(FP_VENDOR_PROP, "")
|
||||
val isUdfps = SystemProperties.getBoolean(IS_UDFPS_PROP, false)
|
||||
|
||||
val supportedBioMask = when (!invalidFpVendors.contains(fpVendor.lowercase())) {
|
||||
true -> AUTH_TYPE_FINGERPRINT or AUTH_TYPE_IRIS
|
||||
else -> AUTH_TYPE_IRIS
|
||||
}
|
||||
|
||||
val ifaaProp = SystemProperties.getInt(
|
||||
SUPPORTED_BIO_MASK_PROP, 0
|
||||
) and supportedBioMask or when (isUdfps) {
|
||||
true -> AUTH_TYPE_OPTICAL_FINGERPRINT
|
||||
else -> 0
|
||||
}
|
||||
|
||||
return ifaaProp
|
||||
}
|
||||
|
||||
override fun startBIOManager(authType: Int) = when (authType) {
|
||||
AUTH_TYPE_FINGERPRINT -> {
|
||||
val intent = Intent(Settings.ACTION_SECURITY_SETTINGS).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
|
||||
applicationContext.startActivity(intent)
|
||||
|
||||
COMMAND_OK
|
||||
}
|
||||
|
||||
else -> COMMAND_FAIL
|
||||
}
|
||||
|
||||
override fun getDeviceModel() = "${Build.MANUFACTURER}-${Build.DEVICE}"
|
||||
|
||||
override fun processCmd(param: ByteArray) = getMlipayService()?.let { mlipayService ->
|
||||
var receiveBuffer: ByteArray? = null
|
||||
|
||||
val paramByteArray = ArrayList<Byte>().apply {
|
||||
for (byte in param) {
|
||||
add(byte)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val receiveBufferByteArray = mlipayService.invoke_command(
|
||||
paramByteArray, paramByteArray.size
|
||||
)
|
||||
|
||||
receiveBuffer = receiveBufferByteArray.toByteArray()
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "processCmdImpl: mlipay invoke_command failed", e)
|
||||
}
|
||||
|
||||
receiveBuffer
|
||||
}
|
||||
|
||||
override fun getVersion() = 4
|
||||
|
||||
override fun getExtInfo(authType: Int, keyExtInfo: String) = initExtString()
|
||||
|
||||
override fun setExtInfo(authType: Int, keyExtInfo: String, valExtInfo: String) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun getEnabled(bioType: Int) = when (bioType) {
|
||||
AUTH_TYPE_FINGERPRINT -> BIOMETRICS_AVAILABLE
|
||||
else -> SCREEN_LOCK_NONE
|
||||
}
|
||||
|
||||
override fun getIDList(bioType: Int): IntArray {
|
||||
var idList = IntArray(0)
|
||||
|
||||
getMlipayService()?.let { mlipayService ->
|
||||
try {
|
||||
val idListAL = mlipayService.ifaa_get_idlist(bioType)
|
||||
|
||||
idList = idListAL.toIntArray()
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "getIDListImpl: mlipay ifaa_get_idlist failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
return idList
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent) = mBinder
|
||||
|
||||
private fun getMlipayService() = _mlipayService ?: runCatching {
|
||||
IMlipayService.getService(true)
|
||||
}.onSuccess {
|
||||
_mlipayService = it
|
||||
it.linkToDeath(mlipayServiceDeathRecipient, 0)
|
||||
}.getOrNull()
|
||||
|
||||
private fun initExtString(): String {
|
||||
val obj = JSONObject()
|
||||
val keyInfo = JSONObject()
|
||||
|
||||
val xy = SystemProperties.get(UDFPS_LOCATION_X_Y_PROP, "")
|
||||
val wh = SystemProperties.get(UDFPS_SIZE_W_H_PROP, "")
|
||||
|
||||
try {
|
||||
if (!validateVal(xy) || !validateVal(wh)) {
|
||||
Log.e(LOG_TAG, "initExtString: invalidate, xy: $xy, wh: $wh")
|
||||
return ""
|
||||
}
|
||||
|
||||
val split = xy.split(",")
|
||||
val split2 = wh.split(",")
|
||||
|
||||
keyInfo.put("startX", split[0].toInt())
|
||||
keyInfo.put("startY", split[1].toInt())
|
||||
keyInfo.put("width", split2[0].toInt())
|
||||
keyInfo.put("height", split2[1].toInt())
|
||||
keyInfo.put("navConflict", true)
|
||||
|
||||
obj.put("type", 0)
|
||||
obj.put("fullView", keyInfo)
|
||||
|
||||
return obj.toString()
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "initExtString: Exception, xy: $xy, wh: $wh", e)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateVal(str: String) = !"".equals(str, ignoreCase = true) && str.contains(",")
|
||||
|
||||
companion object {
|
||||
private val LOG_TAG = IfaaService::class.simpleName!!
|
||||
|
||||
private const val AUTH_TYPE_NOT_SUPPORT = 0
|
||||
private const val AUTH_TYPE_FINGERPRINT = 1
|
||||
private const val AUTH_TYPE_IRIS = 1.shl(1)
|
||||
private const val AUTH_TYPE_OPTICAL_FINGERPRINT = 1.shl(2)
|
||||
|
||||
private const val BIOMETRICS_AVAILABLE = 1000
|
||||
private const val SCREEN_LOCK_NONE = 1003
|
||||
|
||||
private const val COMMAND_OK = 0
|
||||
private const val COMMAND_FAIL = -1
|
||||
|
||||
private const val SUPPORTED_BIO_MASK_PROP = "persist.vendor.sys.pay.ifaa"
|
||||
private const val FP_VENDOR_PROP = "persist.vendor.sys.fp.vendor"
|
||||
private const val IS_UDFPS_PROP = "ro.hardware.fp.udfps"
|
||||
private const val UDFPS_LOCATION_X_Y_PROP = "persist.vendor.sys.fp.udfps.location.X_Y"
|
||||
private const val UDFPS_SIZE_W_H_PROP = "persist.vendor.sys.fp.udfps.size.width_height"
|
||||
|
||||
private val invalidFpVendors = arrayOf(
|
||||
"",
|
||||
"none",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,4 @@ service vendor.fingerprint-default /vendor/bin/hw/android.hardware.biometrics.fi
|
||||
group system input uhid
|
||||
capabilities SYS_NICE
|
||||
shutdown critical
|
||||
task_profiles ProcessCapacityHigh MaxPerformance
|
||||
|
||||
48
aidl/health/Android.bp
Normal file
48
aidl/health/Android.bp
Normal file
@@ -0,0 +1,48 @@
|
||||
cc_defaults {
|
||||
name: "android.hardware.health-service.xiaomi-defaults",
|
||||
relative_install_path: "hw",
|
||||
vintf_fragments: ["android.hardware.health-service.xiaomi.xml"],
|
||||
vendor: true,
|
||||
recovery_available: true,
|
||||
|
||||
defaults: [
|
||||
"libhealth_aidl_impl_user",
|
||||
],
|
||||
|
||||
include_dirs: [
|
||||
"system/core/healthd",
|
||||
"system/core/healthd/include",
|
||||
"system/core/healthd/include_charger"
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"libhealth_aidl_impl",
|
||||
],
|
||||
|
||||
srcs: [
|
||||
"main.cpp",
|
||||
],
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "android.hardware.health-service.xiaomi",
|
||||
recovery: false,
|
||||
vendor: true,
|
||||
defaults: ["android.hardware.health-service.xiaomi-defaults"],
|
||||
init_rc: ["android.hardware.health-service.xiaomi.rc"],
|
||||
overrides: ["charger","android.hardware.health-service.qti"],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "android.hardware.health-service.xiaomi_recovery",
|
||||
vendor: false,
|
||||
recovery: true,
|
||||
defaults: ["android.hardware.health-service.xiaomi-defaults"],
|
||||
init_rc: ["android.hardware.health-service.xiaomi_recovery.rc"],
|
||||
overrides: ["charger.recovery", "android.hardware.health-service.qti_recovery"],
|
||||
}
|
||||
14
aidl/health/android.hardware.health-service.xiaomi.rc
Normal file
14
aidl/health/android.hardware.health-service.xiaomi.rc
Normal file
@@ -0,0 +1,14 @@
|
||||
service vendor.health-default /vendor/bin/hw/android.hardware.health-service.xiaomi
|
||||
class hal
|
||||
user system
|
||||
group system
|
||||
capabilities WAKE_ALARM BLOCK_SUSPEND
|
||||
file /dev/kmsg w
|
||||
|
||||
service vendor.charger /vendor/bin/hw/android.hardware.health-service.xiaomi --charger
|
||||
class charger
|
||||
seclabel u:r:charger_vendor:s0
|
||||
user system
|
||||
group system wakelock input graphics
|
||||
capabilities SYS_BOOT WAKE_ALARM BLOCK_SUSPEND
|
||||
file /dev/kmsg w
|
||||
12
aidl/health/android.hardware.health-service.xiaomi.xml
Normal file
12
aidl/health/android.hardware.health-service.xiaomi.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<!--
|
||||
Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
SPDX-License-Identifier: BSD-3-Clause-Clear
|
||||
-->
|
||||
|
||||
<manifest version="1.0" type="device">
|
||||
<hal format="aidl">
|
||||
<name>android.hardware.health</name>
|
||||
<version>3</version>
|
||||
<fqname>IHealth/default</fqname>
|
||||
</hal>
|
||||
</manifest>
|
||||
@@ -0,0 +1,7 @@
|
||||
service vendor.health-recovery /system/bin/hw/android.hardware.health-service.xiaomi_recovery
|
||||
class hal
|
||||
seclabel u:r:hal_health_default:s0
|
||||
user system
|
||||
group system
|
||||
capabilities WAKE_ALARM BLOCK_SUSPEND
|
||||
file /dev/kmsg w
|
||||
123
aidl/health/main.cpp
Normal file
123
aidl/health/main.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause-Clear
|
||||
*/
|
||||
|
||||
#define LOG_TAG "android.hardware.health-service.xiaomi"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android/binder_interface_utils.h>
|
||||
#include <health/utils.h>
|
||||
#include <health-impl/ChargerUtils.h>
|
||||
#include <health-impl/Health.h>
|
||||
#include <cutils/klog.h>
|
||||
|
||||
using aidl::android::hardware::health::HalHealthLoop;
|
||||
using aidl::android::hardware::health::Health;
|
||||
|
||||
#if !CHARGER_FORCE_NO_UI
|
||||
using aidl::android::hardware::health::charger::ChargerCallback;
|
||||
using aidl::android::hardware::health::charger::ChargerModeMain;
|
||||
namespace aidl::android::hardware::health {
|
||||
class ChargerCallbackImpl : public ChargerCallback {
|
||||
public:
|
||||
ChargerCallbackImpl(const std::shared_ptr<Health>& service) : ChargerCallback(service) {}
|
||||
bool ChargerEnableSuspend() override { return true; }
|
||||
};
|
||||
} //namespace aidl::android::hardware::health
|
||||
#endif
|
||||
|
||||
namespace aidl::android::hardware::health {
|
||||
static constexpr int kChargeCounterMultiplier = 1000; // mAh to uAh
|
||||
static constexpr int kChargeTimeToFullMultiplier = 60; // mins to secs
|
||||
class HealthImpl : public Health {
|
||||
public:
|
||||
using Health::Health;
|
||||
virtual ~HealthImpl() {}
|
||||
ndk::ScopedAStatus getChargeCounterUah(int32_t* out) override;
|
||||
protected:
|
||||
void UpdateHealthInfo(HealthInfo* health_info) override;
|
||||
};
|
||||
void HealthImpl::UpdateHealthInfo(HealthInfo* health_info) {
|
||||
if (health_info->batteryChargeTimeToFullNowSeconds == 65535) {
|
||||
health_info->batteryChargeTimeToFullNowSeconds = -1;
|
||||
} else {
|
||||
health_info->batteryChargeTimeToFullNowSeconds *= kChargeTimeToFullMultiplier;
|
||||
}
|
||||
health_info->batteryChargeCounterUah *= kChargeCounterMultiplier;
|
||||
}
|
||||
ndk::ScopedAStatus HealthImpl::getChargeCounterUah(int32_t* out) {
|
||||
*out *= kChargeCounterMultiplier;
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
} // namespace aidl::android::hardware::health
|
||||
|
||||
static constexpr const char* gInstanceName = "default";
|
||||
static constexpr std::string_view gChargerArg{"--charger"};
|
||||
|
||||
constexpr char ucsiPSYName[]{"ucsi-source-psy-soc:qcom,pmic_glink:qcom,ucsi1"};
|
||||
|
||||
#define RETRY_COUNT 100
|
||||
|
||||
void qti_healthd_board_init(struct healthd_config *hc)
|
||||
{
|
||||
int fd;
|
||||
unsigned char retries = RETRY_COUNT;
|
||||
int ret = 0;
|
||||
unsigned char buf;
|
||||
|
||||
hc->ignorePowerSupplyNames.push_back(android::String8(ucsiPSYName));
|
||||
retry:
|
||||
if (!retries) {
|
||||
KLOG_ERROR(LOG_TAG, "Cannot open battery/capacity, fd=%d\n", fd);
|
||||
return;
|
||||
}
|
||||
|
||||
fd = open("/sys/class/power_supply/battery/capacity", 0440);
|
||||
if (fd >= 0) {
|
||||
KLOG_INFO(LOG_TAG, "opened battery/capacity after %d retries\n", RETRY_COUNT - retries);
|
||||
while (retries) {
|
||||
ret = read(fd, &buf, 1);
|
||||
if(ret >= 0) {
|
||||
KLOG_INFO(LOG_TAG, "Read Batt Capacity after %d retries ret : %d\n", RETRY_COUNT - retries, ret);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
retries--;
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
KLOG_ERROR(LOG_TAG, "Failed to read Battery Capacity ret=%d\n", ret);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
retries--;
|
||||
usleep(100000);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#ifdef __ANDROID_RECOVERY__
|
||||
android::base::InitLogging(argv, android::base::KernelLogger);
|
||||
#endif
|
||||
auto config = std::make_unique<healthd_config>();
|
||||
qti_healthd_board_init(config.get());
|
||||
::android::hardware::health::InitHealthdConfig(config.get());
|
||||
auto binder = ndk::SharedRefBase::make<aidl::android::hardware::health::HealthImpl>(gInstanceName, std::move(config));
|
||||
|
||||
if (argc >= 2 && argv[1] == gChargerArg) {
|
||||
#if !CHARGER_FORCE_NO_UI
|
||||
KLOG_INFO(LOG_TAG, "Starting charger mode with UI.");
|
||||
auto charger_callback = std::make_shared<aidl::android::hardware::health::ChargerCallbackImpl>(binder);
|
||||
return ChargerModeMain(binder, charger_callback);
|
||||
#endif
|
||||
KLOG_INFO(LOG_TAG, "Starting charger mode without UI.");
|
||||
} else {
|
||||
KLOG_INFO(LOG_TAG, "Starting health HAL.");
|
||||
}
|
||||
|
||||
auto hal_health_loop = std::make_shared<HalHealthLoop>(binder, binder);
|
||||
return hal_health_loop->StartLoop();
|
||||
}
|
||||
@@ -19,6 +19,12 @@ package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
vintf_fragment {
|
||||
name: "android.hardware.sensors.xiaomi-multihal.xml",
|
||||
src: "android.hardware.sensors.xiaomi-multihal.xml",
|
||||
vendor: true,
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "android.hardware.sensors-service.xiaomi-multihal",
|
||||
vendor: true,
|
||||
@@ -33,7 +39,7 @@ cc_binary {
|
||||
"android.hardware.sensors@2.X-shared-utils",
|
||||
],
|
||||
init_rc: ["android.hardware.sensors-service.xiaomi-multihal.rc"],
|
||||
vintf_fragments: ["android.hardware.sensors.xiaomi-multihal.xml"],
|
||||
vintf_fragment_modules: ["android.hardware.sensors.xiaomi-multihal.xml"],
|
||||
shared_libs: [
|
||||
"android.hardware.sensors@2.0-ScopedWakelock",
|
||||
"android.hardware.sensors@2.0",
|
||||
|
||||
@@ -147,9 +147,9 @@ Return<void> HalProxy::getSensorsList_2_1(ISensorsV2_1::getSensorsList_2_1_cb _h
|
||||
Return<void> HalProxy::getSensorsList(ISensorsV2_0::getSensorsList_cb _hidl_cb) {
|
||||
std::vector<V1_0::SensorInfo> sensors;
|
||||
for (const auto& iter : mSensors) {
|
||||
if (iter.second.type != SensorType::HINGE_ANGLE) {
|
||||
sensors.push_back(convertToOldSensorInfo(iter.second));
|
||||
}
|
||||
if (iter.second.type != SensorType::HINGE_ANGLE) {
|
||||
sensors.push_back(convertToOldSensorInfo(iter.second));
|
||||
}
|
||||
}
|
||||
_hidl_cb(sensors);
|
||||
return Void();
|
||||
@@ -612,7 +612,8 @@ void HalProxy::handlePendingWrites() {
|
||||
static_cast<uint32_t>(EventQueueFlagBits::EVENTS_READ),
|
||||
static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS),
|
||||
kPendingWriteTimeoutNs, mEventQueueFlag)) {
|
||||
ALOGE("Dropping %zu events after blockingWrite failed.", numToWrite);
|
||||
ALOGE("Dropping %zu events after blockingWrite failed (is system_server running?).",
|
||||
numToWrite);
|
||||
if (numWakeupEvents > 0) {
|
||||
if (pendingWriteEvents.size() > eventQueueSize) {
|
||||
decrementRefCountAndMaybeReleaseWakelock(
|
||||
@@ -735,8 +736,7 @@ void HalProxy::decrementRefCountAndMaybeReleaseWakelock(size_t delta,
|
||||
if (!mThreadsRun.load()) return;
|
||||
std::lock_guard<std::recursive_mutex> lockGuard(mWakelockMutex);
|
||||
if (delta > mWakelockRefCount) {
|
||||
ALOGE("Decrementing wakelock ref count by %zu when count is %zu",
|
||||
delta, mWakelockRefCount);
|
||||
ALOGE("Decrementing wakelock ref count by %zu when count is %zu", delta, mWakelockRefCount);
|
||||
}
|
||||
if (timeoutStart == -1) timeoutStart = mWakelockTimeoutResetTime;
|
||||
if (mWakelockRefCount == 0 || timeoutStart < mWakelockTimeoutResetTime) return;
|
||||
|
||||
@@ -74,8 +74,7 @@ std::vector<V2_1::Event> HalProxyCallbackBase::processEvents(const std::vector<V
|
||||
}
|
||||
const V2_1::SensorInfo& sensor = mCallback->getSensorInfo(event.sensorHandle);
|
||||
|
||||
if (sensor.type == V2_1::SensorType::PICK_UP_GESTURE
|
||||
&& event.u.scalar != 1) {
|
||||
if (sensor.type == V2_1::SensorType::PICK_UP_GESTURE && event.u.scalar != 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
on boot
|
||||
setprop vendor.sensors.dynamic_sensor_op_timeout_ms 1600
|
||||
|
||||
service vendor.sensors-hal-multihal /vendor/bin/hw/android.hardware.sensors-service.xiaomi-multihal
|
||||
class hal
|
||||
user system
|
||||
|
||||
37
dolby/Android.bp
Normal file
37
dolby/Android.bp
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright (C) 2017-2021 The LineageOS Project
|
||||
// (C) 2023-24 Paranoid Android
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
android_app {
|
||||
name: "XiaomiDolby",
|
||||
|
||||
srcs: ["src/**/*.kt"],
|
||||
resource_dirs: ["res"],
|
||||
certificate: "platform",
|
||||
platform_apis: true,
|
||||
system_ext_specific: true,
|
||||
privileged: true,
|
||||
|
||||
overrides: ["MusicFX", "AudioFX"],
|
||||
static_libs: [
|
||||
"SettingsLib",
|
||||
"SpaLib",
|
||||
"androidx.activity_activity-compose",
|
||||
"androidx.compose.material3_material3",
|
||||
"androidx.compose.runtime_runtime",
|
||||
"androidx.preference_preference",
|
||||
"org.lineageos.settings.resources",
|
||||
],
|
||||
|
||||
required: ["preinstalled-packages-platform-dolby.xml"],
|
||||
}
|
||||
|
||||
prebuilt_etc {
|
||||
name: "preinstalled-packages-platform-dolby.xml",
|
||||
src: "preinstalled-packages-platform-dolby.xml",
|
||||
sub_dir: "sysconfig",
|
||||
system_ext_specific: true,
|
||||
}
|
||||
79
dolby/AndroidManifest.xml
Normal file
79
dolby/AndroidManifest.xml
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023-24 Paranoid Android
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="co.aospa.dolby.xiaomi"
|
||||
android:sharedUserId="android.uid.system">
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:allowBackup="false"
|
||||
android:label="@string/dolby_title"
|
||||
android:persistent="true">
|
||||
|
||||
<receiver
|
||||
android:name=".BootCompletedReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter android:priority="1000">
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".DolbyActivity"
|
||||
android:label="@string/dolby_title"
|
||||
android:theme="@style/Theme.SubSettingsBase.Expressive"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.settings.action.IA_SETTINGS" />
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.media.action.DISPLAY_AUDIO_EFFECT_CONTROL_PANEL" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.CATEGORY_CONTENT_MUSIC" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<meta-data android:name="com.android.settings.category"
|
||||
android:value="com.android.settings.category.ia.sound" />
|
||||
<meta-data android:name="com.android.settings.summary_uri"
|
||||
android:value="content://co.aospa.dolby.xiaomi.summary/dolby" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".geq.EqualizerActivity"
|
||||
android:label="@string/dolby_preset"
|
||||
android:theme="@style/Theme.SubSettingsBase.Expressive"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".DolbyTileService"
|
||||
android:icon="@drawable/ic_dolby_qs"
|
||||
android:label="@string/dolby_title"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name=".SummaryProvider"
|
||||
android:authorities="co.aospa.dolby.xiaomi.summary">
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
26
dolby/preinstalled-packages-platform-dolby.xml
Normal file
26
dolby/preinstalled-packages-platform-dolby.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2024 crDroid Android 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.
|
||||
-->
|
||||
<config>
|
||||
|
||||
<!-- Dolby -->
|
||||
<install-in-user-type package="co.aospa.dolby.xiaomi">
|
||||
<install-in user-type="FULL" />
|
||||
<install-in user-type="PROFILE" />
|
||||
<do-not-install-in user-type="android.os.usertype.profile.CLONE" />
|
||||
<do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
|
||||
</install-in-user-type>
|
||||
</config>
|
||||
8
dolby/res/drawable/ic_dolby.xml
Normal file
8
dolby/res/drawable/ic_dolby.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?android:attr/colorControlNormal">
|
||||
<path android:fillColor="#000000" android:pathData="M1,4.0214C2.2767,4.0743 3.5798,3.9866 4.8252,4.2063C8.8352,4.9133 11.4129,8.3489 11.0507,12.3402C10.7124,16.0695 7.3661,18.9511 3.3484,18.9651C2.5657,18.9678 1.7827,18.9441 1,18.9324L1,4.0214Z" android:strokeColor="#00000000" android:strokeWidth="1" android:fillType="evenOdd"/>
|
||||
<group>
|
||||
<clip-path android:pathData="M12.9332,4l10.0668,0l0,15l-10.0668,0z"/>
|
||||
<path android:fillColor="#000000" android:pathData="M23,4.0924L23,18.8825C19.4973,19.298 16.399,18.6968 14.3366,15.6947C12.5148,13.043 12.4594,10.2265 14.2129,7.5241C16.244,4.394 19.3953,3.7204 23,4.0924" android:strokeColor="#00000000" android:strokeWidth="1" android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
||||
8
dolby/res/drawable/ic_dolby_qs.xml
Normal file
8
dolby/res/drawable/ic_dolby_qs.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#191c1e" android:pathData="M1,4.0214C2.2767,4.0743 3.5798,3.9866 4.8252,4.2063C8.8352,4.9133 11.4129,8.3489 11.0507,12.3402C10.7124,16.0695 7.3661,18.9511 3.3484,18.9651C2.5657,18.9678 1.7827,18.9441 1,18.9324L1,4.0214Z" android:strokeColor="#00000000" android:strokeWidth="1" android:fillType="evenOdd"/>
|
||||
<group>
|
||||
<clip-path android:pathData="M12.9332,4l10.0668,0l0,15l-10.0668,0z"/>
|
||||
<path android:fillColor="#191c1e" android:pathData="M23,4.0924L23,18.8825C19.4973,19.298 16.399,18.6968 14.3366,15.6947C12.5148,13.043 12.4594,10.2265 14.2129,7.5241C16.244,4.394 19.3953,3.7204 23,4.0924" android:strokeColor="#00000000" android:strokeWidth="1" android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
||||
24
dolby/res/drawable/ic_ieq_balanced.xml
Normal file
24
dolby/res/drawable/ic_ieq_balanced.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="192dp"
|
||||
android:viewportWidth="192"
|
||||
android:viewportHeight="192"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 69.584 66.547 L 63.792 70.797 L 38.646 71.697 L 13.5 72.597 L 13.5 89.723 C 13.5 99.143 13.865 111.999 14.313 118.292 L 15.124 129.734 L 178.5 129.734 L 178.5 102.234 L 121.248 102.234 L 99.203 82.234 C 87.078 71.234 76.756 62.248 76.266 62.266 C 75.776 62.282 72.769 64.209 69.584 66.547"
|
||||
android:strokeWidth="1.25"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 69.125 67.065 L 62.875 71.921 L 38.188 71.99 L 13.5 72.06 L 13.504 81.122 C 13.506 86.106 13.876 99.044 14.325 109.872 L 15.141 129.56 L 178.5 129.56 L 178.5 103.31 L 122.126 103.31 L 99.675 82.997 C 87.326 71.826 76.808 62.577 76.299 62.446 C 75.791 62.315 72.563 64.394 69.125 67.065"
|
||||
android:strokeWidth="1.25"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 69.854 67.249 L 63.237 72.187 L 13.311 72.187 L 14.119 84.374 C 14.562 91.077 14.929 104.016 14.932 113.124 L 14.939 129.687 L 178.689 129.687 L 178.689 103.437 L 150.251 103.428 L 121.814 103.418 L 99.143 82.865 L 76.471 62.313 L 69.854 67.249"
|
||||
android:strokeWidth="1.25"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 69.141 67.266 L 63.221 71.609 L 45.888 71.609 C 36.354 71.609 25.238 71.956 21.184 72.378 L 13.813 73.147 L 14.115 93.316 C 14.281 104.408 14.748 117.001 15.151 121.297 L 15.886 129.109 L 178.188 129.109 L 178.188 102.859 L 120.703 102.859 L 98.696 82.859 C 86.594 71.859 76.324 62.873 75.876 62.891 C 75.429 62.907 72.399 64.876 69.141 67.266"
|
||||
android:strokeWidth="1.25"/>
|
||||
</vector>
|
||||
24
dolby/res/drawable/ic_ieq_detailed.xml
Normal file
24
dolby/res/drawable/ic_ieq_detailed.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="192dp"
|
||||
android:viewportWidth="192"
|
||||
android:viewportHeight="192"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 126.625 63.749 C 120.886 65.812 115.12 69.774 103.5 79.643 C 85.566 94.873 77.207 98.716 62.179 98.637 C 51.344 98.582 42.841 96.091 34.946 90.659 C 29.282 86.763 22.25 77.097 22.25 73.209 C 22.25 71.466 21.259 70.954 17.875 70.954 L 13.5 70.954 L 13.5 129.704 L 178.5 129.704 L 178.5 86.329 L 174.915 81.381 C 169.567 73.999 161.404 67.534 153.865 64.714 C 146.447 61.939 132.988 61.463 126.625 63.749"
|
||||
android:strokeWidth="1.25"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 128.493 63.479 C 122.229 65.384 117.026 68.819 104.59 79.26 C 89.19 92.189 83.945 95.359 73.54 98.024 C 60.438 101.381 44.281 98.268 33.966 90.4 C 28.19 85.994 22.25 77.679 22.25 73.999 C 22.25 71.475 21.634 71.061 17.875 71.061 L 13.5 71.061 L 13.5 129.811 L 178.5 129.811 L 178.494 108.249 L 178.488 86.686 L 173.807 80.541 C 163.119 66.51 143.193 59.008 128.493 63.479"
|
||||
android:strokeWidth="1.25"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 129.75 63.348 C 122.865 65.451 115.829 70.014 103.442 80.413 C 96.136 86.547 86.969 92.947 82.48 95.048 C 60.304 105.426 32.044 97.046 23.267 77.492 C 21.242 72.979 20.328 72.179 17.191 72.179 L 13.5 72.179 L 13.5 129.679 L 178.5 129.679 L 178.456 108.742 C 178.41 86.981 178.329 86.587 172.313 78.669 C 163.463 67.022 142.469 59.466 129.75 63.348"
|
||||
android:strokeWidth="1.25"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 131.184 63.546 C 123.825 64.992 117.559 68.919 103.5 80.894 C 96.969 86.458 87.98 92.824 83.526 95.043 C 75.941 98.821 74.591 99.077 62.25 99.077 C 49.875 99.077 48.564 98.827 40.714 94.962 C 31.655 90.503 24.469 83.298 22.19 76.396 C 21.04 72.909 20.19 72.202 17.152 72.202 L 13.5 72.202 L 13.5 129.702 L 178.5 129.702 L 178.5 87.577 L 174.965 82.702 C 170.49 76.532 163.525 70.403 157.488 67.323 C 153.545 65.312 139.684 61.884 137.333 62.339 C 136.944 62.414 134.176 62.957 131.184 63.546"
|
||||
android:strokeWidth="1.25"/>
|
||||
</vector>
|
||||
13
dolby/res/drawable/ic_ieq_off.xml
Normal file
13
dolby/res/drawable/ic_ieq_off.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="192dp"
|
||||
android:viewportWidth="192"
|
||||
android:viewportHeight="192"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 13.5 96 L 13.5 103.5 L 178.5 103.5 L 178.5 88.5 L 13.5 88.5 L 13.5 96"
|
||||
android:strokeWidth="1.25"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
28
dolby/res/drawable/ic_ieq_warm.xml
Normal file
28
dolby/res/drawable/ic_ieq_warm.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="192dp"
|
||||
android:viewportWidth="192"
|
||||
android:viewportHeight="192"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 48.789 64.919 C 45.865 66.366 40.885 70.429 37.721 73.948 C 28.465 84.243 13.479 116.446 13.479 126.041 L 13.479 129.786 L 95.979 129.786 C 141.354 129.786 178.479 129.486 178.479 129.12 C 178.479 128.753 177.131 125.8 175.483 122.558 C 170.413 112.579 162.723 104.006 155.705 100.509 C 149.673 97.503 148.053 97.286 131.582 97.286 C 105.944 97.286 102.89 95.881 86.335 76.454 C 74.977 63.124 61.056 58.848 48.789 64.919"
|
||||
android:strokeWidth="1.25"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 49.712 64.871 C 40.942 68.842 33.167 78.157 25.45 93.941 C 18.063 109.047 13.462 121.585 13.462 126.604 L 13.462 129.769 L 178.496 129.769 L 176.673 125.407 C 171.895 113.971 159.81 101.317 151.203 98.739 C 148.476 97.921 138.591 97.264 128.942 97.256 C 107.671 97.241 102.106 95.265 93.347 84.617 C 76.128 63.684 64.227 58.297 49.712 64.871"
|
||||
android:strokeWidth="1.25"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 52.781 63.452 C 47.321 65.482 40.256 70.907 35.792 76.496 C 27.971 86.29 13.406 119.09 13.406 126.91 L 13.406 129.502 L 178.552 129.502 L 174.586 122.06 C 169.31 112.162 163.213 105.23 156.113 101.056 C 150.453 97.729 149.705 97.625 130.807 97.53 C 107.81 97.414 103.646 96.04 94.012 85.387 C 80.912 70.9 77.898 68.045 73.171 65.632 C 67.157 62.565 57.846 61.569 52.781 63.452"
|
||||
android:strokeWidth="1.25"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M 56.078 63.069 C 44.607 65.852 35.711 75.314 26.246 94.802 C 19.707 108.263 16.537 116.731 14.817 125.334 L 14.005 129.397 L 95.98 129.397 C 165.857 129.397 177.953 129.136 177.953 127.627 C 177.953 124.146 165.22 106.857 160.048 103.314 C 152.515 98.156 150.572 97.791 129.828 97.641 C 111.478 97.507 110.935 97.424 104.33 93.776 C 99.905 91.331 95.316 87.208 91 81.798 C 80.578 68.737 74.351 64.324 64.203 62.812 C 61.797 62.453 58.141 62.568 56.078 63.069"
|
||||
android:strokeWidth="1.25"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
12
dolby/res/drawable/ic_launcher_background.xml
Normal file
12
dolby/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector android:height="108.0dip" android:width="108.0dip" android:viewportWidth="108.0" android:viewportHeight="108.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<group android:scaleX="1.3544992" android:scaleY="1.3544992" android:translateX="-23.22" android:translateY="-23.22">
|
||||
<group>
|
||||
<clip-path android:pathData="M57.01,57.01m-54,0a54,54 0,1 1,108 0a54,54 0,1 1,-108 0" />
|
||||
<path android:fillColor="#ffeaedef" android:pathData="M-1.9,-1.9h117.82v117.82h-117.82z" />
|
||||
<path android:fillColor="@drawable/ic_launcher_background__0" android:pathData="M-1.9,115.92l0,-117.82l117.82,0l-117.82,117.82z" />
|
||||
<path android:fillColor="@drawable/ic_launcher_background__1" android:pathData="M-1.9,-1.9h117.82v117.82h-117.82z" android:strokeAlpha="0.2" android:fillAlpha="0.2" />
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
6
dolby/res/drawable/ic_launcher_background__0.xml
Normal file
6
dolby/res/drawable/ic_launcher_background__0.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<gradient android:angle="0.0" android:type="linear" android:startX="57.01" android:startY="56.4" android:endX="57.01" android:endY="-1.58"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<item android:color="#ffffffff" android:offset="0.0" />
|
||||
<item android:color="#fff3f5f6" android:offset="1.0" />
|
||||
</gradient>
|
||||
6
dolby/res/drawable/ic_launcher_background__1.xml
Normal file
6
dolby/res/drawable/ic_launcher_background__1.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<gradient android:angle="0.0" android:type="linear" android:startX="-1.9" android:startY="115.92" android:endX="115.92" android:endY="-1.9"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<item android:color="#ff80d0ce" android:offset="0.0" />
|
||||
<item android:color="#ff9fa8da" android:offset="1.0" />
|
||||
</gradient>
|
||||
8
dolby/res/drawable/ic_launcher_foreground.xml
Normal file
8
dolby/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector android:height="108.0dip" android:width="108.0dip" android:viewportWidth="108.0" android:viewportHeight="108.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group android:scaleX="0.84" android:scaleY="0.84" android:translateX="23.76" android:translateY="23.76">
|
||||
<path android:fillColor="#ff465461" android:pathData="M12,19.13h5a16.87,16.87 0,0 1,0 33.74H12Z" />
|
||||
<path android:fillColor="#ff465461" android:pathData="M60,52.87H55a16.87,16.87 0,0 1,0 -33.74h5Z" />
|
||||
</group>
|
||||
</vector>
|
||||
4
dolby/res/drawable/ic_launcher_mono.xml
Normal file
4
dolby/res/drawable/ic_launcher_mono.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="108dp" android:width="108dp" android:viewportWidth="108" android:viewportHeight="108">
|
||||
<path android:fillColor="#000000" android:pathData="M36.6305484,38 C45.6078241,38.1592687 52.837468,45.7082857 52.837468,54.9974325 C52.837468,64.2865794 45.6078241,71.8355964 36.6307694,71.9948651 L32,72 L32,38 L36.6305484,38 Z M36.999,43.017 L36.999,66.977 L37.1225728,66.9701894 C42.9872179,66.559014 47.6837958,61.5395304 47.8337726,55.3050128 L47.837468,54.9974325 C47.837468,48.6211219 43.0833381,43.4425908 37.1223719,43.0246757 L36.999,43.017 Z M71.3694516,38 C62.3921759,38.1592687 55.162532,45.7082857 55.162532,54.9974325 C55.162532,64.2865794 62.3921759,71.8355964 71.3692306,71.9948651 L76,72 L76,38 L71.3694516,38 Z M71.000532,43.017 L71.000532,66.977 L70.8774272,66.9701894 C65.0127821,66.559014 60.3162042,61.5395304 60.1662274,55.3050128 L60.162532,54.9974325 C60.162532,48.6211219 64.9166619,43.4425908 70.8776281,43.0246757 L71.000532,43.017 Z" android:strokeWidth="1"/>
|
||||
</vector>
|
||||
11
dolby/res/drawable/ic_profile_custom.xml
Executable file
11
dolby/res/drawable/ic_profile_custom.xml
Executable file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:tint="?android:attr/colorControlNormal"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="72.0"
|
||||
android:viewportHeight="72.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M45,9L45,27L51,27L51,21L63,21L63,15L51,15L51,9L45,9M9,15L9,21L39,21L39,15L9,15M21,27L21,33L9,33L9,39L21,39L21,45L27,45L27,27L21,27M33,33L33,39L63,39L63,33L33,33M33,45L33,63L39,63L39,57L63,57L63,51L39,51L39,45L33,45M9,51L9,57L27,57L27,51L9,51z"/>
|
||||
</vector>
|
||||
11
dolby/res/drawable/ic_profile_dynamic.xml
Executable file
11
dolby/res/drawable/ic_profile_dynamic.xml
Executable file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:tint="?android:attr/colorControlNormal"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="72.0"
|
||||
android:viewportHeight="72.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M21,6L21,39L30,39L30,66C34.0185,62.1036 36.3516,56.7839 39.2006,52C43.3436,45.0435 48.7076,37.7933 51,30L40,30C43.3656,21.916 48.5711,14.4315 51,6L21,6z"/>
|
||||
</vector>
|
||||
11
dolby/res/drawable/ic_profile_movie.xml
Executable file
11
dolby/res/drawable/ic_profile_movie.xml
Executable file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:tint="?android:attr/colorControlNormal"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="72.0"
|
||||
android:viewportHeight="72.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7.84,13.82C9.62,11.74 12.52,12.02 14.98,11.95C16.97,15.97 19,19.98 21,24C24,24 27,24 30,24C28,20 26,16 24,12C26,12 28,12 30,12C32,16 34,20 36,24C39,24 42,24 45,24C43,20 41,16 39,12C41,12 43,12 45,12C47,16 49,20 51,24C54,24 57,24 60,24C58,20 56,16 54,12C58,12 62,12 66,12C65.99,25.69 66.01,39.38 66,53.07C66.42,56.49 63.62,59.98 60.12,59.96C44.39,60.06 28.66,59.97 12.93,60C9.52,60.43 6.02,57.62 6.04,54.12C5.93,42.75 6.06,31.37 6,19.99C5.95,17.82 6.08,15.36 7.84,13.82Z"/>
|
||||
</vector>
|
||||
11
dolby/res/drawable/ic_profile_music.xml
Executable file
11
dolby/res/drawable/ic_profile_music.xml
Executable file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:tint="?android:attr/colorControlNormal"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="72.0"
|
||||
android:viewportHeight="72.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M36,9L36,40C32.245,38.8901 27.6726,38.2912 24.0154,40.179C17.5734,43.5044 15.8784,53.3215 20.4336,58.8912C25.822,65.4796 38.0937,64.1632 41.2577,55.9961C42.655,52.3894 42,47.7931 42,44L42,21L54,21L54,9L36,9z"/>
|
||||
</vector>
|
||||
9
dolby/res/drawable/reset_settings_24px.xml
Normal file
9
dolby/res/drawable/reset_settings_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="?android:attr/colorControlNormal"
|
||||
android:pathData="M520,630L520,570L680,570L680,630L520,630ZM580,840L580,790L520,790L520,730L580,730L580,680L640,680L640,840L580,840ZM680,790L680,730L840,730L840,790L680,790ZM720,680L720,520L780,520L780,570L840,570L840,630L780,630L780,680L720,680ZM831,400L748,400Q722,312 649,256Q576,200 480,200Q363,200 281.5,281.5Q200,363 200,480Q200,552 232.5,612Q265,672 320,710L320,600L400,600L400,840L160,840L160,760L254,760Q192,710 156,637.5Q120,565 120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q609,120 706.5,199.5Q804,279 831,400Z" />
|
||||
</vector>
|
||||
9
dolby/res/drawable/save_as_24px.xml
Normal file
9
dolby/res/drawable/save_as_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="?android:attr/colorControlNormal"
|
||||
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L680,120L840,280L840,492Q821,484 800.5,481.5Q780,479 760,482L760,313L647,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L440,760L440,764L440,840L200,840ZM200,200L200,313L200,482Q200,485 200,494.5Q200,504 200,519L200,760L200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200ZM520,920L520,797L741,577Q750,568 761,564Q772,560 783,560Q795,560 806,564.5Q817,569 826,578L863,615Q871,624 875.5,635Q880,646 880,657Q880,668 876,679.5Q872,691 863,700L643,920L520,920ZM820,657L820,657L783,620L783,620L820,657ZM580,860L618,860L739,738L721,719L702,701L580,822L580,860ZM721,719L702,701L702,701L739,738L739,738L721,719ZM240,400L600,400L600,240L240,240L240,400ZM480,720Q481,720 482,720Q483,720 484,720L600,605Q600,603 600,602.5Q600,602 600,600Q600,550 565,515Q530,480 480,480Q430,480 395,515Q360,550 360,600Q360,650 395,685Q430,720 480,720Z"/>
|
||||
</vector>
|
||||
41
dolby/res/layout/ieq_icon_layout.xml
Normal file
41
dolby/res/layout/ieq_icon_layout.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2021 The Android Open Source Project
|
||||
(C) 2024 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.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@android:id/widget_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="56dp"
|
||||
android:gravity="end|center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingRight="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp">
|
||||
|
||||
<androidx.preference.internal.PreferenceImageView
|
||||
android:id="@+id/ieq_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:maxWidth="56dp"
|
||||
app:maxHeight="56dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
7
dolby/res/mipmap-anydpi/ic_launcher.xml
Normal file
7
dolby/res/mipmap-anydpi/ic_launcher.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_mono" />
|
||||
</adaptive-icon>
|
||||
70
dolby/res/values-ru/strings.xml
Normal file
70
dolby/res/values-ru/strings.xml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023-24 Paranoid Android
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- Dolby Atmos -->
|
||||
<string name="dolby_title">Dolby Atmos</string>
|
||||
<string name="dolby_enable">Использовать Dolby Atmos</string>
|
||||
<string name="dolby_profile_title">Выберите профиль</string>
|
||||
<string name="dolby_preset">Графический эквалайзер</string>
|
||||
<string name="dolby_off">Выключено</string>
|
||||
<string name="dolby_on">Включено</string>
|
||||
<string name="dolby_low">Низкий</string>
|
||||
<string name="dolby_medium">Средний</string>
|
||||
<string name="dolby_high">Высокий</string>
|
||||
<string name="dolby_max">Максимальный</string>
|
||||
<string name="dolby_unknown">Неизвестно</string>
|
||||
<string name="dolby_on_with_profile">Включено (%1$s)</string>
|
||||
<string name="dolby_category_settings">Настройки</string>
|
||||
<string name="dolby_bass_enhancer">Усилитель басов</string>
|
||||
<string name="dolby_dialogue_enhancer">Усиление диалогов</string>
|
||||
<string name="dolby_spk_virtualizer">Виртуализация динамиков</string>
|
||||
<string name="dolby_hp_virtualizer">Виртуализация наушников</string>
|
||||
<string name="dolby_stereo_widening">Расширение стерео</string>
|
||||
<string name="dolby_volume_leveler">Выравниватель громкости</string>
|
||||
<string name="dolby_connect_headphones">Подключите наушники</string>
|
||||
<string name="dolby_reset_profile">Сбросить до настроек по умолчанию</string>
|
||||
<string name="dolby_reset_profile_toast">Настройки для профиля %1$s успешно сброшены</string>
|
||||
|
||||
<!-- Dolby profiles -->
|
||||
<string name="dolby_profile_dynamic">Динамический</string>
|
||||
<string name="dolby_profile_video">Кино/Видео</string>
|
||||
<string name="dolby_profile_music">Музыка</string>
|
||||
<string name="dolby_profile_voice">Голос</string>
|
||||
|
||||
<!-- Dolby equalizer presets -->
|
||||
<string name="dolby_preset_default">Плоский (выключено)</string>
|
||||
<string name="dolby_preset_rock">Рок</string>
|
||||
<string name="dolby_preset_jazz">Джаз</string>
|
||||
<string name="dolby_preset_pop">Поп</string>
|
||||
<string name="dolby_preset_classical">Классика</string>
|
||||
<string name="dolby_preset_hiphop">Хип-хоп</string>
|
||||
<string name="dolby_preset_blues">Блюз</string>
|
||||
<string name="dolby_preset_electronic">Электронная музыка</string>
|
||||
<string name="dolby_preset_country">Кантри</string>
|
||||
<string name="dolby_preset_dance">Танцевальная музыка</string>
|
||||
<string name="dolby_preset_metal">Метал</string>
|
||||
|
||||
<!-- Dolby equalizer UI -->
|
||||
<string name="dolby_geq_slider_label_gain">Усиление</string>
|
||||
<string name="dolby_geq_preset">Предустановка</string>
|
||||
<string name="dolby_geq_preset_name">Название предустановки</string>
|
||||
<string name="dolby_geq_new_preset">Новая предустановка</string>
|
||||
<string name="dolby_geq_rename_preset">Переименовать предустановку</string>
|
||||
<string name="dolby_geq_delete_preset">Удалить предустановку</string>
|
||||
<string name="dolby_geq_delete_preset_prompt">Вы хотите удалить эту предустановку?</string>
|
||||
<string name="dolby_geq_reset_gains">Сбросить усиление</string>
|
||||
<string name="dolby_geq_reset_gains_prompt">Вы хотите сбросить эту предустановку до настроек по умолчанию?</string>
|
||||
<string name="dolby_geq_preset_name_exists">Имя предустановки уже существует!</string>
|
||||
<string name="dolby_geq_preset_name_too_long">Имя предустановки слишком длинное!</string>
|
||||
|
||||
<!-- Dolby intelligent EQ -->
|
||||
<string name="dolby_ieq">Интеллектуальный эквалайзер</string>
|
||||
<string name="dolby_balanced">Сбалансированный</string>
|
||||
<string name="dolby_warm">Тёплый</string>
|
||||
<string name="dolby_detailed">Детализированный</string>
|
||||
|
||||
</resources>
|
||||
102
dolby/res/values/arrays.xml
Normal file
102
dolby/res/values/arrays.xml
Normal file
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023-24 Paranoid Android
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- Dolby Atmos -->
|
||||
<string-array name="dolby_profile_entries">
|
||||
<item>@string/dolby_profile_dynamic</item>
|
||||
<item>@string/dolby_profile_movie</item>
|
||||
<item>@string/dolby_profile_music</item>
|
||||
<item>@string/dolby_profile_custom</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dolby_profile_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dolby_preset_entries" translatable="false">
|
||||
<item>@string/dolby_preset_default</item>
|
||||
<item>@string/dolby_preset_rock</item>
|
||||
<item>@string/dolby_preset_jazz</item>
|
||||
<item>@string/dolby_preset_pop</item>
|
||||
<item>@string/dolby_preset_classical</item>
|
||||
<item>@string/dolby_preset_hiphop</item>
|
||||
<item>@string/dolby_preset_blues</item>
|
||||
<item>@string/dolby_preset_electronic</item>
|
||||
<item>@string/dolby_preset_metal</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dolby_preset_values">
|
||||
<!--
|
||||
<item>0,0,0,0,0,0,0,0,0,0</item>
|
||||
<item>4,1,-2,-0.25,0,-2,0,-2,0.5,4</item>
|
||||
<item>0,0,0,-1,-1,-3,-0.5,0,0,0</item>
|
||||
<item>-2,-0.5,-5,-1,0,0,-0.5,-3,-0.5,0</item>
|
||||
<item>0,0,0,0,0.5,3,1,6,2,6</item>
|
||||
<item>3,0,-3,-0.5,-0.5,-3,-0.5,0,0,2</item>
|
||||
<item>2,2,-6,-2,3,1,0,1,0,2</item>
|
||||
<item>3,1,-1,0,-0.5,-3,-0.5,0,0,0</item>
|
||||
<item>2,0,0,-1.25,-1,-4,0,0,0,0</item>
|
||||
-->
|
||||
<item>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0</item>
|
||||
<item>60,36,12,-12,-36,-24,-12,-8,-4,-20,-36,-20,-4,-20,-36,-16,4,32,60,60</item>
|
||||
<item>8,8,8,8,8,0,-8,-8,-8,-24,-40,-20,0,4,8,8,8,8,8,8</item>
|
||||
<item>-13,-1,11,-25,-61,-29,3,11,19,19,19,15,11,-9,-29,-9,11,15,19,19</item>
|
||||
<item>-32,-32,-32,-32,-32,-32,-32,-28,-24,-4,16,0,-16,24,64,32,0,32,64,64</item>
|
||||
<item>52,28,4,-20,-44,-24,-4,-4,-4,-24,-44,-24,-4,0,4,4,4,20,36,36</item>
|
||||
<item>28,28,28,-36,-100,-68,-36,4,44,28,12,4,-4,4,12,4,-4,12,28,28</item>
|
||||
<item>50,34,18,2,-14,-6,2,-2,-6,-26,-46,-26,-6,-2,2,2,2,2,2,2</item>
|
||||
<item>40,24,8,8,8,-4,-16,-12,-8,-32,-56,-24,8,8,8,8,8,8,8,8</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dolby_dialogue_entries">
|
||||
<item>@string/dolby_off</item>
|
||||
<item>@string/dolby_low</item>
|
||||
<item>@string/dolby_medium</item>
|
||||
<item>@string/dolby_high</item>
|
||||
<item>@string/dolby_max</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dolby_dialogue_values">
|
||||
<item>0</item>
|
||||
<item>2</item>
|
||||
<item>6</item>
|
||||
<item>9</item>
|
||||
<item>12</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dolby_stereo_entries">
|
||||
<item>@string/dolby_low</item>
|
||||
<item>@string/dolby_medium</item>
|
||||
<item>@string/dolby_high</item>
|
||||
<item>@string/dolby_max</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dolby_stereo_values">
|
||||
<item>4</item>
|
||||
<item>24</item>
|
||||
<item>44</item>
|
||||
<item>64</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dolby_ieq_entries">
|
||||
<item>@string/dolby_off</item>
|
||||
<item>@string/dolby_balanced</item>
|
||||
<item>@string/dolby_warm</item>
|
||||
<item>@string/dolby_detailed</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="dolby_ieq_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
70
dolby/res/values/strings.xml
Normal file
70
dolby/res/values/strings.xml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023-24 Paranoid Android
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- Dolby Atmos -->
|
||||
<string name="dolby_title">Dolby Atmos</string>
|
||||
<string name="dolby_enable">Use Dolby Atmos</string>
|
||||
<string name="dolby_profile_title">Choose a profile</string>
|
||||
<string name="dolby_preset">Graphic equalizer</string>
|
||||
<string name="dolby_off">Off</string>
|
||||
<string name="dolby_on">On</string>
|
||||
<string name="dolby_low">Low</string>
|
||||
<string name="dolby_medium">Medium</string>
|
||||
<string name="dolby_high">High</string>
|
||||
<string name="dolby_max">Max</string>
|
||||
<string name="dolby_unknown">Unknown</string>
|
||||
<string name="dolby_on_with_profile">On (%1$s)</string>
|
||||
<string name="dolby_category_settings">Settings</string>
|
||||
<string name="dolby_bass_enhancer">Bass enhancer</string>
|
||||
<string name="dolby_dialogue_enhancer">Dialogue enhancer</string>
|
||||
<string name="dolby_spk_virtualizer">Speaker virtualization</string>
|
||||
<string name="dolby_hp_virtualizer">Headphone virtualization</string>
|
||||
<string name="dolby_stereo_widening">Stereo widening</string>
|
||||
<string name="dolby_volume_leveler">Volume leveler</string>
|
||||
<string name="dolby_connect_headphones">Connect headphones</string>
|
||||
<string name="dolby_reset_profile">Reset to defaults</string>
|
||||
<string name="dolby_reset_profile_toast">Successfully reset settings for %1$s profile</string>
|
||||
|
||||
<!-- Dolby profiles -->
|
||||
<string name="dolby_profile_dynamic">Dynamic</string>
|
||||
<string name="dolby_profile_movie">Movie</string>
|
||||
<string name="dolby_profile_music">Music</string>
|
||||
<string name="dolby_profile_custom">Custom</string>
|
||||
|
||||
<!-- Dolby equalizer presets -->
|
||||
<string name="dolby_preset_default">Flat (off)</string>
|
||||
<string name="dolby_preset_rock">Rock</string>
|
||||
<string name="dolby_preset_jazz">Jazz</string>
|
||||
<string name="dolby_preset_pop">Pop</string>
|
||||
<string name="dolby_preset_classical">Classical</string>
|
||||
<string name="dolby_preset_hiphop">Hip Hop</string>
|
||||
<string name="dolby_preset_blues">Blues</string>
|
||||
<string name="dolby_preset_electronic">Electronic</string>
|
||||
<string name="dolby_preset_country">Country</string>
|
||||
<string name="dolby_preset_dance">Dance</string>
|
||||
<string name="dolby_preset_metal">Metal</string>
|
||||
|
||||
<!-- Dolby equalizer UI -->
|
||||
<string name="dolby_geq_slider_label_gain">Gain</string>
|
||||
<string name="dolby_geq_preset">Preset</string>
|
||||
<string name="dolby_geq_preset_name">Preset name</string>
|
||||
<string name="dolby_geq_new_preset">New preset</string>
|
||||
<string name="dolby_geq_rename_preset">Rename preset</string>
|
||||
<string name="dolby_geq_delete_preset">Delete preset</string>
|
||||
<string name="dolby_geq_delete_preset_prompt">Do you want to delete this preset?</string>
|
||||
<string name="dolby_geq_reset_gains">Reset gains</string>
|
||||
<string name="dolby_geq_reset_gains_prompt">Do you want to reset this preset to defaults?</string>
|
||||
<string name="dolby_geq_preset_name_exists">Preset name already exists!</string>
|
||||
<string name="dolby_geq_preset_name_too_long">Preset name is too long!</string>
|
||||
|
||||
<!-- Dolby intelligent EQ -->
|
||||
<string name="dolby_ieq">Intelligent equalizer</string>
|
||||
<string name="dolby_balanced">Balanced</string>
|
||||
<string name="dolby_warm">Warm</string>
|
||||
<string name="dolby_detailed">Detailed</string>
|
||||
|
||||
</resources>
|
||||
76
dolby/res/xml/dolby_settings.xml
Normal file
76
dolby/res/xml/dolby_settings.xml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023-24 Paranoid Android
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/dolby_title">
|
||||
|
||||
<com.android.settingslib.widget.MainSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="dolby_enable"
|
||||
android:title="@string/dolby_enable" />
|
||||
|
||||
<ListPreference
|
||||
android:key="dolby_profile"
|
||||
android:entries="@array/dolby_profile_entries"
|
||||
android:entryValues="@array/dolby_profile_values"
|
||||
android:defaultValue="0"
|
||||
android:title="@string/dolby_profile_title"
|
||||
android:summary="%s" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/dolby_category_settings">
|
||||
|
||||
<Preference
|
||||
android:key="dolby_preset"
|
||||
android:title="@string/dolby_preset">
|
||||
<intent
|
||||
android:action="android.intent.action.MAIN"
|
||||
android:targetPackage="co.aospa.dolby.xiaomi"
|
||||
android:targetClass="co.aospa.dolby.xiaomi.geq.EqualizerActivity" />
|
||||
</Preference>
|
||||
|
||||
<co.aospa.dolby.xiaomi.preference.DolbyIeqPreference
|
||||
android:key="dolby_ieq"
|
||||
android:entries="@array/dolby_ieq_entries"
|
||||
android:entryValues="@array/dolby_ieq_values"
|
||||
android:title="@string/dolby_ieq" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="dolby_spk_virtualizer"
|
||||
android:title="@string/dolby_spk_virtualizer" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="dolby_virtualizer"
|
||||
android:title="@string/dolby_hp_virtualizer" />
|
||||
|
||||
<ListPreference
|
||||
android:key="dolby_stereo"
|
||||
android:entries="@array/dolby_stereo_entries"
|
||||
android:entryValues="@array/dolby_stereo_values"
|
||||
android:title="@string/dolby_stereo_widening"
|
||||
android:dependency="dolby_virtualizer" />
|
||||
|
||||
<ListPreference
|
||||
android:key="dolby_dialogue"
|
||||
android:entries="@array/dolby_dialogue_entries"
|
||||
android:entryValues="@array/dolby_dialogue_values"
|
||||
android:title="@string/dolby_dialogue_enhancer" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="dolby_bass"
|
||||
android:title="@string/dolby_bass_enhancer" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="dolby_volume"
|
||||
android:title="@string/dolby_volume_leveler" />
|
||||
|
||||
<Preference
|
||||
android:key="dolby_reset"
|
||||
android:title="@string/dolby_reset_profile" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
27
dolby/src/co/aospa/dolby/xiaomi/BootCompletedReceiver.kt
Normal file
27
dolby/src/co/aospa/dolby/xiaomi/BootCompletedReceiver.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023-24 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
|
||||
private const val TAG = "XiaomiDolby-Boot"
|
||||
|
||||
class BootCompletedReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
Log.d(TAG, "Received intent: ${intent.action}")
|
||||
if (intent.action != Intent.ACTION_BOOT_COMPLETED) {
|
||||
return
|
||||
}
|
||||
|
||||
Log.i(TAG, "Boot completed, starting dolby")
|
||||
DolbyController.getInstance(context).onBootCompleted()
|
||||
}
|
||||
}
|
||||
25
dolby/src/co/aospa/dolby/xiaomi/DolbyActivity.kt
Normal file
25
dolby/src/co/aospa/dolby/xiaomi/DolbyActivity.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2023-24 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi
|
||||
|
||||
import android.os.Bundle
|
||||
import co.aospa.dolby.xiaomi.preference.DolbySettingsFragment
|
||||
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity
|
||||
|
||||
class DolbyActivity : CollapsingToolbarBaseActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(
|
||||
com.android.settingslib.collapsingtoolbar.R.id.content_frame,
|
||||
DolbySettingsFragment(),
|
||||
)
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
136
dolby/src/co/aospa/dolby/xiaomi/DolbyAudioEffect.kt
Normal file
136
dolby/src/co/aospa/dolby/xiaomi/DolbyAudioEffect.kt
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (C) 2023-24 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi
|
||||
|
||||
import android.media.audiofx.AudioEffect
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.dlog
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.DsParam
|
||||
import java.util.UUID
|
||||
|
||||
class DolbyAudioEffect(priority: Int, audioSession: Int) : AudioEffect(
|
||||
EFFECT_TYPE_NULL, EFFECT_TYPE_DAP, priority, audioSession
|
||||
) {
|
||||
|
||||
var dsOn: Boolean
|
||||
get() = getIntParam(EFFECT_PARAM_ENABLE) == 1
|
||||
set(value) {
|
||||
setIntParam(EFFECT_PARAM_ENABLE, if (value) 1 else 0)
|
||||
enabled = value
|
||||
}
|
||||
|
||||
var profile: Int
|
||||
get() = getIntParam(EFFECT_PARAM_PROFILE)
|
||||
set(value) {
|
||||
setIntParam(EFFECT_PARAM_PROFILE, value)
|
||||
}
|
||||
|
||||
private fun setIntParam(param: Int, value: Int) {
|
||||
dlog(TAG, "setIntParam($param, $value)")
|
||||
val buf = ByteArray(12)
|
||||
int32ToByteArray(param, buf, 0)
|
||||
int32ToByteArray(1, buf, 4)
|
||||
int32ToByteArray(value, buf, 8)
|
||||
checkStatus(setParameter(EFFECT_PARAM_CPDP_VALUES, buf))
|
||||
}
|
||||
|
||||
private fun getIntParam(param: Int): Int {
|
||||
val buf = ByteArray(12)
|
||||
int32ToByteArray(param, buf, 0)
|
||||
checkStatus(getParameter(EFFECT_PARAM_CPDP_VALUES + param, buf))
|
||||
return byteArrayToInt32(buf).also {
|
||||
dlog(TAG, "getIntParam($param): $it")
|
||||
}
|
||||
}
|
||||
|
||||
fun resetProfileSpecificSettings(profile: Int = this.profile) {
|
||||
dlog(TAG, "resetProfileSpecificSettings: profile=$profile")
|
||||
setIntParam(EFFECT_PARAM_RESET_PROFILE_SETTINGS, profile)
|
||||
}
|
||||
|
||||
fun setDapParameter(param: DsParam, values: IntArray, profile: Int = this.profile) {
|
||||
dlog(TAG, "setDapParameter: profile=$profile param=$param")
|
||||
val length = values.size
|
||||
val buf = ByteArray((length + 4) * 4)
|
||||
int32ToByteArray(EFFECT_PARAM_SET_PROFILE_PARAMETER, buf, 0)
|
||||
int32ToByteArray(length + 1, buf, 4)
|
||||
int32ToByteArray(profile, buf, 8)
|
||||
int32ToByteArray(param.id, buf, 12)
|
||||
int32ArrayToByteArray(values, buf, 16)
|
||||
checkStatus(setParameter(EFFECT_PARAM_CPDP_VALUES, buf))
|
||||
}
|
||||
|
||||
fun setDapParameter(param: DsParam, enable: Boolean, profile: Int = this.profile) =
|
||||
setDapParameter(param, intArrayOf(if (enable) 1 else 0), profile)
|
||||
|
||||
fun setDapParameter(param: DsParam, value: Int, profile: Int = this.profile) =
|
||||
setDapParameter(param, intArrayOf(value), profile)
|
||||
|
||||
fun getDapParameter(param: DsParam, profile: Int = this.profile): IntArray {
|
||||
dlog(TAG, "getDapParameter: profile=$profile param=$param")
|
||||
val length = param.length
|
||||
val buf = ByteArray((length + 2) * 4)
|
||||
val p = (param.id shl 16) + (profile shl 8) + EFFECT_PARAM_GET_PROFILE_PARAMETER
|
||||
checkStatus(getParameter(p, buf))
|
||||
return byteArrayToInt32Array(buf, length)
|
||||
}
|
||||
|
||||
fun getDapParameterBool(param: DsParam, profile: Int = this.profile): Boolean =
|
||||
getDapParameter(param, profile)[0] == 1
|
||||
|
||||
fun getDapParameterInt(param: DsParam, profile: Int = this.profile): Int =
|
||||
getDapParameter(param, profile)[0]
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DolbyAudioEffect"
|
||||
private val EFFECT_TYPE_DAP =
|
||||
UUID.fromString("9d4921da-8225-4f29-aefa-39537a04bcaa")
|
||||
|
||||
private const val EFFECT_PARAM_ENABLE = 0
|
||||
private const val EFFECT_PARAM_CPDP_VALUES = 5
|
||||
private const val EFFECT_PARAM_PROFILE = 0xA000000
|
||||
private const val EFFECT_PARAM_SET_PROFILE_PARAMETER = 0x1000000
|
||||
private const val EFFECT_PARAM_GET_PROFILE_PARAMETER = 0x1000005
|
||||
private const val EFFECT_PARAM_RESET_PROFILE_SETTINGS = 0xC000000
|
||||
|
||||
private fun int32ToByteArray(value: Int, dst: ByteArray, index: Int) {
|
||||
var idx = index
|
||||
dst[idx++] = (value and 0xff).toByte()
|
||||
dst[idx++] = ((value ushr 8) and 0xff).toByte()
|
||||
dst[idx++] = ((value ushr 16) and 0xff).toByte()
|
||||
dst[idx] = ((value ushr 24) and 0xff).toByte()
|
||||
}
|
||||
|
||||
private fun byteArrayToInt32(ba: ByteArray): Int {
|
||||
return ((ba[3].toInt() and 0xff) shl 24) or
|
||||
((ba[2].toInt() and 0xff) shl 16) or
|
||||
((ba[1].toInt() and 0xff) shl 8) or
|
||||
(ba[0].toInt() and 0xff)
|
||||
}
|
||||
|
||||
private fun int32ArrayToByteArray(src: IntArray, dst: ByteArray, index: Int) {
|
||||
var idx = index
|
||||
for (x in src) {
|
||||
dst[idx++] = (x and 0xff).toByte()
|
||||
dst[idx++] = ((x ushr 8) and 0xff).toByte()
|
||||
dst[idx++] = ((x ushr 16) and 0xff).toByte()
|
||||
dst[idx++] = ((x ushr 24) and 0xff).toByte()
|
||||
}
|
||||
}
|
||||
|
||||
private fun byteArrayToInt32Array(ba: ByteArray, dstLength: Int): IntArray {
|
||||
val srcLength = ba.size shr 2
|
||||
val dst = IntArray(dstLength.coerceAtMost(srcLength))
|
||||
for (i in dst.indices) {
|
||||
dst[i] = ((ba[i * 4 + 3].toInt() and 0xff) shl 24) or
|
||||
((ba[i * 4 + 2].toInt() and 0xff) shl 16) or
|
||||
((ba[i * 4 + 1].toInt() and 0xff) shl 8) or
|
||||
(ba[i * 4].toInt() and 0xff)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
}
|
||||
}
|
||||
60
dolby/src/co/aospa/dolby/xiaomi/DolbyConstants.kt
Normal file
60
dolby/src/co/aospa/dolby/xiaomi/DolbyConstants.kt
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2023-24 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi
|
||||
|
||||
import android.util.Log
|
||||
|
||||
class DolbyConstants {
|
||||
|
||||
enum class DsParam(val id: Int, val length: Int = 1) {
|
||||
HEADPHONE_VIRTUALIZER(101),
|
||||
SPEAKER_VIRTUALIZER(102),
|
||||
VOLUME_LEVELER_ENABLE(103),
|
||||
IEQ_PRESET(104),
|
||||
DIALOGUE_ENHANCER_ENABLE(105),
|
||||
DIALOGUE_ENHANCER_AMOUNT(108),
|
||||
GEQ_BAND_GAINS(110, 20),
|
||||
BASS_ENHANCER_ENABLE(111),
|
||||
STEREO_WIDENING_AMOUNT(113);
|
||||
|
||||
override fun toString(): String {
|
||||
return "${name}(${id})"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "XiaomiDolby"
|
||||
const val PREF_ENABLE = "dolby_enable"
|
||||
const val PREF_PROFILE = "dolby_profile"
|
||||
const val PREF_PRESET = "dolby_preset"
|
||||
const val PREF_IEQ = "dolby_ieq"
|
||||
const val PREF_HP_VIRTUALIZER = "dolby_virtualizer"
|
||||
const val PREF_SPK_VIRTUALIZER = "dolby_spk_virtualizer"
|
||||
const val PREF_STEREO = "dolby_stereo"
|
||||
const val PREF_DIALOGUE = "dolby_dialogue"
|
||||
const val PREF_BASS = "dolby_bass"
|
||||
const val PREF_VOLUME = "dolby_volume"
|
||||
const val PREF_RESET = "dolby_reset"
|
||||
|
||||
val PROFILE_SPECIFIC_PREFS = setOf(
|
||||
PREF_PRESET,
|
||||
PREF_IEQ,
|
||||
PREF_HP_VIRTUALIZER,
|
||||
PREF_SPK_VIRTUALIZER,
|
||||
PREF_STEREO,
|
||||
PREF_DIALOGUE,
|
||||
PREF_BASS,
|
||||
PREF_VOLUME
|
||||
)
|
||||
|
||||
fun dlog(tag: String, msg: String) {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(tag, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
323
dolby/src/co/aospa/dolby/xiaomi/DolbyController.kt
Normal file
323
dolby/src/co/aospa/dolby/xiaomi/DolbyController.kt
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright (C) 2023-24 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioDeviceCallback
|
||||
import android.media.AudioDeviceInfo
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioManager.AudioPlaybackCallback
|
||||
import android.media.AudioPlaybackConfiguration
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import androidx.preference.PreferenceManager
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.dlog
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.DsParam
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
|
||||
internal class DolbyController private constructor(
|
||||
private val context: Context
|
||||
) {
|
||||
private var dolbyEffect = DolbyAudioEffect(EFFECT_PRIORITY, audioSession = 0)
|
||||
private val audioManager = context.getSystemService(AudioManager::class.java)!!
|
||||
private val handler = Handler(context.mainLooper)
|
||||
|
||||
// Restore current profile on every media session
|
||||
private val playbackCallback = object : AudioPlaybackCallback() {
|
||||
override fun onPlaybackConfigChanged(configs: List<AudioPlaybackConfiguration>) {
|
||||
val isPlaying = configs.any {
|
||||
it.playerState == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
|
||||
}
|
||||
dlog(TAG, "onPlaybackConfigChanged: isPlaying=$isPlaying")
|
||||
if (isPlaying)
|
||||
setCurrentProfile()
|
||||
}
|
||||
}
|
||||
|
||||
// Restore current profile on audio device change
|
||||
private val audioDeviceCallback = object : AudioDeviceCallback() {
|
||||
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) {
|
||||
dlog(TAG, "onAudioDevicesAdded")
|
||||
setCurrentProfile()
|
||||
}
|
||||
|
||||
override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) {
|
||||
dlog(TAG, "onAudioDevicesRemoved")
|
||||
setCurrentProfile()
|
||||
}
|
||||
}
|
||||
|
||||
private var registerCallbacks = false
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
dlog(TAG, "setRegisterCallbacks($value)")
|
||||
if (value) {
|
||||
audioManager.registerAudioPlaybackCallback(playbackCallback, handler)
|
||||
audioManager.registerAudioDeviceCallback(audioDeviceCallback, handler)
|
||||
} else {
|
||||
audioManager.unregisterAudioPlaybackCallback(playbackCallback)
|
||||
audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
|
||||
}
|
||||
}
|
||||
|
||||
var dsOn: Boolean
|
||||
get() =
|
||||
dolbyEffect.dsOn.also {
|
||||
dlog(TAG, "getDsOn: $it")
|
||||
}
|
||||
set(value) {
|
||||
dlog(TAG, "setDsOn: $value")
|
||||
checkEffect()
|
||||
dolbyEffect.dsOn = value
|
||||
registerCallbacks = value
|
||||
if (value)
|
||||
setCurrentProfile()
|
||||
}
|
||||
|
||||
var profile: Int
|
||||
get() =
|
||||
dolbyEffect.profile.also {
|
||||
dlog(TAG, "getProfile: $it")
|
||||
}
|
||||
set(value) {
|
||||
dlog(TAG, "setProfile: $value")
|
||||
checkEffect()
|
||||
dolbyEffect.profile = value
|
||||
}
|
||||
|
||||
init {
|
||||
dlog(TAG, "initialized")
|
||||
}
|
||||
|
||||
fun onBootCompleted() {
|
||||
dlog(TAG, "onBootCompleted")
|
||||
|
||||
// Restore our main settings
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
dsOn = prefs.getBoolean(DolbyConstants.PREF_ENABLE, true)
|
||||
|
||||
context.resources.getStringArray(R.array.dolby_profile_values)
|
||||
.map { it.toInt() }
|
||||
.forEach { profile ->
|
||||
// Reset dolby first to prevent it from loading bad settings
|
||||
dolbyEffect.resetProfileSpecificSettings(profile)
|
||||
// Now restore our profile-specific settings
|
||||
restoreSettings(profile)
|
||||
}
|
||||
|
||||
// Finally restore the current profile.
|
||||
setCurrentProfile()
|
||||
}
|
||||
|
||||
private fun restoreSettings(profile: Int) {
|
||||
dlog(TAG, "restoreSettings(profile=$profile)")
|
||||
val prefs = context.getSharedPreferences("profile_$profile", Context.MODE_PRIVATE)
|
||||
setPreset(
|
||||
prefs.getString(DolbyConstants.PREF_PRESET, getPreset(profile))!!,
|
||||
profile
|
||||
)
|
||||
setIeqPreset(
|
||||
prefs.getString(
|
||||
DolbyConstants.PREF_IEQ,
|
||||
getIeqPreset(profile).toString()
|
||||
)!!.toInt(),
|
||||
profile
|
||||
)
|
||||
setHeadphoneVirtEnabled(
|
||||
prefs.getBoolean(DolbyConstants.PREF_HP_VIRTUALIZER, getHeadphoneVirtEnabled(profile)),
|
||||
profile
|
||||
)
|
||||
setSpeakerVirtEnabled(
|
||||
prefs.getBoolean(DolbyConstants.PREF_SPK_VIRTUALIZER, getSpeakerVirtEnabled(profile)),
|
||||
profile
|
||||
)
|
||||
setStereoWideningAmount(
|
||||
prefs.getString(
|
||||
DolbyConstants.PREF_STEREO,
|
||||
getStereoWideningAmount(profile).toString()
|
||||
)!!.toInt(),
|
||||
profile
|
||||
)
|
||||
setDialogueEnhancerAmount(
|
||||
prefs.getString(
|
||||
DolbyConstants.PREF_DIALOGUE,
|
||||
getDialogueEnhancerAmount(profile).toString()
|
||||
)!!.toInt(),
|
||||
profile
|
||||
)
|
||||
setBassEnhancerEnabled(
|
||||
prefs.getBoolean(DolbyConstants.PREF_BASS, getBassEnhancerEnabled(profile)),
|
||||
profile
|
||||
)
|
||||
setVolumeLevelerEnabled(
|
||||
prefs.getBoolean(DolbyConstants.PREF_VOLUME, getVolumeLevelerEnabled(profile)),
|
||||
profile
|
||||
)
|
||||
}
|
||||
|
||||
private fun checkEffect() {
|
||||
if (!dolbyEffect.hasControl()) {
|
||||
Log.w(TAG, "lost control, recreating effect")
|
||||
dolbyEffect.release()
|
||||
dolbyEffect = DolbyAudioEffect(EFFECT_PRIORITY, audioSession = 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setCurrentProfile() {
|
||||
dlog(TAG, "setCurrentProfile")
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
profile = prefs.getString(DolbyConstants.PREF_PROFILE, "0" /*dynamic*/)!!.toInt()
|
||||
}
|
||||
|
||||
fun setDsOnAndPersist(dsOn: Boolean) {
|
||||
this.dsOn = dsOn
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.putBoolean(DolbyConstants.PREF_ENABLE, dsOn)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun getProfileName(): String? {
|
||||
val profile = dolbyEffect.profile.toString()
|
||||
val profiles = context.resources.getStringArray(R.array.dolby_profile_values)
|
||||
val profileIndex = profiles.indexOf(profile)
|
||||
dlog(TAG, "getProfileName: profile=$profile index=$profileIndex")
|
||||
return if (profileIndex == -1) null else context.resources.getStringArray(
|
||||
R.array.dolby_profile_entries
|
||||
)[profileIndex]
|
||||
}
|
||||
|
||||
fun resetProfileSpecificSettings() {
|
||||
dlog(TAG, "resetProfileSpecificSettings")
|
||||
checkEffect()
|
||||
dolbyEffect.resetProfileSpecificSettings()
|
||||
context.deleteSharedPreferences("profile_$profile")
|
||||
}
|
||||
|
||||
fun getPreset(profile: Int = this.profile): String {
|
||||
val gains = dolbyEffect.getDapParameter(DsParam.GEQ_BAND_GAINS, profile)
|
||||
return gains.joinToString(separator = ",").also {
|
||||
dlog(TAG, "getPreset: $it")
|
||||
}
|
||||
}
|
||||
|
||||
fun setPreset(value: String, profile: Int = this.profile) {
|
||||
dlog(TAG, "setPreset: $value")
|
||||
checkEffect()
|
||||
val gains = value.split(",")
|
||||
.map { it.toInt() }
|
||||
.toIntArray()
|
||||
dolbyEffect.setDapParameter(DsParam.GEQ_BAND_GAINS, gains, profile)
|
||||
}
|
||||
|
||||
fun getPresetName(): String {
|
||||
val presets = context.resources.getStringArray(R.array.dolby_preset_values)
|
||||
val presetIndex = presets.indexOf(getPreset())
|
||||
return if (presetIndex == -1) {
|
||||
"Custom"
|
||||
} else {
|
||||
context.resources.getStringArray(
|
||||
R.array.dolby_preset_entries
|
||||
)[presetIndex]
|
||||
}
|
||||
}
|
||||
|
||||
fun getHeadphoneVirtEnabled(profile: Int = this.profile) =
|
||||
dolbyEffect.getDapParameterBool(DsParam.HEADPHONE_VIRTUALIZER, profile).also {
|
||||
dlog(TAG, "getHeadphoneVirtEnabled: $it")
|
||||
}
|
||||
|
||||
fun setHeadphoneVirtEnabled(value: Boolean, profile: Int = this.profile) {
|
||||
dlog(TAG, "setHeadphoneVirtEnabled: $value")
|
||||
checkEffect()
|
||||
dolbyEffect.setDapParameter(DsParam.HEADPHONE_VIRTUALIZER, value, profile)
|
||||
}
|
||||
|
||||
fun getSpeakerVirtEnabled(profile: Int = this.profile) =
|
||||
dolbyEffect.getDapParameterBool(DsParam.SPEAKER_VIRTUALIZER, profile).also {
|
||||
dlog(TAG, "getSpeakerVirtEnabled: $it")
|
||||
}
|
||||
|
||||
fun setSpeakerVirtEnabled(value: Boolean, profile: Int = this.profile) {
|
||||
dlog(TAG, "setSpeakerVirtEnabled: $value")
|
||||
checkEffect()
|
||||
dolbyEffect.setDapParameter(DsParam.SPEAKER_VIRTUALIZER, value, profile)
|
||||
}
|
||||
|
||||
fun getBassEnhancerEnabled(profile: Int = this.profile) =
|
||||
dolbyEffect.getDapParameterBool(DsParam.BASS_ENHANCER_ENABLE, profile).also {
|
||||
dlog(TAG, "getBassEnhancerEnabled: $it")
|
||||
}
|
||||
|
||||
fun setBassEnhancerEnabled(value: Boolean, profile: Int = this.profile) {
|
||||
dlog(TAG, "setBassEnhancerEnabled: $value")
|
||||
checkEffect()
|
||||
dolbyEffect.setDapParameter(DsParam.BASS_ENHANCER_ENABLE, value, profile)
|
||||
}
|
||||
|
||||
fun getVolumeLevelerEnabled(profile: Int = this.profile) =
|
||||
dolbyEffect.getDapParameterBool(DsParam.VOLUME_LEVELER_ENABLE, profile).also {
|
||||
dlog(TAG, "getVolumeLevelerEnabled: $it")
|
||||
}
|
||||
|
||||
fun setVolumeLevelerEnabled(value: Boolean, profile: Int = this.profile) {
|
||||
dlog(TAG, "setVolumeLevelerEnabled: $value")
|
||||
checkEffect()
|
||||
dolbyEffect.setDapParameter(DsParam.VOLUME_LEVELER_ENABLE, value, profile)
|
||||
}
|
||||
|
||||
fun getStereoWideningAmount(profile: Int = this.profile) =
|
||||
dolbyEffect.getDapParameterInt(DsParam.STEREO_WIDENING_AMOUNT, profile).also {
|
||||
dlog(TAG, "getStereoWideningAmount: $it")
|
||||
}
|
||||
|
||||
fun setStereoWideningAmount(value: Int, profile: Int = this.profile) {
|
||||
dlog(TAG, "setStereoWideningAmount: $value")
|
||||
checkEffect()
|
||||
dolbyEffect.setDapParameter(DsParam.STEREO_WIDENING_AMOUNT, value, profile)
|
||||
}
|
||||
|
||||
fun getDialogueEnhancerAmount(profile: Int = this.profile): Int {
|
||||
val enabled = dolbyEffect.getDapParameterBool(DsParam.DIALOGUE_ENHANCER_ENABLE, profile)
|
||||
val amount = if (enabled) {
|
||||
dolbyEffect.getDapParameterInt(DsParam.DIALOGUE_ENHANCER_AMOUNT, profile)
|
||||
} else 0
|
||||
dlog(TAG, "getDialogueEnhancerAmount: enabled=$enabled amount=$amount")
|
||||
return amount
|
||||
}
|
||||
|
||||
fun setDialogueEnhancerAmount(value: Int, profile: Int = this.profile) {
|
||||
dlog(TAG, "setDialogueEnhancerAmount: $value")
|
||||
checkEffect()
|
||||
dolbyEffect.setDapParameter(DsParam.DIALOGUE_ENHANCER_ENABLE, (value > 0), profile)
|
||||
dolbyEffect.setDapParameter(DsParam.DIALOGUE_ENHANCER_AMOUNT, value, profile)
|
||||
}
|
||||
|
||||
fun getIeqPreset(profile: Int = this.profile) =
|
||||
dolbyEffect.getDapParameterInt(DsParam.IEQ_PRESET, profile).also {
|
||||
dlog(TAG, "getIeqPreset: $it")
|
||||
}
|
||||
|
||||
fun setIeqPreset(value: Int, profile: Int = this.profile) {
|
||||
dlog(TAG, "setIeqPreset: $value")
|
||||
checkEffect()
|
||||
dolbyEffect.setDapParameter(DsParam.IEQ_PRESET, value, profile)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DolbyController"
|
||||
private const val EFFECT_PRIORITY = 100
|
||||
|
||||
@Volatile
|
||||
private var instance: DolbyController? = null
|
||||
|
||||
fun getInstance(context: Context) =
|
||||
instance ?: synchronized(this) {
|
||||
instance ?: DolbyController(context).also { instance = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
36
dolby/src/co/aospa/dolby/xiaomi/DolbyTileService.kt
Normal file
36
dolby/src/co/aospa/dolby/xiaomi/DolbyTileService.kt
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2023-24 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi
|
||||
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
|
||||
private const val TAG = "DolbyTileService"
|
||||
|
||||
class DolbyTileService : TileService() {
|
||||
|
||||
private val dolbyController by lazy { DolbyController.getInstance(applicationContext) }
|
||||
|
||||
override fun onStartListening() {
|
||||
qsTile.apply {
|
||||
state = if (dolbyController.dsOn) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
|
||||
subtitle = dolbyController.getProfileName() ?: getString(R.string.dolby_unknown)
|
||||
updateTile()
|
||||
}
|
||||
super.onStartListening()
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
val isDsOn = dolbyController.dsOn
|
||||
dolbyController.setDsOnAndPersist(!isDsOn) // toggle
|
||||
qsTile.apply {
|
||||
state = if (isDsOn) Tile.STATE_INACTIVE else Tile.STATE_ACTIVE
|
||||
updateTile()
|
||||
}
|
||||
super.onClick()
|
||||
}
|
||||
}
|
||||
71
dolby/src/co/aospa/dolby/xiaomi/SummaryProvider.kt
Normal file
71
dolby/src/co/aospa/dolby/xiaomi/SummaryProvider.kt
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
* (C) 2023-24 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
import com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY
|
||||
|
||||
private const val KEY_DOLBY = "dolby"
|
||||
|
||||
/** Provide preference summary for injected items. */
|
||||
class SummaryProvider : ContentProvider() {
|
||||
|
||||
override fun call(
|
||||
method: String,
|
||||
arg: String?,
|
||||
extras: Bundle?
|
||||
): Bundle? {
|
||||
val summary = when (method) {
|
||||
KEY_DOLBY -> getDolbySummary()
|
||||
else -> return null
|
||||
}
|
||||
return Bundle().apply {
|
||||
putString(META_DATA_PREFERENCE_SUMMARY, summary)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean = true
|
||||
|
||||
override fun query(
|
||||
uri: Uri,
|
||||
projection: Array<String>?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<String>?,
|
||||
sortOrder: String?
|
||||
): Cursor? = null
|
||||
|
||||
override fun getType(uri: Uri): String? = null
|
||||
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0
|
||||
|
||||
override fun update(
|
||||
uri: Uri,
|
||||
values: ContentValues?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<String>?
|
||||
): Int = 0
|
||||
|
||||
private fun getDolbySummary(): String {
|
||||
val context = context!!
|
||||
val dolbyController = DolbyController.getInstance(context)
|
||||
if (!dolbyController.dsOn) {
|
||||
return context.getString(R.string.dolby_off)
|
||||
}
|
||||
return dolbyController.getProfileName()?.let {
|
||||
context.getString(R.string.dolby_on_with_profile, it)
|
||||
} ?: context.getString(R.string.dolby_on)
|
||||
}
|
||||
|
||||
}
|
||||
53
dolby/src/co/aospa/dolby/xiaomi/geq/EqualizerActivity.kt
Normal file
53
dolby/src/co/aospa/dolby/xiaomi/geq/EqualizerActivity.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
import co.aospa.dolby.xiaomi.geq.ui.EqualizerScreen
|
||||
import co.aospa.dolby.xiaomi.geq.ui.EqualizerViewModel
|
||||
import com.android.settingslib.spa.framework.compose.localNavController
|
||||
import com.android.settingslib.spa.framework.theme.SettingsTheme
|
||||
import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
|
||||
|
||||
class EqualizerActivity : ComponentActivity() {
|
||||
|
||||
private val viewModel: EqualizerViewModel by viewModels { EqualizerViewModel.Factory }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
SettingsTheme {
|
||||
MainContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainContent() {
|
||||
val navController = rememberNavController()
|
||||
CompositionLocalProvider(navController.localNavController()) {
|
||||
SettingsScaffold(
|
||||
title = stringResource(id = R.string.dolby_preset)
|
||||
) { paddingValues ->
|
||||
EqualizerScreen(
|
||||
viewModel = viewModel,
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
dolby/src/co/aospa/dolby/xiaomi/geq/data/BandGain.kt
Normal file
12
dolby/src/co/aospa/dolby/xiaomi/geq/data/BandGain.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.data
|
||||
|
||||
data class BandGain(
|
||||
val band: Int,
|
||||
var gain: Int = 0
|
||||
)
|
||||
183
dolby/src/co/aospa/dolby/xiaomi/geq/data/EqualizerRepository.kt
Normal file
183
dolby/src/co/aospa/dolby/xiaomi/geq/data/EqualizerRepository.kt
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_PRESET
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.dlog
|
||||
import co.aospa.dolby.xiaomi.DolbyController
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class EqualizerRepository(
|
||||
private val context: Context
|
||||
) {
|
||||
|
||||
private val dolbyController by lazy { DolbyController.getInstance(context) }
|
||||
|
||||
// Preset is saved as a string of comma separated gains in SharedPreferences
|
||||
// and is unique to each profile ID
|
||||
private val profile = dolbyController.profile
|
||||
private val profileSharedPrefs by lazy {
|
||||
context.getSharedPreferences(
|
||||
"profile_$profile",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
}
|
||||
|
||||
private val presetsSharedPrefs by lazy {
|
||||
context.getSharedPreferences(
|
||||
"presets",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
}
|
||||
|
||||
val builtInPresets: List<Preset> by lazy {
|
||||
val names = context.resources.getStringArray(
|
||||
R.array.dolby_preset_entries
|
||||
)
|
||||
val presets = context.resources.getStringArray(
|
||||
R.array.dolby_preset_values
|
||||
)
|
||||
List(names.size) { index ->
|
||||
Preset(
|
||||
name = names[index],
|
||||
bandGains = deserializeGains(presets[index]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val defaultPreset by lazy { builtInPresets[0] } // Flat
|
||||
|
||||
// User defined presets are stored in a SharedPreferences as
|
||||
// key - preset name
|
||||
// value - comma separated string of gains
|
||||
val userPresets: Flow<List<Preset>> = callbackFlow {
|
||||
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
|
||||
dlog(TAG, "presetsSharedPrefs changed")
|
||||
trySend(
|
||||
presetsSharedPrefs.all.map { (key, value) ->
|
||||
Preset(
|
||||
name = key,
|
||||
bandGains = deserializeGains(value.toString()),
|
||||
isUserDefined = true
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
presetsSharedPrefs.registerOnSharedPreferenceChangeListener(listener)
|
||||
dlog(TAG, "presetsSharedPrefs registered listener")
|
||||
// trigger an initial emission
|
||||
listener.onSharedPreferenceChanged(presetsSharedPrefs, null)
|
||||
|
||||
awaitClose {
|
||||
presetsSharedPrefs.unregisterOnSharedPreferenceChangeListener(listener)
|
||||
dlog(TAG, "presetsSharedPrefs unregistered listener")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getBandGains(): List<BandGain> = withContext(Dispatchers.IO) {
|
||||
val gains = profileSharedPrefs.getString(PREF_PRESET, dolbyController.getPreset())
|
||||
return@withContext if (gains.isNullOrEmpty()) {
|
||||
defaultPreset.bandGains
|
||||
} else {
|
||||
deserializeGains(gains)
|
||||
}.also {
|
||||
dlog(TAG, "getBandGains: $it")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setBandGains(bandGains: List<BandGain>) = withContext(Dispatchers.IO) {
|
||||
dlog(TAG, "setBandGains($bandGains)")
|
||||
val gains = serializeGains(bandGains)
|
||||
dolbyController.setPreset(gains)
|
||||
profileSharedPrefs.edit()
|
||||
.putString(PREF_PRESET, gains)
|
||||
.apply()
|
||||
}
|
||||
|
||||
suspend fun addPreset(preset: Preset) = withContext(Dispatchers.IO) {
|
||||
dlog(TAG, "addPreset($preset)")
|
||||
presetsSharedPrefs.edit()
|
||||
.putString(preset.name, serializeGains(preset.bandGains))
|
||||
.apply()
|
||||
}
|
||||
|
||||
suspend fun removePreset(preset: Preset) = withContext(Dispatchers.IO) {
|
||||
dlog(TAG, "removePreset($preset)")
|
||||
presetsSharedPrefs.edit()
|
||||
.remove(preset.name)
|
||||
.apply()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val TAG = "EqRepository"
|
||||
|
||||
val tenBandFreqs = intArrayOf(
|
||||
32,
|
||||
64,
|
||||
125,
|
||||
250,
|
||||
500,
|
||||
1000,
|
||||
2000,
|
||||
4000,
|
||||
8000,
|
||||
16000
|
||||
)
|
||||
|
||||
fun deserializeGains(bandGains: String): List<BandGain> {
|
||||
val gains: List<Int> =
|
||||
bandGains.split(",").runCatching {
|
||||
require(size == 20) {
|
||||
"Preset must have 20 elements, has only $size!"
|
||||
}
|
||||
map { it.toInt() }
|
||||
.twentyToTenBandGains()
|
||||
}.onFailure { exception ->
|
||||
Log.e(TAG, "Failed to parse preset", exception)
|
||||
}.getOrDefault(
|
||||
// fallback to flat
|
||||
List<Int>(10) { 0 }
|
||||
)
|
||||
return List(10) { index ->
|
||||
BandGain(
|
||||
band = tenBandFreqs[index],
|
||||
gain = gains[index]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun serializeGains(bandGains: List<BandGain>): String {
|
||||
return bandGains.map { it.gain }
|
||||
.tenToTwentyBandGains()
|
||||
.joinToString(",")
|
||||
}
|
||||
|
||||
// we show only 10 bands in UI however backend requires 20 bands
|
||||
fun List<Int>.tenToTwentyBandGains() =
|
||||
List<Int>(20) { index ->
|
||||
if (index % 2 == 1 && index < 19) {
|
||||
// every odd element is the average of its surrounding elements
|
||||
(this[(index - 1) / 2] + this[(index + 1) / 2]) / 2
|
||||
} else {
|
||||
this[index / 2]
|
||||
}
|
||||
}
|
||||
|
||||
fun List<Int>.twentyToTenBandGains() =
|
||||
// skip every odd element
|
||||
filterIndexed { index, _ -> index % 2 == 0 }
|
||||
}
|
||||
}
|
||||
14
dolby/src/co/aospa/dolby/xiaomi/geq/data/Preset.kt
Normal file
14
dolby/src/co/aospa/dolby/xiaomi/geq/data/Preset.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.data
|
||||
|
||||
data class Preset(
|
||||
var name: String,
|
||||
val bandGains: List<BandGain>,
|
||||
var isUserDefined: Boolean = false,
|
||||
var isMutated: Boolean = false
|
||||
)
|
||||
101
dolby/src/co/aospa/dolby/xiaomi/geq/ui/BandGainSlider.kt
Normal file
101
dolby/src/co/aospa/dolby/xiaomi/geq/ui/BandGainSlider.kt
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import co.aospa.dolby.xiaomi.geq.data.BandGain
|
||||
|
||||
@Composable
|
||||
fun BandGainSlider(
|
||||
bandGain: BandGain,
|
||||
onValueChangeFinished: (Int) -> Unit
|
||||
) {
|
||||
// Gain range is of -1->1 in UI, -100->100 in backend, but actually is -10->10 dB.
|
||||
|
||||
// Ensure we update the slider when gain is changed,
|
||||
// for eg. when changing the preset
|
||||
var sliderPosition by remember(bandGain.gain) {
|
||||
mutableFloatStateOf(bandGain.gain / 100f)
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SliderText(
|
||||
"%.1f".format(sliderPosition * 10f)
|
||||
)
|
||||
Slider(
|
||||
value = sliderPosition,
|
||||
onValueChange = { sliderPosition = it },
|
||||
onValueChangeFinished = {
|
||||
onValueChangeFinished((sliderPosition * 100f).toInt())
|
||||
},
|
||||
valueRange = -1f..1f,
|
||||
modifier = Modifier
|
||||
.graphicsLayer {
|
||||
rotationZ = 270f
|
||||
transformOrigin = TransformOrigin(0f, 0f)
|
||||
}
|
||||
.layout { measurable, constraints ->
|
||||
val placeable = measurable.measure(
|
||||
Constraints(
|
||||
minWidth = constraints.minHeight,
|
||||
maxWidth = constraints.maxHeight,
|
||||
minHeight = constraints.minWidth,
|
||||
maxHeight = constraints.maxHeight,
|
||||
)
|
||||
)
|
||||
layout(placeable.height, placeable.width) {
|
||||
placeable.place(-placeable.width, 0)
|
||||
}
|
||||
}
|
||||
// horizontal and vertical dimensions are inverted due to rotation
|
||||
.width(200.dp)
|
||||
.height(40.dp)
|
||||
.padding(8.dp)
|
||||
)
|
||||
SliderText(
|
||||
with(bandGain.band) {
|
||||
if (this >= 1000) {
|
||||
"${this / 1000}k"
|
||||
} else {
|
||||
"$this"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SliderText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
|
||||
@Composable
|
||||
fun BandGainSliderLabels() {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
) {
|
||||
LabelText(
|
||||
stringResource(id = R.string.dolby_geq_slider_label_gain)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.height(200.dp),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
LabelText(
|
||||
"+10 dB",
|
||||
modifier = Modifier.padding(
|
||||
top = 10.dp
|
||||
)
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
LabelText("0 dB")
|
||||
Spacer(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
LabelText(
|
||||
"-10 dB",
|
||||
modifier = Modifier.padding(
|
||||
bottom = 10.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
LabelText("Hz")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LabelText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
58
dolby/src/co/aospa/dolby/xiaomi/geq/ui/ConfirmationDialog.kt
Normal file
58
dolby/src/co/aospa/dolby/xiaomi/geq/ui/ConfirmationDialog.kt
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun ConfirmationDialog(
|
||||
text: String,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(true) }
|
||||
if (!showDialog) {
|
||||
onDismiss()
|
||||
return
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
showDialog = false
|
||||
onConfirm()
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
stringResource(id = android.R.string.ok)
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = { showDialog = false }
|
||||
) {
|
||||
Text(
|
||||
stringResource(id = android.R.string.cancel)
|
||||
)
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Text(text)
|
||||
}
|
||||
)
|
||||
}
|
||||
39
dolby/src/co/aospa/dolby/xiaomi/geq/ui/EqualizerBands.kt
Normal file
39
dolby/src/co/aospa/dolby/xiaomi/geq/ui/EqualizerBands.kt
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun EqualizerBands(viewModel: EqualizerViewModel) {
|
||||
val preset by viewModel.preset.collectAsState()
|
||||
val bandGains = preset.bandGains
|
||||
|
||||
LazyRow(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
item {
|
||||
BandGainSliderLabels()
|
||||
}
|
||||
items(bandGains.size) { index ->
|
||||
BandGainSlider(
|
||||
bandGains[index],
|
||||
onValueChangeFinished = {
|
||||
viewModel.setGain(index, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
42
dolby/src/co/aospa/dolby/xiaomi/geq/ui/EqualizerScreen.kt
Normal file
42
dolby/src/co/aospa/dolby/xiaomi/geq/ui/EqualizerScreen.kt
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.settingslib.spa.framework.theme.settingsBackground
|
||||
import com.android.settingslib.spa.framework.theme.SettingsDimension
|
||||
|
||||
@Composable
|
||||
fun EqualizerScreen(
|
||||
viewModel: EqualizerViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(SettingsDimension.itemPadding)
|
||||
.then(modifier),
|
||||
color = MaterialTheme.colorScheme.settingsBackground
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier = Modifier.fillMaxHeight()
|
||||
) {
|
||||
PresetSelector(viewModel = viewModel)
|
||||
EqualizerBands(viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
175
dolby/src/co/aospa/dolby/xiaomi/geq/ui/EqualizerViewModel.kt
Normal file
175
dolby/src/co/aospa/dolby/xiaomi/geq/ui/EqualizerViewModel.kt
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import co.aospa.dolby.xiaomi.geq.data.EqualizerRepository
|
||||
import co.aospa.dolby.xiaomi.geq.data.Preset
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.dlog
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
const val TAG = "EqViewModel"
|
||||
|
||||
class EqualizerViewModel(
|
||||
private val repository: EqualizerRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val _presets = MutableStateFlow(repository.builtInPresets)
|
||||
val presets = _presets.asStateFlow()
|
||||
|
||||
private val _preset = MutableStateFlow(repository.defaultPreset)
|
||||
val preset = _preset.asStateFlow()
|
||||
|
||||
private var presetRestored = false
|
||||
|
||||
init {
|
||||
// Update the list of presets: combined list of user defined presets if any,
|
||||
// and then the built in presets.
|
||||
repository.userPresets
|
||||
.onEach { presets ->
|
||||
dlog(TAG, "updated userPresets: $presets")
|
||||
_presets.value = mutableListOf<Preset>().apply {
|
||||
addAll(presets)
|
||||
addAll(repository.builtInPresets)
|
||||
}.toList()
|
||||
|
||||
// We can restore the active preset only after the presets list is populated,
|
||||
// since we do not save the preset name but only its gains.
|
||||
if (!presetRestored) {
|
||||
val bandGains = repository.getBandGains()
|
||||
_preset.value = _presets.value.find {
|
||||
bandGains == it.bandGains
|
||||
} ?: Preset(
|
||||
name = "Custom",
|
||||
bandGains = bandGains
|
||||
)
|
||||
dlog(TAG, "restored preset: ${_preset.value}")
|
||||
presetRestored = true
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
// Update the preset in repository everytime we set it here
|
||||
_preset
|
||||
.drop(1) // skip the initial value
|
||||
.onEach {
|
||||
// wait till the active preset is restored
|
||||
if (!presetRestored) {
|
||||
return@onEach
|
||||
}
|
||||
dlog(TAG, "updated preset: $it")
|
||||
repository.setBandGains(it.bandGains)
|
||||
if (it.isUserDefined) {
|
||||
repository.addPreset(it)
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
dlog(TAG, "reset()")
|
||||
if (_preset.value.isUserDefined) {
|
||||
// Reset gains to 0
|
||||
_preset.value = _preset.value.copy(
|
||||
bandGains = repository.defaultPreset.bandGains
|
||||
)
|
||||
} else {
|
||||
// Switch to flat preset
|
||||
_preset.value = repository.defaultPreset
|
||||
}
|
||||
}
|
||||
|
||||
fun setPreset(preset: Preset) {
|
||||
dlog(TAG, "setPreset($preset)")
|
||||
_preset.value = preset
|
||||
}
|
||||
|
||||
fun setGain(index: Int, gain: Int) {
|
||||
dlog(TAG, "setGain($index, $gain)")
|
||||
_preset.value = _preset.value.run {
|
||||
copy(
|
||||
name = if (!isUserDefined) "Custom" else name,
|
||||
bandGains = bandGains
|
||||
.toMutableList()
|
||||
// create a new object to ensure the flow emits an update.
|
||||
.apply { this[index] = this[index].copy(gain = gain) }
|
||||
.toList(),
|
||||
isMutated = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns string containing the error message if it failed, otherwise null
|
||||
private fun validatePresetName(name: String): PresetNameValidationError? {
|
||||
// Ensure we don't have another preset with the same name
|
||||
return if (
|
||||
_presets.value
|
||||
.any { it.name.equals(name.trim(), ignoreCase = true) }
|
||||
) {
|
||||
PresetNameValidationError.NAME_EXISTS
|
||||
} else if (name.length > 50) {
|
||||
PresetNameValidationError.NAME_TOO_LONG
|
||||
} else null
|
||||
}
|
||||
|
||||
fun createNewPreset(name: String): PresetNameValidationError? {
|
||||
dlog(TAG, "createNewPreset($name)")
|
||||
validatePresetName(name)?.let {
|
||||
dlog(TAG, "createNewPreset failed: $it")
|
||||
return it
|
||||
}
|
||||
_preset.value = _preset.value.copy(
|
||||
name = name.trim(),
|
||||
isUserDefined = true,
|
||||
isMutated = false
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
fun renamePreset(preset: Preset, name: String): PresetNameValidationError? {
|
||||
dlog(TAG, "renamePreset($preset, $name)")
|
||||
// create a preset with the new name and same gains
|
||||
createNewPreset(name = name)?.let {
|
||||
dlog(TAG, "renamePreset failed")
|
||||
return it
|
||||
}
|
||||
// and delete the old one.
|
||||
deletePreset(preset, shouldReset = false)
|
||||
return null
|
||||
}
|
||||
|
||||
fun deletePreset(preset: Preset, shouldReset: Boolean = true) {
|
||||
dlog(TAG, "deletePreset($preset)")
|
||||
viewModelScope.launch {
|
||||
repository.removePreset(preset)
|
||||
}
|
||||
if (shouldReset) {
|
||||
_preset.value = repository.defaultPreset
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
EqualizerViewModel(
|
||||
repository = EqualizerRepository(
|
||||
this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
dolby/src/co/aospa/dolby/xiaomi/geq/ui/PresetNameDialog.kt
Normal file
94
dolby/src/co/aospa/dolby/xiaomi/geq/ui/PresetNameDialog.kt
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
|
||||
@Composable
|
||||
fun PresetNameDialog(
|
||||
title: String,
|
||||
presetName: String = "",
|
||||
onPresetNameSet: (String) -> PresetNameValidationError?,
|
||||
onDismissDialog: () -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(true) }
|
||||
if (!showDialog) {
|
||||
onDismissDialog()
|
||||
return
|
||||
}
|
||||
var text by remember { mutableStateOf(presetName) }
|
||||
var error by remember { mutableStateOf<PresetNameValidationError?>(null) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onPresetNameSet(text)?.let {
|
||||
// validation failed
|
||||
error = it
|
||||
return@TextButton
|
||||
}
|
||||
// succeeded
|
||||
showDialog = false
|
||||
error = null
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
stringResource(id = android.R.string.ok)
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = { showDialog = false }
|
||||
) {
|
||||
Text(
|
||||
stringResource(id = android.R.string.cancel)
|
||||
)
|
||||
}
|
||||
},
|
||||
title = { Text(title) },
|
||||
text = {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
label = {
|
||||
Text(
|
||||
stringResource(id = R.string.dolby_geq_preset_name)
|
||||
)
|
||||
},
|
||||
isError = error != null,
|
||||
singleLine = true
|
||||
)
|
||||
error?.let {
|
||||
Text(
|
||||
text = it.toErrorMessage(),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
|
||||
enum class PresetNameValidationError {
|
||||
NAME_EXISTS,
|
||||
NAME_TOO_LONG;
|
||||
|
||||
@Composable
|
||||
fun toErrorMessage() =
|
||||
stringResource(
|
||||
id = when (this) {
|
||||
NAME_EXISTS -> R.string.dolby_geq_preset_name_exists
|
||||
NAME_TOO_LONG -> R.string.dolby_geq_preset_name_too_long
|
||||
}
|
||||
)
|
||||
}
|
||||
176
dolby/src/co/aospa/dolby/xiaomi/geq/ui/PresetSelector.kt
Normal file
176
dolby/src/co/aospa/dolby/xiaomi/geq/ui/PresetSelector.kt
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PresetSelector(viewModel: EqualizerViewModel) {
|
||||
val presets by viewModel.presets.collectAsState()
|
||||
val currentPreset by viewModel.preset.collectAsState()
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var showNewPresetDialog by remember { mutableStateOf(false) }
|
||||
var showRenamePresetDialog by remember { mutableStateOf(false) }
|
||||
var showDeleteConfirmDialog by remember { mutableStateOf(false) }
|
||||
var showResetConfirmDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 24.dp),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded },
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.weight(1f)
|
||||
) {
|
||||
TextField(
|
||||
value = currentPreset.name,
|
||||
onValueChange = { },
|
||||
readOnly = true,
|
||||
label = {
|
||||
Text(
|
||||
stringResource(id = R.string.dolby_geq_preset)
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(
|
||||
expanded = expanded
|
||||
)
|
||||
},
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
modifier = Modifier.menuAnchor()
|
||||
// prevent keyboard from popping up
|
||||
.focusProperties { canFocus = false }
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
presets.forEach { preset ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = preset.name) },
|
||||
onClick = {
|
||||
viewModel.setPreset(preset)
|
||||
expanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TooltipIconButton(
|
||||
icon = ImageVector.vectorResource(
|
||||
id = R.drawable.save_as_24px
|
||||
),
|
||||
text = stringResource(id = R.string.dolby_geq_new_preset),
|
||||
onClick = { showNewPresetDialog = true }
|
||||
)
|
||||
|
||||
if (currentPreset.isUserDefined) {
|
||||
TooltipIconButton(
|
||||
icon = Icons.Default.Edit,
|
||||
text = stringResource(id = R.string.dolby_geq_rename_preset),
|
||||
onClick = { showRenamePresetDialog = true }
|
||||
)
|
||||
TooltipIconButton(
|
||||
icon = Icons.Default.Delete,
|
||||
text = stringResource(id = R.string.dolby_geq_delete_preset),
|
||||
onClick = { showDeleteConfirmDialog = true }
|
||||
)
|
||||
}
|
||||
|
||||
TooltipIconButton(
|
||||
icon = ImageVector.vectorResource(
|
||||
id = R.drawable.reset_settings_24px
|
||||
),
|
||||
text = stringResource(id = R.string.dolby_geq_reset_gains),
|
||||
onClick = {
|
||||
if (currentPreset.isUserDefined) {
|
||||
showResetConfirmDialog = true
|
||||
} else {
|
||||
viewModel.reset()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Dialogs
|
||||
|
||||
if (showNewPresetDialog) {
|
||||
PresetNameDialog(
|
||||
title = stringResource(id = R.string.dolby_geq_new_preset),
|
||||
onPresetNameSet = {
|
||||
return@PresetNameDialog viewModel.createNewPreset(name = it)
|
||||
},
|
||||
onDismissDialog = { showNewPresetDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showRenamePresetDialog) {
|
||||
PresetNameDialog(
|
||||
title = stringResource(id = R.string.dolby_geq_rename_preset),
|
||||
presetName = currentPreset.name,
|
||||
onPresetNameSet = {
|
||||
return@PresetNameDialog viewModel.renamePreset(
|
||||
preset = currentPreset,
|
||||
name = it
|
||||
)
|
||||
},
|
||||
onDismissDialog = { showRenamePresetDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showDeleteConfirmDialog) {
|
||||
ConfirmationDialog(
|
||||
text = stringResource(id = R.string.dolby_geq_delete_preset_prompt),
|
||||
onConfirm = { viewModel.deletePreset(currentPreset) },
|
||||
onDismiss = { showDeleteConfirmDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showResetConfirmDialog) {
|
||||
ConfirmationDialog(
|
||||
text = stringResource(id = R.string.dolby_geq_reset_gains_prompt),
|
||||
onConfirm = { viewModel.reset() },
|
||||
onDismiss = { showResetConfirmDialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
46
dolby/src/co/aospa/dolby/xiaomi/geq/ui/TooltipIconButton.kt
Normal file
46
dolby/src/co/aospa/dolby/xiaomi/geq/ui/TooltipIconButton.kt
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.geq.ui
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TooltipIconButton(
|
||||
icon: ImageVector,
|
||||
text: String,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
Text(text)
|
||||
},
|
||||
state = rememberTooltipState()
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = text,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
|
||||
// Preference with icon on the right side
|
||||
class DolbyIeqPreference(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
) : ListPreference(context, attrs) {
|
||||
|
||||
init {
|
||||
widgetLayoutResource = R.layout.ieq_icon_layout
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
super.onBindViewHolder(holder)
|
||||
val iconView = holder.findViewById(R.id.ieq_icon)!! as ImageView
|
||||
val icon = AppCompatResources.getDrawable(context, getIeqIconResId())
|
||||
iconView.setImageDrawable(icon)
|
||||
}
|
||||
|
||||
private fun getIeqIconResId(): Int {
|
||||
val ieqValue = value?.toIntOrNull() ?: 0
|
||||
return when (ieqValue) {
|
||||
0 -> R.drawable.ic_ieq_off
|
||||
1 -> R.drawable.ic_ieq_balanced
|
||||
2 -> R.drawable.ic_ieq_warm
|
||||
3 -> R.drawable.ic_ieq_detailed
|
||||
else -> 0 // should never hit this!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import androidx.preference.PreferenceManager
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants
|
||||
|
||||
class DolbyPreferenceStore(
|
||||
private val context: Context
|
||||
) : PreferenceDataStore() {
|
||||
|
||||
private val defaultSharedPrefs by lazy {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
private lateinit var profileSharedPrefs: SharedPreferences
|
||||
|
||||
var profile = 0
|
||||
set(value) {
|
||||
field = value
|
||||
profileSharedPrefs = context.getSharedPreferences(
|
||||
"profile_$value",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSharedPreferences(key: String) =
|
||||
if (DolbyConstants.PROFILE_SPECIFIC_PREFS.contains(key)) {
|
||||
profileSharedPrefs
|
||||
} else {
|
||||
defaultSharedPrefs
|
||||
}
|
||||
|
||||
override fun putBoolean(key: String, value: Boolean) =
|
||||
getSharedPreferences(key).edit()
|
||||
.putBoolean(key, value)
|
||||
.apply()
|
||||
|
||||
override fun getBoolean(key: String, defValue: Boolean) =
|
||||
getSharedPreferences(key).getBoolean(key, defValue)
|
||||
|
||||
override fun putInt(key: String, value: Int) =
|
||||
getSharedPreferences(key).edit()
|
||||
.putInt(key, value)
|
||||
.apply()
|
||||
|
||||
override fun getInt(key: String, defValue: Int) =
|
||||
getSharedPreferences(key).getInt(key, defValue)
|
||||
|
||||
override fun putString(key: String, value: String?) =
|
||||
getSharedPreferences(key).edit()
|
||||
.putString(key, value)
|
||||
.apply()
|
||||
|
||||
override fun getString(key: String, defValue: String?) =
|
||||
getSharedPreferences(key).getString(key, defValue)
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (C) 2023-24 Paranoid Android
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package co.aospa.dolby.xiaomi.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioDeviceCallback
|
||||
import android.media.AudioDeviceInfo
|
||||
import android.media.AudioManager
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.Toast
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_BASS
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_DIALOGUE
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_ENABLE
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_HP_VIRTUALIZER
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_IEQ
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_PRESET
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_PROFILE
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_RESET
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_SPK_VIRTUALIZER
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_STEREO
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.PREF_VOLUME
|
||||
import co.aospa.dolby.xiaomi.DolbyConstants.Companion.dlog
|
||||
import co.aospa.dolby.xiaomi.DolbyController
|
||||
import co.aospa.dolby.xiaomi.R
|
||||
import com.android.settingslib.widget.MainSwitchPreference
|
||||
import com.android.settingslib.widget.SettingsBasePreferenceFragment
|
||||
|
||||
class DolbySettingsFragment : SettingsBasePreferenceFragment(),
|
||||
Preference.OnPreferenceChangeListener, CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
private val appContext: Context
|
||||
get() = requireContext().applicationContext
|
||||
|
||||
private val switchBar by lazy { findPreference<MainSwitchPreference>(PREF_ENABLE)!! }
|
||||
private val profilePref by lazy { findPreference<ListPreference>(PREF_PROFILE)!! }
|
||||
private val presetPref by lazy { findPreference<Preference>(PREF_PRESET)!! }
|
||||
private val ieqPref by lazy { findPreference<DolbyIeqPreference>(PREF_IEQ)!! }
|
||||
private val stereoPref by lazy { findPreference<ListPreference>(PREF_STEREO)!! }
|
||||
private val dialoguePref by lazy { findPreference<ListPreference>(PREF_DIALOGUE)!! }
|
||||
private val bassPref by lazy { findPreference<SwitchPreferenceCompat>(PREF_BASS)!! }
|
||||
private val hpVirtPref by lazy { findPreference<SwitchPreferenceCompat>(PREF_HP_VIRTUALIZER)!! }
|
||||
private val spkVirtPref by lazy { findPreference<SwitchPreferenceCompat>(PREF_SPK_VIRTUALIZER)!! }
|
||||
private val volumePref by lazy { findPreference<SwitchPreferenceCompat>(PREF_VOLUME)!! }
|
||||
private val resetPref by lazy { findPreference<Preference>(PREF_RESET)!! }
|
||||
|
||||
private val dolbyController by lazy(LazyThreadSafetyMode.NONE) {
|
||||
DolbyController.getInstance(appContext)
|
||||
}
|
||||
private val audioManager by lazy(LazyThreadSafetyMode.NONE) {
|
||||
appContext.getSystemService(AudioManager::class.java)
|
||||
}
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
private var isOnSpeaker = true
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
dlog(TAG, "setIsOnSpeaker($value)")
|
||||
updateProfileSpecificPrefs()
|
||||
}
|
||||
|
||||
private val audioDeviceCallback = object : AudioDeviceCallback() {
|
||||
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) {
|
||||
dlog(TAG, "onAudioDevicesAdded")
|
||||
updateSpeakerState()
|
||||
}
|
||||
|
||||
override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) {
|
||||
dlog(TAG, "onAudioDevicesRemoved")
|
||||
updateSpeakerState()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
dlog(TAG, "onCreatePreferences")
|
||||
setPreferencesFromResource(R.xml.dolby_settings, rootKey)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val profile = dolbyController.profile
|
||||
preferenceManager.preferenceDataStore = DolbyPreferenceStore(appContext).also {
|
||||
it.profile = profile
|
||||
}
|
||||
|
||||
val dsOn = dolbyController.dsOn
|
||||
switchBar.addOnSwitchChangeListener(this)
|
||||
switchBar.isChecked = dsOn
|
||||
|
||||
profilePref.onPreferenceChangeListener = this
|
||||
updateProfileIcon(profile)
|
||||
profilePref.isEnabled = dsOn
|
||||
profilePref.apply {
|
||||
if (entryValues.contains(profile.toString())) {
|
||||
summary = "%s"
|
||||
value = profile.toString()
|
||||
} else {
|
||||
summary = getString(R.string.dolby_unknown)
|
||||
}
|
||||
}
|
||||
|
||||
hpVirtPref.onPreferenceChangeListener = this
|
||||
spkVirtPref.onPreferenceChangeListener = this
|
||||
stereoPref.onPreferenceChangeListener = this
|
||||
dialoguePref.onPreferenceChangeListener = this
|
||||
bassPref.onPreferenceChangeListener = this
|
||||
volumePref.onPreferenceChangeListener = this
|
||||
ieqPref.onPreferenceChangeListener = this
|
||||
|
||||
resetPref.setOnPreferenceClickListener {
|
||||
dolbyController.resetProfileSpecificSettings()
|
||||
updateProfileSpecificPrefs()
|
||||
Toast.makeText(
|
||||
appContext,
|
||||
getString(R.string.dolby_reset_profile_toast, profilePref.summary),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
true
|
||||
}
|
||||
|
||||
audioManager?.registerAudioDeviceCallback(audioDeviceCallback, handler)
|
||||
updateSpeakerState()
|
||||
updateProfileSpecificPrefs()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
dlog(TAG, "onDestroyView")
|
||||
audioManager?.unregisterAudioDeviceCallback(audioDeviceCallback)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
updateProfileSpecificPrefs()
|
||||
}
|
||||
|
||||
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
|
||||
dlog(TAG, "onPreferenceChange: key=${preference.key} value=$newValue")
|
||||
when (preference.key) {
|
||||
PREF_PROFILE -> {
|
||||
val profile = newValue.toString().toInt()
|
||||
dolbyController.profile = profile
|
||||
(preferenceManager.preferenceDataStore as DolbyPreferenceStore).profile = profile
|
||||
updateProfileIcon(profile)
|
||||
updateProfileSpecificPrefs()
|
||||
}
|
||||
PREF_SPK_VIRTUALIZER -> dolbyController.setSpeakerVirtEnabled(newValue as Boolean)
|
||||
PREF_HP_VIRTUALIZER -> dolbyController.setHeadphoneVirtEnabled(newValue as Boolean)
|
||||
PREF_STEREO -> dolbyController.setStereoWideningAmount(newValue.toString().toInt())
|
||||
PREF_DIALOGUE -> dolbyController.setDialogueEnhancerAmount(newValue.toString().toInt())
|
||||
PREF_BASS -> dolbyController.setBassEnhancerEnabled(newValue as Boolean)
|
||||
PREF_VOLUME -> dolbyController.setVolumeLevelerEnabled(newValue as Boolean)
|
||||
PREF_IEQ -> dolbyController.setIeqPreset(newValue.toString().toInt())
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
||||
dlog(TAG, "onCheckedChanged($isChecked)")
|
||||
dolbyController.dsOn = isChecked
|
||||
profilePref.isEnabled = isChecked
|
||||
updateProfileSpecificPrefs()
|
||||
}
|
||||
|
||||
private fun updateSpeakerState() {
|
||||
val devices = audioManager
|
||||
?.getDevicesForAttributes(ATTRIBUTES_MEDIA)
|
||||
.orEmpty()
|
||||
val firstType = devices.firstOrNull()?.type
|
||||
isOnSpeaker = (firstType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
|
||||
}
|
||||
|
||||
private fun updateProfileSpecificPrefs() {
|
||||
val unknownRes = getString(R.string.dolby_unknown)
|
||||
val headphoneRes = getString(R.string.dolby_connect_headphones)
|
||||
val dsOn = dolbyController.dsOn
|
||||
val currentProfile = dolbyController.profile
|
||||
|
||||
dlog(
|
||||
TAG, "updateProfileSpecificPrefs: dsOn=$dsOn currentProfile=$currentProfile"
|
||||
+ " isOnSpeaker=$isOnSpeaker"
|
||||
)
|
||||
|
||||
val enable = dsOn && (currentProfile != -1)
|
||||
presetPref.isEnabled = enable
|
||||
spkVirtPref.isEnabled = enable
|
||||
ieqPref.isEnabled = enable
|
||||
dialoguePref.isEnabled = enable
|
||||
volumePref.isEnabled = enable
|
||||
bassPref.isEnabled = enable
|
||||
resetPref.isEnabled = enable
|
||||
hpVirtPref.isEnabled = enable && !isOnSpeaker
|
||||
stereoPref.isEnabled = enable && !isOnSpeaker
|
||||
|
||||
if (!enable) return
|
||||
|
||||
presetPref.summary = dolbyController.getPresetName()
|
||||
|
||||
val ieqValue = dolbyController.getIeqPreset(currentProfile)
|
||||
ieqPref.apply {
|
||||
if (entryValues.contains(ieqValue.toString())) {
|
||||
summary = "%s"
|
||||
value = ieqValue.toString()
|
||||
} else {
|
||||
summary = unknownRes
|
||||
}
|
||||
}
|
||||
|
||||
val deValue = dolbyController.getDialogueEnhancerAmount(currentProfile).toString()
|
||||
dialoguePref.apply {
|
||||
if (entryValues.contains(deValue)) {
|
||||
summary = "%s"
|
||||
value = deValue
|
||||
} else {
|
||||
summary = unknownRes
|
||||
}
|
||||
}
|
||||
|
||||
spkVirtPref.isChecked = dolbyController.getSpeakerVirtEnabled(currentProfile)
|
||||
volumePref.isChecked = dolbyController.getVolumeLevelerEnabled(currentProfile)
|
||||
bassPref.isChecked = dolbyController.getBassEnhancerEnabled(currentProfile)
|
||||
|
||||
// below prefs are not enabled on loudspeaker
|
||||
if (isOnSpeaker) {
|
||||
stereoPref.summary = headphoneRes
|
||||
hpVirtPref.summary = headphoneRes
|
||||
return
|
||||
}
|
||||
|
||||
val swValue = dolbyController.getStereoWideningAmount(currentProfile).toString()
|
||||
stereoPref.apply {
|
||||
if (entryValues.contains(swValue)) {
|
||||
summary = "%s"
|
||||
value = swValue
|
||||
} else {
|
||||
summary = unknownRes
|
||||
}
|
||||
}
|
||||
|
||||
hpVirtPref.apply {
|
||||
isChecked = dolbyController.getHeadphoneVirtEnabled(currentProfile)
|
||||
summary = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProfileIcon(profile: Int) {
|
||||
when (profile) {
|
||||
0 -> profilePref.setIcon(R.drawable.ic_profile_dynamic)
|
||||
1 -> profilePref.setIcon(R.drawable.ic_profile_movie)
|
||||
2 -> profilePref.setIcon(R.drawable.ic_profile_music)
|
||||
3 -> profilePref.setIcon(R.drawable.ic_profile_custom)
|
||||
else -> profilePref.setIcon(R.drawable.ic_dolby)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DolbySettingsFragment"
|
||||
private val ATTRIBUTES_MEDIA = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,12 @@ package vendor.xiaomi.hardware.displayfeature@1.0;
|
||||
import @1.0::IDisplayFeatureCallback;
|
||||
|
||||
interface IDisplayFeature {
|
||||
notifyBrightness(uint32_t brightness);
|
||||
registerCallback(uint32_t displayId, IDisplayFeatureCallback callback) generates (Status status);
|
||||
sendMessage(uint32_t index, uint32_t value, string cmd);
|
||||
sendPanelCommand(string cmd) generates (Status status);
|
||||
sendPostProcCommand(uint32_t cmd, uint32_t value) generates (Status status);
|
||||
sendRefreshCommand() generates (Status status);
|
||||
setFeature(uint32_t displayId, uint32_t caseId, uint32_t modeId, uint32_t cookie) generates (Status status);
|
||||
registerCallback(uint32_t displayId, IDisplayFeatureCallback callback) generates (Status status);
|
||||
setFunction(uint32_t displayId, uint32_t caseId, uint32_t modeId, uint32_t cookie) generates (Status status);
|
||||
sendMessage(uint32_t index, uint32_t value, string cmd);
|
||||
notifyBrightness(uint32_t brightness);
|
||||
sendPanelCommand(string cmd) generates (Status status);
|
||||
sendRefreshCommand() generates (Status status);
|
||||
sendPostProcCommand(uint32_t cmd, uint32_t value) generates (Status status);
|
||||
};
|
||||
|
||||
27
interfaces/xiaomi/hardware/displayfeature/aidl/Android.bp
Normal file
27
interfaces/xiaomi/hardware/displayfeature/aidl/Android.bp
Normal file
@@ -0,0 +1,27 @@
|
||||
aidl_interface {
|
||||
name: "vendor.xiaomi.hardware.displayfeature",
|
||||
vendor_available: true,
|
||||
srcs: [
|
||||
"vendor/xiaomi/hardware/displayfeature_aidl/*.aidl",
|
||||
],
|
||||
stability: "vintf",
|
||||
backend: {
|
||||
java: {
|
||||
sdk_version: "module_current",
|
||||
min_sdk_version: "30",
|
||||
lint: {
|
||||
// Disable linter to avoid error about fixed size arrays.
|
||||
// Interface will only be accessed on devices >= U.
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
owner: "xiaomi",
|
||||
versions_with_info: [
|
||||
{
|
||||
version: "2",
|
||||
imports: [],
|
||||
},
|
||||
],
|
||||
frozen: true,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cd486b29c0e2df9789995f54ffb594530db39428
|
||||
@@ -0,0 +1,15 @@
|
||||
package vendor.xiaomi.hardware.displayfeature_aidl;
|
||||
|
||||
import vendor.xiaomi.hardware.displayfeature_aidl.IDisplayFeatureCallback;
|
||||
|
||||
@VintfStability
|
||||
interface IDisplayFeature {
|
||||
void notifyBrightness(int brightness);
|
||||
void registerCallback(int displayId, IDisplayFeatureCallback callback);
|
||||
void sendMessage(int messageId, int param, String message);
|
||||
void sendPanelCommand(String command);
|
||||
void sendPostProcCommand(int commandId, int param);
|
||||
void sendRefreshCommand();
|
||||
void setFeature(int featureId, int param1, int param2, int param3);
|
||||
void setFunction(int functionId, int param1, int param2, int param3);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package vendor.xiaomi.hardware.displayfeature_aidl;
|
||||
|
||||
@VintfStability
|
||||
interface IDisplayFeatureCallback {
|
||||
float displayfeatureInfoChanged(int displayId, int caseId, float modeId, float cookie);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package vendor.xiaomi.hardware.displayfeature_aidl;
|
||||
@VintfStability
|
||||
interface IDisplayFeature {
|
||||
void notifyBrightness(int brightness);
|
||||
void registerCallback(int displayId, vendor.xiaomi.hardware.displayfeature_aidl.IDisplayFeatureCallback callback);
|
||||
void sendMessage(int messageId, int param, String message);
|
||||
void sendPanelCommand(String command);
|
||||
void sendPostProcCommand(int commandId, int param);
|
||||
void sendRefreshCommand();
|
||||
void setFeature(int featureId, int param1, int param2, int param3);
|
||||
void setFunction(int functionId, int param1, int param2, int param3);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package vendor.xiaomi.hardware.displayfeature_aidl;
|
||||
@VintfStability
|
||||
interface IDisplayFeatureCallback {
|
||||
float displayfeatureInfoChanged(int displayId, int caseId, float modeId, float cookie);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package vendor.xiaomi.hardware.displayfeature_aidl;
|
||||
|
||||
import vendor.xiaomi.hardware.displayfeature_aidl.IDisplayFeatureCallback;
|
||||
|
||||
@VintfStability
|
||||
interface IDisplayFeature {
|
||||
void notifyBrightness(int brightness);
|
||||
void registerCallback(int displayId, IDisplayFeatureCallback callback);
|
||||
void sendMessage(int messageId, int param, String message);
|
||||
void sendPanelCommand(String command);
|
||||
void sendPostProcCommand(int commandId, int param);
|
||||
void sendRefreshCommand();
|
||||
void setFeature(int featureId, int param1, int param2, int param3);
|
||||
void setFunction(int functionId, int param1, int param2, int param3);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package vendor.xiaomi.hardware.displayfeature_aidl;
|
||||
|
||||
@VintfStability
|
||||
interface IDisplayFeatureCallback {
|
||||
float displayfeatureInfoChanged(int displayId, int caseId, float modeId, float cookie);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
aidl_interface {
|
||||
name: "vendor.xiaomi.hardware.fingerprintextension",
|
||||
vendor_available: true,
|
||||
srcs: [
|
||||
"vendor/xiaomi/hardware/fingerprintextension/*.aidl",
|
||||
],
|
||||
stability: "vintf",
|
||||
backend: {
|
||||
java: {
|
||||
sdk_version: "module_current",
|
||||
min_sdk_version: "30",
|
||||
lint: {
|
||||
// Disable linter to avoid error about fixed size arrays.
|
||||
// Interface will only be accessed on devices >= U.
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
owner: "xiaomi",
|
||||
versions_with_info: [
|
||||
{
|
||||
version: "1",
|
||||
imports: [],
|
||||
},
|
||||
],
|
||||
frozen: true,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
86a025226d979f72a91d348bd1f5e904e4ac64a1
|
||||
@@ -0,0 +1,6 @@
|
||||
package vendor.xiaomi.hardware.fingerprintextension;
|
||||
|
||||
@VintfStability
|
||||
interface IXiaomiFingerprint {
|
||||
int extCmd(int cmd, int param1);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
30ed1571b7fec61cb1386b8d3a2081c88db6283e
|
||||
@@ -0,0 +1,23 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package vendor.xiaomi.hardware.fingerprintextension;
|
||||
@VintfStability
|
||||
interface IXiaomiFingerprint {
|
||||
int extCmd(int cmd, int param1);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package vendor.xiaomi.hardware.fingerprintextension;
|
||||
|
||||
@VintfStability
|
||||
interface IXiaomiFingerprint {
|
||||
int extCmd(int cmd, int param1);
|
||||
}
|
||||
27
interfaces/xiaomi/hw/touchfeature/aidl/Android.bp
Normal file
27
interfaces/xiaomi/hw/touchfeature/aidl/Android.bp
Normal file
@@ -0,0 +1,27 @@
|
||||
aidl_interface {
|
||||
name: "vendor.xiaomi.hw.touchfeature",
|
||||
vendor_available: true,
|
||||
srcs: [
|
||||
"vendor/xiaomi/hw/touchfeature/*.aidl",
|
||||
],
|
||||
stability: "vintf",
|
||||
backend: {
|
||||
java: {
|
||||
sdk_version: "module_current",
|
||||
min_sdk_version: "30",
|
||||
lint: {
|
||||
// Disable linter to avoid error about fixed size arrays.
|
||||
// Interface will only be accessed on devices >= U.
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
owner: "xiaomi",
|
||||
versions_with_info: [
|
||||
{
|
||||
version: "1",
|
||||
imports: [],
|
||||
},
|
||||
],
|
||||
frozen: true,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
2c3f4fe227305cd02c47fd3e1f847ac139b353cf
|
||||
@@ -0,0 +1,14 @@
|
||||
package vendor.xiaomi.hw.touchfeature;
|
||||
|
||||
@VintfStability
|
||||
interface ITouchFeature {
|
||||
int getModeCurValueString(int touchId, int mode);
|
||||
int getModeValues(int touchId, int mode);
|
||||
int getTouchModeCurValue(int touchId, int mode);
|
||||
int getTouchModeDefValue(int touchId, int mode);
|
||||
int getTouchModeMaxValue(int touchId, int mode);
|
||||
int getTouchModeMinValue(int touchId, int mode);
|
||||
boolean resetTouchMode(int touchId, int mode);
|
||||
boolean setEdgeMode(int touchId, int mode, in int[] value, int length);
|
||||
void setTouchMode(int touchId, int mode, int value);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package vendor.xiaomi.hw.touchfeature;
|
||||
@VintfStability
|
||||
interface ITouchFeature {
|
||||
int getModeCurValueString(int touchId, int mode);
|
||||
int getModeValues(int touchId, int mode);
|
||||
int getTouchModeCurValue(int touchId, int mode);
|
||||
int getTouchModeDefValue(int touchId, int mode);
|
||||
int getTouchModeMaxValue(int touchId, int mode);
|
||||
int getTouchModeMinValue(int touchId, int mode);
|
||||
boolean resetTouchMode(int touchId, int mode);
|
||||
boolean setEdgeMode(int touchId, int mode, in int[] value, int length);
|
||||
void setTouchMode(int touchId, int mode, int value);
|
||||
}
|
||||
31
interfaces/xiaomi/hw/touchfeature/aidl/vendor/xiaomi/hw/touchfeature/ITouchFeature.aidl
vendored
Normal file
31
interfaces/xiaomi/hw/touchfeature/aidl/vendor/xiaomi/hw/touchfeature/ITouchFeature.aidl
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package vendor.xiaomi.hw.touchfeature;
|
||||
@VintfStability
|
||||
interface ITouchFeature {
|
||||
int getModeCurValueString(int touchId, int mode);
|
||||
int getModeValues(int touchId, int mode);
|
||||
int getTouchModeCurValue(int touchId, int mode);
|
||||
int getTouchModeDefValue(int touchId, int mode);
|
||||
int getTouchModeMaxValue(int touchId, int mode);
|
||||
int getTouchModeMinValue(int touchId, int mode);
|
||||
boolean resetTouchMode(int touchId, int mode);
|
||||
boolean setEdgeMode(int touchId, int mode, in int[] value, int length);
|
||||
void setTouchMode(int touchId, int mode, int value);
|
||||
}
|
||||
@@ -2,42 +2,19 @@
|
||||
* Copyright (C) 2022 The LineageOS Project
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
*/
|
||||
|
||||
static int stub_fail() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static char *stub_fail_str() {
|
||||
static char* stub_fail_str() {
|
||||
return "stub";
|
||||
}
|
||||
|
||||
void *mg_facepp[] = {
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail_str,
|
||||
&stub_fail_str,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
&stub_fail,
|
||||
void* mg_facepp[] = {
|
||||
&stub_fail, &stub_fail, &stub_fail, &stub_fail_str, &stub_fail_str, &stub_fail, &stub_fail,
|
||||
&stub_fail, &stub_fail, &stub_fail, &stub_fail, &stub_fail, &stub_fail, &stub_fail,
|
||||
&stub_fail, &stub_fail, &stub_fail, &stub_fail, &stub_fail, &stub_fail, &stub_fail,
|
||||
&stub_fail, &stub_fail, &stub_fail, &stub_fail, &stub_fail, &stub_fail,
|
||||
};
|
||||
|
||||
18
packages/Euicc/Android.bp
Normal file
18
packages/Euicc/Android.bp
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
//
|
||||
// SPDX-FileCopyrightText: The LineageOS Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
phony {
|
||||
name: "XiaomiEuicc",
|
||||
required: [
|
||||
"EuiccPolicy",
|
||||
"EuiccPolicyXiaomi",
|
||||
],
|
||||
}
|
||||
|
||||
runtime_resource_overlay {
|
||||
name: "EuiccPolicyXiaomi",
|
||||
product_specific: true,
|
||||
}
|
||||
10
packages/Euicc/AndroidManifest.xml
Normal file
10
packages/Euicc/AndroidManifest.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: The LineageOS Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.lineageos.euicc.overlay.xiaomi">
|
||||
<overlay android:targetPackage="org.lineageos.euicc"
|
||||
android:isStatic="true" />
|
||||
</manifest>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user