47 Commits

Author SHA1 Message Date
kondors1995
9e6e67a60b aidl/fingerprint: utilize MaxPerformance task_profile
Some checks failed
action.yml / aidl/fingerprint: utilize MaxPerformance task_profile (push) Failing after 0s
2026-01-23 08:16:24 +09:00
Pranav Vashi
ea228a83fe dolby: Update layout to material expressive design
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
2026-01-23 08:16:24 +09:00
Adithya R
5af4457253 interfaces: displayfeature: Fix method ordering
Based on analysis of stock interface.

Change-Id: Ib49d18ee15e3f7be1b3b314277357e54f953d73a
2026-01-23 08:16:24 +09:00
Aaron Kling
e73db17e01 healthd-ext: update AIDL HAL version to 3
Resolves vts failure:
For android.hardware.health.IHealth/default, manifest (targeting FCM:6)
declares version 2, but the actual version is 3

Change-Id: I8ff7278d373d6bdf906d3e1367fe7d4613552a8b
2026-01-23 08:16:24 +09:00
Fenglin Wu
48f0028833 healthd-ext: update VINTF manifest version to 2
Update VINTF manifest version to 2 to match with the latest definition
in android.hardware.health.IHealth AIDL interface.

Change-Id: Iabf4165c52cca95ff8aa75a67bbc61f4c102bb9e
(cherry picked from commit ad957d158ad1d53193536a2e0cee016836d90cd0)
2026-01-23 08:16:24 +09:00
Fabian Leutenegger
644e3d1535 healthd-ext: Fix charge_counter and ETA values
Based on 137843ec4f

Co-authored-by: Adithya R <gh0strider.2k18.reborn@gmail.com>
Change-Id: I3d000d283302a84ee9fcbd5a85ef26817b68c0a7
2026-01-23 08:16:24 +09:00
chrisl7
2f541dfbfc healthd-ext: Define override to QTI AIDL healthd-ext hal
Change-Id: I26009b1f20c02219dd371ad184cad4eafb9e8eec
2026-01-23 08:16:24 +09:00
chrisl7
10cf15c402 healthd-ext: Rename hal to xiaomi to avoid compilation conflicts
Change-Id: I8f33b6870eccfd19272cfbdb816b5b53371012c5
2026-01-23 08:16:24 +09:00
Fenglin Wu
0323f7b0e6 healthd-ext: Update AIDL HAL service installation paths
Update the installation path for vendor and recovery partition
respectively.

Change-Id: I1908d10d782127f555e2fb316f3640bc93efcdbd
2026-01-23 08:16:24 +09:00
Fenglin Wu
872e163ae9 healthd-ext: Add suspend support for charger mode
Override ChargerEnableSuspend() function to true to support kernel
suspend and resume in charger mode.

Change-Id: I01ceaecf7e918504624b2bf1bfb34207fcde74e7
2026-01-23 08:16:24 +09:00
Fenglin Wu
bdf015820e healthd-ext: Add health HAL AIDL implementation
Add health HAL AIDL implementation which is a service running for
both health HAL and charger mode.

Change-Id: I1f3205d1e34d93ed1739d5fa29c95a8f2b2d2894
2026-01-23 08:16:24 +09:00
AdarshGrewal
5ff2752c40 hardware/xiaomi: Mark setTouchMode as void
* these methods dont return any values this needs to be doe for remaining methods too probabbly
2026-01-23 08:16:24 +09:00
AdarshGrewal
8cb5e216a8 interfaces: Import reversed aidl interface for IDisplayFeature
This Interface Needed for parts fragment to make working custom saturation Slider instead of using linej livedisplay hal.
Signed-off-by: GuidixX <guidixm@gmail.com>
2026-01-23 08:16:24 +09:00
AdarshGrewal
c3061b6b82 hardware/xiaomi: fixup aidl interfaces 2026-01-23 08:16:24 +09:00
raghavt20
2407c73977 hardware/xiaomi: Import reversed aidl interface for ITouchFeature
Change-Id: Icc959b71ae4d90f81c5769de4ad16ba1a0984871
2026-01-23 08:16:24 +09:00
raghavt20
c12f2b7f19 hardware/xiaomi: Import reversed aidl interface for IXiaomiFingerprint
Change-Id: I0d60666324d986333dc10824250d90fe42ab878a
2026-01-23 08:16:24 +09:00
Piotr Kozimor
fbb6973d74 vintf: Add IMiSys HAL in compatibility matrix
Change-Id: I65bb9bf6d0aff733ca533f3c8bd5a7922058c9ff
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
Signed-off-by: firebird11 <hbgassel@gmail.com>
2026-01-23 08:16:24 +09:00
basamaryan
4d300dc97e vinf: Add {vendor.dolby_sp.hardware.dmssp, vendor.dolby_v3_6.hardware.dms360} to FCM
Change-Id: I19a0ca830469aa3978e0e89a75536d14cec735b8
2026-01-23 08:16:24 +09:00
Adithya R
a8f7cfdf39 vintf: Add more hals to fcm
Change-Id: I6645216d2665ab1d51f789b9f4f80acf411e875c
2026-01-23 08:16:24 +09:00
Pranav Vashi
1e0db9dfa6 DSPVolumeSynchronizer: Do not install for clone apps or private space
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
2026-01-23 08:16:24 +09:00
Alcatraz323
9854381e8a Implement DSPVolumeSynchronizer
Some Xiaomi devices have a speaker that needs a framework to cooperate
with DSP to synchronize volume so that the DSP can limit bass when the
volume is high to prevent distortion.

Change-Id: I750803d94161e1e7482552d2a39566f42e82fc0a
2026-01-23 08:16:24 +09:00
Abhay Singh Gill
3808a4ae68 dolby: Add custom profile
Signed-off-by: Abhay Singh Gill <abhaygill017@gmail.com>
2026-01-23 08:16:24 +09:00
Abhay Singh Gill
6b582cabb8 dolby: Add icons for dolby profiles
Also nuke voice profile and fixup some strings.

Signed-off-by: Abhay Singh Gill <abhaygill017@gmail.com>
2026-01-23 08:16:24 +09:00
Abhay Singh Gill
3c023d81a5 dolby: Make bass enhancer available on speakers as well
Signed-off-by: Abhay Singh Gill <abhaygill017@gmail.com>
2026-01-23 08:16:24 +09:00
Pranav Vashi
b84903b8ae dolby: Exempt installing package in clone or private space
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
2026-01-23 08:16:24 +09:00
Marat Budkevich
8ed00feb62 dolby: translate strings to Russian 2026-01-23 08:16:24 +09:00
Pranav Vashi
aac59d1bb9 dolby: Override AudioFx
Change-Id: I8523c10fdec7809f2872db82d85e89d076ae582a
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
2026-01-23 08:16:24 +09:00
Kevin Truong
e2a08fae39 dolby: [Tooltip] Use new positioning logic
rememberTooltipPositionProvider contains new positioning logic that should be used for both Plain and Rich tooltips.

Test: Update to use rememberTooltipPositionProvider.
Bug: b/372914353
Relnote: Added rememberTooltipPositionProvider that contains an updated positioning logic. Deprecated rememberPlainTooltipPositionProvider and rememberRichTooltipPositionProvider.
Change-Id: Ie66e2ec58567cc38fc06bb8e13ef928160db114a
2026-01-23 08:16:24 +09:00
Bruno Martins
a55e7da413 dolby: Use all shared resources from devicesettings
Change-Id: Icd7f381c574ea36eb4d797cefd60ba9f1a0941bd
2026-01-23 08:16:24 +09:00
Fabian Leutenegger
9e64f1bfa4 dolby: Update EqualizerScreen background color for 15
Switch to MaterialTheme settingsBackground for EqualizerScreen background color

Change-Id: I546e3528814276eb857a650cb6c173d914550fb5
2026-01-23 08:16:24 +09:00
Pranav Vashi
ac49d682fc dolby: Remove deprecated PlainTooltipBox
Change-Id: I70ffff5ba30c5eeaff431e46c82eaf05d46e4cb0
2026-01-23 08:16:24 +09:00
basamaryan
1c85c69ecd dolby: Fix build with kotlinc 1.9.0
Change-Id: I4f9fdc9d25eb57240612cff1b3bef3663014f9a8
Signed-off-by: Adithya R <gh0strider.2k18.reborn@gmail.com>
2026-01-23 08:16:24 +09:00
Michael Bestas
58fa6a7535 dolby: Convert to SwitchPreferenceCompat
Change-Id: Ic1cbaba37d499da1855af9c3930f2df426e2d3af
2026-01-23 08:16:24 +09:00
Chaohui Wang
4ab4637909 dolby: Migrate to CompoundButton.OnCheckedChangeListener
Switch and SwitchCompat are both CompoundButton.

Using CompoundButton in Java will helps migration in the future.

Bug: 306658427 | AOSP | AOSP
Test: manual - check Settings pages
Test: m RunSettingsLibRoboTests
Change-Id: I85a70d4c504d8584030ea4a058f30d74206ab835
2026-01-23 08:16:24 +09:00
Peter Kalauskas
a445c78924 dolby: Enable use_resource_processor for all sysui deps
This will make the build faster

Test: m checkbuild
Bug: 295208392
Change-Id: I0c1bd901429bbe3bf81c1530e156735f8637a96e
2026-01-23 08:16:23 +09:00
Adithya R
fcb804fe98 dolby: Make sure to persist value after toggling QS tile
Toggling the switch pref automatically sets the shared pref for us, but
toggling the QS tile does no such thing so we gotta do it ourselves.

Change-Id: Iac881ed654bf4eb76b111fc87667f16476d11522
2026-01-23 08:16:23 +09:00
Adithya R
3ca0074fef dolby: Add intelligent equalizer setting
Move preference-related classes to a new package while we're at it,
to reduce code clutter.

Change-Id: I2430e8ab9b6758503ce1777ec985a3e400b55b8e
2026-01-23 08:16:23 +09:00
Adithya R
a43373294b dolby: Introduce graphical equalizer
Squashed:

dolby: Refresh preset name on main screen

Change-Id: I96783e2a03c384f031787f4cc9140f7d64dadb2f
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>

Change-Id: I38ee6ce594e5671af42afc3d4bf0f004329482b9
2026-01-23 08:16:23 +09:00
Pranav Vashi
d080d60171 dolby: Add launcher icon
Change-Id: I4d36842ca96048f9b55604d66cc7741759d657f3
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
[adithya2306: Add monochrome icon as well]
Signed-off-by: Adithya R <gh0strider.2k18.reborn@gmail.com>
2026-01-23 08:16:23 +09:00
Adithya R
ab8b987d02 fixup! dolby: Restore all settings upon bootup
Stereo widening dependency on virtualizer was accidentally removed.

Change-Id: I9b1e35aef5037935af3dc18a303408e2a81ca635
2026-01-23 08:16:23 +09:00
Adithya R
1fdea7dc97 dolby: Restore current profile _after_ resetting profiles
Ensure to end the onBootCompleted routine with the correct profile set.

Change-Id: I2d5f74a7c0145af2f9d064cd98fa2dc70e5a7acd
2026-01-23 08:16:23 +09:00
Adithya R
99c749a64c dolby: Do not set volume leveler amount
This value is set to zero in almost every known dax-default.xml,
including ours.
DaxService also doesn't mess with this value, instead only sets
VolumeLevelerEnabled.

Change-Id: Ib944728d478cff58aebc4f47128bcd5fe32ff9f6
2026-01-23 08:16:23 +09:00
Adithya R
649443ae12 dolby: Restore all settings upon bootup
Dolby often messes up restoring profile-specific settings after a reboot.
"Fine. I'll do it myself."

Change-Id: Ic255c6922eabae0b522c05110f87e2c10a97fb6c
2026-01-23 08:16:23 +09:00
Adithya R
87ec3c12c3 dolby: Rewrite in Kotlin
Some cleanup and restructuring while we're at it.

Change-Id: I2f1fc53c202d91421c7b6af68c814c25398a62e4
2026-01-23 08:16:23 +09:00
Adithya R
beea2617ad dolby: Revert "Re-enable speaker virtualization after bootup"
No longer necessary

Change-Id: Iac820eafa71ea3e4ccaad2bfa0fb76c37279a22a
2026-01-23 08:16:23 +09:00
Adithya R
d3353c6ab3 xiaomi: Introduce Dolby Atmos
Moved from marble/sm8450-common

History:

commit 82fe03168c0402e4cb10d25859c3b398c0ef654a
Author: Adithya R <gh0strider.2k18.reborn@gmail.com>
Date:   Thu Mar 21 21:35:36 2024 +0530

    marble: parts: Restore dolby profile on audio changes

    Something keeps resetting back at random times, from what I observed,
    after resuming media or on a device change, lets workaround that.

    Change-Id: Id065f2482636194655c2399f0c35ad56b8e7a29d

commit c4400bd1326f65aeac1d0f26bb830ce7fd079773
Author: Adithya R <gh0strider.2k18.reborn@gmail.com>
Date:   Fri Feb 2 09:29:08 2024 +0530

    marble: parts/keyhandler: Guard debug logging

    Change-Id: I246941f26cd1f71b696eb3c996794c9baa5dbc00

commit f11b70a98a11d0b89673d73002996aed9f11fbd7
Author: Adithya R <gh0strider.2k18.reborn@gmail.com>
Date:   Sun Dec 31 20:36:52 2023 +0530

    marble: parts: Re-enable speaker virtualization after bootup

    For whatever reason, speaker virtualization isn't automatically
    restored at bootup unlike the other parameters. It was reported to be
    fixed by connecting and disconnecting headphones or disabling and
    enabling the toggle, so let's just automate that at bootup.

commit abcff4fb947c89b69c1d25bd290fd91b7873af6a
Author: Adithya R <gh0strider.2k18.reborn@gmail.com>
Date:   Fri Oct 20 06:49:19 2023 +0530

    marble: parts: Implement profile-specific Dolby settings

    Some refactoring and cleanup while we're at it.

commit dc54f9ddeff212d017b0cba16e56516e99335bb3
Author: Adithya R <gh0strider.2k18.reborn@gmail.com>
Date:   Mon Oct 9 21:58:58 2023 +0530

    marble: parts: Remove play/pause hack while toggling Dolby

    Not required with/fixed by:
    35217: audioflinger: Do not allow DAP effect to be suspended | https://gerrit.aospa.co/c/AOSPA/android_frameworks_av/+/35217

commit dd2acc8e0c10d05f86ff229412cc9f72ea242b44
Author: Adithya R <gh0strider.2k18.reborn@gmail.com>
Date:   Wed Sep 13 21:41:20 2023 +0530

    marble: parts: Set proper summary for dolby settings

    Show the current status in Settings > Sound as well as the QS tile.

commit 92d341ba3d22f323eded525487db4289d6edc0fe
Author: Fabian Leutenegger <fabian.leutenegger@bluewin.ch>
Date:   Fri Aug 25 10:26:53 2023 +0200

    marble: parts: Always refresh playback if status changed

     * otherwise dolby would stay active even if you disable its setting

    Change-Id: If59d8081fa12da2aa67e5149db97965c0805d76e

commit b1944744649b6fddcb7bc3864b92f298b6e78821
Author: Adithya R <gh0strider.2k18.reborn@gmail.com>
Date:   Mon Aug 21 13:21:18 2023 +0530

    marble: parts: Introduce Dolby Atmos

    Based on existing dirac implementation and observing stock
    sound effects app and daxservice.

    Thanks to jhenrique09 for the hack from old dirac parts
    "Pause/play music stream to get effects applied".

    TODO: bring back misound (same as stock)

    Co-authored-by: Henrique Silva <jhenrique09.mcz@hotmail.com>

Change-Id: I79841c045fe7b92c438177916f756faab72ff0e9
2026-01-23 08:16:23 +09:00
Rocky Fang
040e50155c aidl: sensors: Set dynamic_sensor_timeout to 1600 during boot
Test: on device test
Bug: 398856625
Flag: EXEMPT N/A for *.rc file

Change-Id: I6c97e6b34305934efdc833e96f0aa4374cb3d878
2026-01-23 08:16:19 +09:00
87 changed files with 3615 additions and 6 deletions

View 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,
}

View 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>

View 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>

View File

@@ -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>

View 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>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- App Name -->
<string name="app_name">DSP Volume Synchronizer</string>
</resources>

View File

@@ -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));
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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
View 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"],
}

View 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

View 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>

View File

@@ -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
View 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();
}

View File

@@ -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
View 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
View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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>

View 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>

View 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>

View 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()
}
}

View 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()
}
}

View 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
}
}
}

View 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)
}
}
}
}

View 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 }
}
}
}

View 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()
}
}

View 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)
}
}

View 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)
)
}
}
}
}

View 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
)

View 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 }
}
}

View 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
)

View 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
)
}

View File

@@ -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
)
}

View 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)
}
)
}

View 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)
}
)
}
}
}

View 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)
}
}
}

View 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]!!
)
)
}
}
}
}

View 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)
)
}
}
}
)
}

View File

@@ -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
}
)
}

View 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 }
)
}
}

View 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)
)
}
}
}

View File

@@ -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!
}
}
}

View File

@@ -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)
}

View File

@@ -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()
}
}

View File

@@ -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);
};

View 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,
}

View File

@@ -0,0 +1 @@
cd486b29c0e2df9789995f54ffb594530db39428

View File

@@ -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);
}

View File

@@ -0,0 +1,6 @@
package vendor.xiaomi.hardware.displayfeature_aidl;
@VintfStability
interface IDisplayFeatureCallback {
float displayfeatureInfoChanged(int displayId, int caseId, float modeId, float cookie);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -0,0 +1,6 @@
package vendor.xiaomi.hardware.displayfeature_aidl;
@VintfStability
interface IDisplayFeatureCallback {
float displayfeatureInfoChanged(int displayId, int caseId, float modeId, float cookie);
}

View File

@@ -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,
}

View File

@@ -0,0 +1 @@
86a025226d979f72a91d348bd1f5e904e4ac64a1

View File

@@ -0,0 +1,6 @@
package vendor.xiaomi.hardware.fingerprintextension;
@VintfStability
interface IXiaomiFingerprint {
int extCmd(int cmd, int param1);
}

View File

@@ -0,0 +1 @@
30ed1571b7fec61cb1386b8d3a2081c88db6283e

View File

@@ -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);
}

View File

@@ -0,0 +1,6 @@
package vendor.xiaomi.hardware.fingerprintextension;
@VintfStability
interface IXiaomiFingerprint {
int extCmd(int cmd, int param1);
}

View 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,
}

View File

@@ -0,0 +1 @@
2c3f4fe227305cd02c47fd3e1f847ac139b353cf

View File

@@ -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);
}

View 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);
}

View 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);
}

View File

@@ -3,6 +3,14 @@
SPDX-License-Identifier: Apache-2.0
-->
<compatibility-matrix version="1.0" type="framework">
<hal format="hidl" optional="true">
<name>android.hardware.media.c2</name>
<version>1.0</version>
<interface>
<name>IComponentStore</name>
<instance>dolby</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>com.fingerprints.extension</name>
<version>1.0</version>
@@ -89,6 +97,30 @@
<instance>default</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>vendor.dolby.hardware.dms</name>
<version>2.0</version>
<interface>
<name>IDms</name>
<instance>default</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>vendor.dolby_sp.hardware.dmssp</name>
<version>2.0</version>
<interface>
<name>IDms</name>
<instance>default</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>vendor.dolby_v3_6.hardware.dms360</name>
<version>2.0</version>
<interface>
<name>IDms</name>
<instance>default</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>vendor.goodix.hardware.fingerprintextension</name>
<version>1.0</version>
@@ -209,6 +241,17 @@
<instance>default</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>vendor.xiaomi.hardware.misys</name>
<version>1.0</version>
<version>2.0</version>
<version>3.0</version>
<version>4.0</version>
<interface>
<name>IMiSys</name>
<instance>default</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>vendor.xiaomi.hardware.mlipay</name>
<version>1.0-1</version>
@@ -249,6 +292,14 @@
<instance>default</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>vendor.xiaomi.hw.touchfeature</name>
<version>1.0</version>
<interface>
<name>ITouchFeature</name>
<instance>default</instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>vendor.xiaomi.sensor.citsensorservice</name>
<version>1.1</version>