mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-05-25 02:18:45 +00:00
remove unused hook
This commit is contained in:
@@ -2,22 +2,9 @@ package me.kavishdevar.librepods.utils
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.drawable.GradientDrawable
|
|
||||||
import android.os.ParcelUuid
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.animation.AccelerateInterpolator
|
|
||||||
import android.view.animation.DecelerateInterpolator
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import io.github.libxposed.api.XposedInterface
|
import io.github.libxposed.api.XposedInterface
|
||||||
import io.github.libxposed.api.XposedInterface.AfterHookCallback
|
import io.github.libxposed.api.XposedInterface.AfterHookCallback
|
||||||
@@ -26,6 +13,7 @@ import io.github.libxposed.api.XposedModuleInterface
|
|||||||
import io.github.libxposed.api.XposedModuleInterface.ModuleLoadedParam
|
import io.github.libxposed.api.XposedModuleInterface.ModuleLoadedParam
|
||||||
import io.github.libxposed.api.annotations.AfterInvocation
|
import io.github.libxposed.api.annotations.AfterInvocation
|
||||||
import io.github.libxposed.api.annotations.XposedHooker
|
import io.github.libxposed.api.annotations.XposedHooker
|
||||||
|
import kotlin.jvm.java
|
||||||
|
|
||||||
private const val TAG = "AirPodsHook"
|
private const val TAG = "AirPodsHook"
|
||||||
private lateinit var module: KotlinModule
|
private lateinit var module: KotlinModule
|
||||||
@@ -67,17 +55,6 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
|
|||||||
|
|
||||||
hook(updateIconMethod, BluetoothIconHooker::class.java)
|
hook(updateIconMethod, BluetoothIconHooker::class.java)
|
||||||
Log.i(TAG, "Successfully hooked updateIcon method in Bluetooth settings")
|
Log.i(TAG, "Successfully hooked updateIcon method in Bluetooth settings")
|
||||||
|
|
||||||
try {
|
|
||||||
val displayPreferenceMethod = headerControllerClass.getDeclaredMethod(
|
|
||||||
"displayPreference",
|
|
||||||
param.classLoader.loadClass("androidx.preference.PreferenceScreen"))
|
|
||||||
|
|
||||||
hook(displayPreferenceMethod, BluetoothSettingsAirPodsHooker::class.java)
|
|
||||||
Log.i(TAG, "Successfully hooked displayPreference for AirPods button injection")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to hook displayPreference: ${e.message}", e)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to hook Bluetooth icon handler: ${e.message}", e)
|
Log.e(TAG, "Failed to hook Bluetooth icon handler: ${e.message}", e)
|
||||||
}
|
}
|
||||||
@@ -96,110 +73,12 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
|
|||||||
|
|
||||||
hook(updateIconMethod, BluetoothIconHooker::class.java)
|
hook(updateIconMethod, BluetoothIconHooker::class.java)
|
||||||
Log.i(TAG, "Successfully hooked updateIcon method in Bluetooth settings")
|
Log.i(TAG, "Successfully hooked updateIcon method in Bluetooth settings")
|
||||||
|
|
||||||
try {
|
|
||||||
val displayPreferenceMethod = headerControllerClass.getDeclaredMethod(
|
|
||||||
"displayPreference",
|
|
||||||
param.classLoader.loadClass("androidx.preference.PreferenceScreen"))
|
|
||||||
|
|
||||||
hook(displayPreferenceMethod, BluetoothSettingsAirPodsHooker::class.java)
|
|
||||||
Log.i(TAG, "Successfully hooked displayPreference for AirPods button injection")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to hook displayPreference: ${e.message}", e)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to hook Bluetooth icon handler: ${e.message}", e)
|
Log.e(TAG, "Failed to hook Bluetooth icon handler: ${e.message}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@XposedHooker
|
|
||||||
class BluetoothSettingsAirPodsHooker : XposedInterface.Hooker {
|
|
||||||
companion object {
|
|
||||||
private const val AIRPODS_UUID = "74ec2172-0bad-4d01-8f77-997b2be0722a"
|
|
||||||
private const val LIBREPODS_PREFERENCE_KEY = "librepods_open_preference"
|
|
||||||
private const val ACTION_SET_ANC_MODE = "me.kavishdevar.librepods.SET_ANC_MODE"
|
|
||||||
private const val EXTRA_ANC_MODE = "anc_mode"
|
|
||||||
|
|
||||||
private const val ANC_MODE_OFF = 1
|
|
||||||
private const val ANC_MODE_NOISE_CANCELLATION = 2
|
|
||||||
private const val ANC_MODE_TRANSPARENCY = 3
|
|
||||||
private const val ANC_MODE_ADAPTIVE = 4
|
|
||||||
|
|
||||||
private var currentAncMode = ANC_MODE_NOISE_CANCELLATION
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@AfterInvocation
|
|
||||||
fun afterDisplayPreference(callback: AfterHookCallback) {
|
|
||||||
try {
|
|
||||||
val controller = callback.thisObject!!
|
|
||||||
val preferenceScreen = callback.args[0]!!
|
|
||||||
|
|
||||||
val context = preferenceScreen.javaClass.getMethod("getContext").invoke(preferenceScreen) as Context
|
|
||||||
|
|
||||||
val deviceField = controller.javaClass.getDeclaredField("mCachedDevice")
|
|
||||||
deviceField.isAccessible = true
|
|
||||||
val cachedDevice = deviceField.get(controller) ?: return
|
|
||||||
|
|
||||||
val getDeviceMethod = cachedDevice.javaClass.getMethod("getDevice")
|
|
||||||
val bluetoothDevice = getDeviceMethod.invoke(cachedDevice) ?: return
|
|
||||||
|
|
||||||
val uuidsMethod = bluetoothDevice.javaClass.getMethod("getUuids")
|
|
||||||
val uuids = uuidsMethod.invoke(bluetoothDevice) as? Array<ParcelUuid>
|
|
||||||
|
|
||||||
if (uuids != null) {
|
|
||||||
val isAirPods = uuids.any { it.uuid.toString() == AIRPODS_UUID }
|
|
||||||
|
|
||||||
if (isAirPods) {
|
|
||||||
Log.i(TAG, "AirPods device detected in settings, injecting controls")
|
|
||||||
|
|
||||||
val findPreferenceMethod = preferenceScreen.javaClass.getMethod("findPreference", CharSequence::class.java)
|
|
||||||
val existingPref = findPreferenceMethod.invoke(preferenceScreen, LIBREPODS_PREFERENCE_KEY)
|
|
||||||
|
|
||||||
if (existingPref != null) {
|
|
||||||
Log.i(TAG, "LIBREPODS button already exists, skipping")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val preferenceClass = preferenceScreen.javaClass.classLoader.loadClass("androidx.preference.Preference")
|
|
||||||
val preference = preferenceClass.getConstructor(Context::class.java).newInstance(context)
|
|
||||||
|
|
||||||
val setKeyMethod = preferenceClass.getMethod("setKey", String::class.java)
|
|
||||||
setKeyMethod.invoke(preference, LIBREPODS_PREFERENCE_KEY)
|
|
||||||
|
|
||||||
val setTitleMethod = preferenceClass.getMethod("setTitle", CharSequence::class.java)
|
|
||||||
setTitleMethod.invoke(preference, "Open LibrePods")
|
|
||||||
|
|
||||||
val setSummaryMethod = preferenceClass.getMethod("setSummary", CharSequence::class.java)
|
|
||||||
setSummaryMethod.invoke(preference, "Control AirPods features")
|
|
||||||
|
|
||||||
val setIconMethod = preferenceClass.getMethod("setIcon", Int::class.java)
|
|
||||||
setIconMethod.invoke(preference, android.R.drawable.ic_menu_manage)
|
|
||||||
|
|
||||||
val setOrderMethod = preferenceClass.getMethod("setOrder", Int::class.java)
|
|
||||||
setOrderMethod.invoke(preference, 1000)
|
|
||||||
|
|
||||||
val intent = Intent().apply {
|
|
||||||
setClassName("me.kavishdevar.librepods", "me.kavishdevar.librepods.MainActivity")
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
}
|
|
||||||
val setIntentMethod = preferenceClass.getMethod("setIntent", Intent::class.java)
|
|
||||||
setIntentMethod.invoke(preference, intent)
|
|
||||||
|
|
||||||
val addPreferenceMethod = preferenceScreen.javaClass.getMethod("addPreference", preferenceClass)
|
|
||||||
addPreferenceMethod.invoke(preferenceScreen, preference)
|
|
||||||
|
|
||||||
Log.i(TAG, "Successfully added Open LIBREPODS button to AirPods settings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error in BluetoothSettingsAirPodsHooker: ${e.message}", e)
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@XposedHooker
|
@XposedHooker
|
||||||
class BluetoothIconHooker : XposedInterface.Hooker {
|
class BluetoothIconHooker : XposedInterface.Hooker {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -267,497 +146,4 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
|
|||||||
override fun getApplicationInfo(): ApplicationInfo {
|
override fun getApplicationInfo(): ApplicationInfo {
|
||||||
return super.applicationInfo
|
return super.applicationInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val ANC_MODE_OFF = 1
|
|
||||||
private const val ANC_MODE_NOISE_CANCELLATION = 2
|
|
||||||
private const val ANC_MODE_TRANSPARENCY = 3
|
|
||||||
private const val ANC_MODE_ADAPTIVE = 4
|
|
||||||
|
|
||||||
private var currentANCMode = ANC_MODE_NOISE_CANCELLATION
|
|
||||||
|
|
||||||
private const val ACTION_SET_ANC_MODE = "me.kavishdevar.librepods.SET_ANC_MODE"
|
|
||||||
private const val EXTRA_ANC_MODE = "anc_mode"
|
|
||||||
private const val ANIMATION_DURATION = 250L
|
|
||||||
|
|
||||||
private fun addAirPodsControlsToDialog(volumeDialog: Any) {
|
|
||||||
try {
|
|
||||||
val contextField = volumeDialog.javaClass.getDeclaredField("mContext")
|
|
||||||
contextField.isAccessible = true
|
|
||||||
val context = contextField.get(volumeDialog) as Context
|
|
||||||
|
|
||||||
val dialogViewField = volumeDialog.javaClass.getDeclaredField("mDialogView")
|
|
||||||
dialogViewField.isAccessible = true
|
|
||||||
val dialogView = dialogViewField.get(volumeDialog) as ViewGroup
|
|
||||||
|
|
||||||
val dialogRowsViewField = volumeDialog.javaClass.getDeclaredField("mDialogRowsView")
|
|
||||||
dialogRowsViewField.isAccessible = true
|
|
||||||
val dialogRowsView = dialogRowsViewField.get(volumeDialog) as ViewGroup
|
|
||||||
|
|
||||||
Log.d(TAG, "Found dialogRowsView: ${dialogRowsView.javaClass.name}")
|
|
||||||
|
|
||||||
val existingContainer = dialogView.findViewWithTag<View>("airpods_container")
|
|
||||||
if (existingContainer != null) {
|
|
||||||
Log.d(TAG, "AirPods container already exists, ensuring visibility state")
|
|
||||||
val drawer = existingContainer.findViewWithTag<View>("airpods_drawer_container")
|
|
||||||
drawer?.visibility = View.GONE
|
|
||||||
drawer?.alpha = 0f
|
|
||||||
drawer?.translationY = 0f
|
|
||||||
val button = existingContainer.findViewWithTag<ImageButton>("airpods_button")
|
|
||||||
button?.visibility = View.VISIBLE
|
|
||||||
button?.alpha = 1f
|
|
||||||
if (button != null) {
|
|
||||||
updateMainButtonIcon(context, button, currentANCMode)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val newAirPodsButton = ImageButton(context).apply {
|
|
||||||
tag = "airpods_button"
|
|
||||||
|
|
||||||
try {
|
|
||||||
val airPodsPackage = context.createPackageContext(
|
|
||||||
"me.kavishdevar.librepods",
|
|
||||||
Context.CONTEXT_IGNORE_SECURITY
|
|
||||||
)
|
|
||||||
val airPodsIconRes = airPodsPackage.resources.getIdentifier(
|
|
||||||
"airpods", "drawable", "me.kavishdevar.librepods")
|
|
||||||
|
|
||||||
if (airPodsIconRes != 0) {
|
|
||||||
val airPodsDrawable = airPodsPackage.resources.getDrawable(
|
|
||||||
airPodsIconRes, airPodsPackage.theme)
|
|
||||||
setImageDrawable(airPodsDrawable)
|
|
||||||
} else {
|
|
||||||
setImageResource(android.R.drawable.ic_media_play)
|
|
||||||
Log.d(TAG, "Using fallback icon because airpods icon resource not found")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
setImageResource(android.R.drawable.ic_media_play)
|
|
||||||
Log.e(TAG, "Failed to load AirPods icon: ${e.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val shape = GradientDrawable()
|
|
||||||
shape.shape = GradientDrawable.RECTANGLE
|
|
||||||
shape.setColor(Color.BLACK)
|
|
||||||
background = shape
|
|
||||||
|
|
||||||
imageTintList = ColorStateList.valueOf(Color.WHITE)
|
|
||||||
scaleType = ImageView.ScaleType.CENTER_INSIDE
|
|
||||||
|
|
||||||
setPadding(24, 24, 24, 24)
|
|
||||||
|
|
||||||
val params = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
90
|
|
||||||
)
|
|
||||||
params.gravity = Gravity.CENTER
|
|
||||||
params.setMargins(0, 0, 0, 0)
|
|
||||||
layoutParams = params
|
|
||||||
|
|
||||||
setOnClickListener {
|
|
||||||
Log.d(TAG, "AirPods button clicked, toggling drawer")
|
|
||||||
val container = findAirPodsContainer(this)
|
|
||||||
val drawerContainer = container?.findViewWithTag<View>("airpods_drawer_container")
|
|
||||||
if (drawerContainer != null && container != null) {
|
|
||||||
if (drawerContainer.visibility == View.VISIBLE) {
|
|
||||||
hideAirPodsDrawer(container, this, drawerContainer)
|
|
||||||
} else {
|
|
||||||
showAirPodsDrawer(container, this, drawerContainer)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Could not find container or drawer for toggle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contentDescription = "AirPods Settings"
|
|
||||||
}
|
|
||||||
|
|
||||||
val airPodsContainer = FrameLayout(context).apply {
|
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
tag = "airpods_container"
|
|
||||||
}
|
|
||||||
|
|
||||||
newAirPodsButton.setOnLongClickListener {
|
|
||||||
Log.d(TAG, "AirPods button long-pressed, opening QuickSettingsDialogActivity")
|
|
||||||
val intent = Intent().apply {
|
|
||||||
setClassName("me.kavishdevar.librepods", "me.kavishdevar.librepods.QuickSettingsDialogActivity")
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
}
|
|
||||||
context.startActivity(intent)
|
|
||||||
try {
|
|
||||||
val dismissMethod = volumeDialog.javaClass.getMethod("dismissH")
|
|
||||||
dismissMethod.invoke(volumeDialog)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Could not dismiss volume dialog: ${e.message}")
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val airPodsDrawer = LinearLayout(context).apply {
|
|
||||||
orientation = LinearLayout.VERTICAL
|
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
).apply {
|
|
||||||
gravity = Gravity.TOP
|
|
||||||
}
|
|
||||||
tag = "airpods_drawer_container"
|
|
||||||
visibility = View.GONE
|
|
||||||
alpha = 0f
|
|
||||||
|
|
||||||
val drawerShape = GradientDrawable()
|
|
||||||
drawerShape.shape = GradientDrawable.RECTANGLE
|
|
||||||
drawerShape.setColor(Color.BLACK)
|
|
||||||
background = drawerShape
|
|
||||||
|
|
||||||
setPadding(16, 8, 16, 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
val buttonContainer = LinearLayout(context).apply {
|
|
||||||
orientation = LinearLayout.VERTICAL
|
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
).apply {
|
|
||||||
gravity = Gravity.TOP
|
|
||||||
}
|
|
||||||
tag = "airpods_button_container"
|
|
||||||
}
|
|
||||||
|
|
||||||
val modes = listOf(ANC_MODE_OFF, ANC_MODE_TRANSPARENCY, ANC_MODE_ADAPTIVE, ANC_MODE_NOISE_CANCELLATION)
|
|
||||||
for (mode in modes) {
|
|
||||||
val modeOption = createAncModeOption(context, mode, mode == currentANCMode, newAirPodsButton)
|
|
||||||
airPodsDrawer.addView(modeOption)
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonContainer.addView(newAirPodsButton)
|
|
||||||
|
|
||||||
airPodsContainer.addView(airPodsDrawer)
|
|
||||||
airPodsContainer.addView(buttonContainer)
|
|
||||||
|
|
||||||
val settingsViewField = try {
|
|
||||||
val field = volumeDialog.javaClass.getDeclaredField("mSettingsView")
|
|
||||||
field.isAccessible = true
|
|
||||||
field.get(volumeDialog) as? View
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to get settings view field: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settingsViewField != null && settingsViewField.parent is ViewGroup) {
|
|
||||||
val settingsParent = settingsViewField.parent as ViewGroup
|
|
||||||
val settingsIndex = findViewIndexInParent(settingsParent, settingsViewField)
|
|
||||||
|
|
||||||
if (settingsIndex >= 0) {
|
|
||||||
settingsParent.addView(airPodsContainer, settingsIndex)
|
|
||||||
Log.i(TAG, "Added AirPods controls before settings button")
|
|
||||||
} else {
|
|
||||||
settingsParent.addView(airPodsContainer)
|
|
||||||
Log.i(TAG, "Added AirPods controls to the end of settings parent")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogView.addView(airPodsContainer)
|
|
||||||
Log.i(TAG, "Fallback: Added AirPods controls to dialog view")
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMainButtonIcon(context, newAirPodsButton, currentANCMode)
|
|
||||||
|
|
||||||
Log.i(TAG, "Successfully added AirPods button and drawer to volume dialog")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error adding AirPods button to volume panel: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findViewIndexInParent(parent: ViewGroup, view: View): Int {
|
|
||||||
for (i in 0 until parent.childCount) {
|
|
||||||
if (parent.getChildAt(i) == view) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateMainButtonIcon(context: Context, button: ImageButton, mode: Int) {
|
|
||||||
try {
|
|
||||||
val pkgContext = context.createPackageContext(
|
|
||||||
"me.kavishdevar.librepods",
|
|
||||||
Context.CONTEXT_IGNORE_SECURITY
|
|
||||||
)
|
|
||||||
|
|
||||||
val resName = when (mode) {
|
|
||||||
ANC_MODE_OFF -> "noise_cancellation"
|
|
||||||
ANC_MODE_TRANSPARENCY -> "transparency"
|
|
||||||
ANC_MODE_ADAPTIVE -> "adaptive"
|
|
||||||
ANC_MODE_NOISE_CANCELLATION -> "noise_cancellation"
|
|
||||||
else -> "noise_cancellation"
|
|
||||||
}
|
|
||||||
|
|
||||||
val resId = pkgContext.resources.getIdentifier(
|
|
||||||
resName, "drawable", "me.kavishdevar.librepods"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (resId != 0) {
|
|
||||||
val drawable = pkgContext.resources.getDrawable(resId, pkgContext.theme)
|
|
||||||
button.setImageDrawable(drawable)
|
|
||||||
button.setColorFilter(Color.WHITE)
|
|
||||||
} else {
|
|
||||||
button.setImageResource(getIconResourceForMode(mode))
|
|
||||||
button.setColorFilter(Color.WHITE)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
button.setImageResource(getIconResourceForMode(mode))
|
|
||||||
button.setColorFilter(Color.WHITE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createAncModeOption(context: Context, mode: Int, isSelected: Boolean, mainButton: ImageButton): LinearLayout {
|
|
||||||
return LinearLayout(context).apply {
|
|
||||||
orientation = LinearLayout.HORIZONTAL
|
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
).apply {
|
|
||||||
setMargins(0, 6, 0, 6)
|
|
||||||
}
|
|
||||||
gravity = Gravity.CENTER
|
|
||||||
setPadding(24, 16, 24, 16)
|
|
||||||
tag = "anc_mode_${mode}"
|
|
||||||
|
|
||||||
val icon = ImageView(context).apply {
|
|
||||||
layoutParams = LinearLayout.LayoutParams(60, 60).apply {
|
|
||||||
gravity = Gravity.CENTER
|
|
||||||
}
|
|
||||||
tag = "mode_icon_$mode"
|
|
||||||
|
|
||||||
try {
|
|
||||||
val packageContext = context.createPackageContext(
|
|
||||||
"me.kavishdevar.librepods",
|
|
||||||
Context.CONTEXT_IGNORE_SECURITY
|
|
||||||
)
|
|
||||||
|
|
||||||
val resourceName = when (mode) {
|
|
||||||
ANC_MODE_OFF -> "noise_cancellation"
|
|
||||||
ANC_MODE_TRANSPARENCY -> "transparency"
|
|
||||||
ANC_MODE_ADAPTIVE -> "adaptive"
|
|
||||||
ANC_MODE_NOISE_CANCELLATION -> "noise_cancellation"
|
|
||||||
else -> "noise_cancellation"
|
|
||||||
}
|
|
||||||
|
|
||||||
val resourceId = packageContext.resources.getIdentifier(
|
|
||||||
resourceName, "drawable", "me.kavishdevar.librepods"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (resourceId != 0) {
|
|
||||||
val drawable = packageContext.resources.getDrawable(
|
|
||||||
resourceId, packageContext.theme
|
|
||||||
)
|
|
||||||
setImageDrawable(drawable)
|
|
||||||
} else {
|
|
||||||
setImageResource(getIconResourceForMode(mode))
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
setImageResource(getIconResourceForMode(mode))
|
|
||||||
Log.e(TAG, "Failed to load custom drawable for mode $mode: ${e.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSelected) {
|
|
||||||
setColorFilter(Color.BLACK)
|
|
||||||
} else {
|
|
||||||
setColorFilter(Color.WHITE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addView(icon)
|
|
||||||
|
|
||||||
background = if (isSelected) {
|
|
||||||
createSelectedBackground(context)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnClickListener {
|
|
||||||
Log.d(TAG, "ANC mode selected: $mode (was: $currentANCMode)")
|
|
||||||
val container = findAirPodsContainer(this)
|
|
||||||
val drawerContainer = container?.findViewWithTag<View>("airpods_drawer_container")
|
|
||||||
|
|
||||||
if (currentANCMode == mode) {
|
|
||||||
if (drawerContainer != null && container != null) {
|
|
||||||
hideAirPodsDrawer(container, mainButton, drawerContainer)
|
|
||||||
}
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
currentANCMode = mode
|
|
||||||
|
|
||||||
val parentDrawer = parent as? ViewGroup
|
|
||||||
if (parentDrawer != null) {
|
|
||||||
for (i in 0 until parentDrawer.childCount) {
|
|
||||||
val child = parentDrawer.getChildAt(i) as? LinearLayout
|
|
||||||
if (child != null && child.tag.toString().startsWith("anc_mode_")) {
|
|
||||||
val childModeStr = child.tag.toString().substringAfter("anc_mode_")
|
|
||||||
val childMode = childModeStr.toIntOrNull() ?: -1
|
|
||||||
val childIcon = child.findViewWithTag<ImageView>("mode_icon_${childMode}")
|
|
||||||
|
|
||||||
if (childMode == mode) {
|
|
||||||
child.background = createSelectedBackground(context)
|
|
||||||
childIcon?.setColorFilter(Color.BLACK)
|
|
||||||
} else {
|
|
||||||
child.background = null
|
|
||||||
childIcon?.setColorFilter(Color.WHITE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val intent = Intent(ACTION_SET_ANC_MODE).apply {
|
|
||||||
setPackage("me.kavishdevar.librepods")
|
|
||||||
putExtra(EXTRA_ANC_MODE, mode)
|
|
||||||
}
|
|
||||||
context.sendBroadcast(intent)
|
|
||||||
Log.d(TAG, "Sent broadcast to change ANC mode to: ${getLabelForMode(currentANCMode)}")
|
|
||||||
|
|
||||||
|
|
||||||
updateMainButtonIcon(context, mainButton, mode)
|
|
||||||
|
|
||||||
if (drawerContainer != null && container != null) {
|
|
||||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
||||||
hideAirPodsDrawer(container, mainButton, drawerContainer)
|
|
||||||
}, 50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createSelectedBackground(context: Context): GradientDrawable {
|
|
||||||
return GradientDrawable().apply {
|
|
||||||
shape = GradientDrawable.RECTANGLE
|
|
||||||
setColor(Color.WHITE)
|
|
||||||
cornerRadius = 50f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findAirPodsContainer(view: View): ViewGroup? {
|
|
||||||
var current: View? = view
|
|
||||||
while (current != null) {
|
|
||||||
if (current is ViewGroup && current.tag == "airpods_container") {
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
val parent = current.parent
|
|
||||||
if (parent is ViewGroup && parent.tag == "airpods_container") {
|
|
||||||
return parent
|
|
||||||
}
|
|
||||||
current = parent as? View
|
|
||||||
}
|
|
||||||
Log.w(TAG, "Could not find airpods_container ancestor")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showAirPodsDrawer(container: ViewGroup, mainButton: ImageButton, drawerContainer: View) {
|
|
||||||
Log.d(TAG, "Showing AirPods drawer")
|
|
||||||
val selectedModeView = drawerContainer.findViewWithTag<View>("anc_mode_$currentANCMode")
|
|
||||||
val selectedModeIcon = selectedModeView?.findViewWithTag<ImageView>("mode_icon_$currentANCMode")
|
|
||||||
val buttonContainer = container.findViewWithTag<View>("airpods_button_container")
|
|
||||||
|
|
||||||
if (selectedModeView == null || selectedModeIcon == null) {
|
|
||||||
Log.e(TAG, "Cannot find selected mode view or icon for show animation")
|
|
||||||
|
|
||||||
drawerContainer.alpha = 0f
|
|
||||||
drawerContainer.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
drawerContainer.animate()
|
|
||||||
.alpha(1f)
|
|
||||||
.setDuration(ANIMATION_DURATION)
|
|
||||||
.start()
|
|
||||||
|
|
||||||
buttonContainer?.animate()
|
|
||||||
?.alpha(0f)
|
|
||||||
?.setDuration(ANIMATION_DURATION / 2)
|
|
||||||
?.setStartDelay(ANIMATION_DURATION / 2)
|
|
||||||
?.withEndAction {
|
|
||||||
buttonContainer.visibility = View.GONE
|
|
||||||
}
|
|
||||||
?.start()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
drawerContainer.measure(
|
|
||||||
View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY),
|
|
||||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
||||||
)
|
|
||||||
|
|
||||||
val drawerHeight = drawerContainer.measuredHeight
|
|
||||||
|
|
||||||
drawerContainer.alpha = 0f
|
|
||||||
drawerContainer.visibility = View.VISIBLE
|
|
||||||
drawerContainer.translationY = -drawerHeight.toFloat()
|
|
||||||
|
|
||||||
drawerContainer.animate()
|
|
||||||
.translationY(0f)
|
|
||||||
.alpha(1f)
|
|
||||||
.setDuration(ANIMATION_DURATION)
|
|
||||||
.setInterpolator(DecelerateInterpolator())
|
|
||||||
.start()
|
|
||||||
|
|
||||||
buttonContainer?.animate()
|
|
||||||
?.alpha(0f)
|
|
||||||
?.setDuration(ANIMATION_DURATION / 2)
|
|
||||||
?.setStartDelay(ANIMATION_DURATION / 3)
|
|
||||||
?.withEndAction {
|
|
||||||
buttonContainer.visibility = View.GONE
|
|
||||||
}
|
|
||||||
?.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hideAirPodsDrawer(container: ViewGroup, mainButton: ImageButton, drawerContainer: View) {
|
|
||||||
Log.d(TAG, "Hiding AirPods drawer")
|
|
||||||
val buttonContainer = container.findViewWithTag<View>("airpods_button_container")
|
|
||||||
|
|
||||||
if (buttonContainer != null && buttonContainer.visibility != View.VISIBLE) {
|
|
||||||
buttonContainer.alpha = 0f
|
|
||||||
buttonContainer.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonContainer?.animate()
|
|
||||||
?.alpha(1f)
|
|
||||||
?.setDuration(ANIMATION_DURATION / 2)
|
|
||||||
?.start()
|
|
||||||
|
|
||||||
drawerContainer.animate()
|
|
||||||
.translationY(-drawerContainer.height.toFloat())
|
|
||||||
.alpha(0f)
|
|
||||||
.setDuration(ANIMATION_DURATION)
|
|
||||||
.setInterpolator(AccelerateInterpolator())
|
|
||||||
.setStartDelay(ANIMATION_DURATION / 4)
|
|
||||||
.withEndAction {
|
|
||||||
drawerContainer.visibility = View.GONE
|
|
||||||
drawerContainer.translationY = 0f
|
|
||||||
}
|
|
||||||
.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getIconResourceForMode(mode: Int): Int {
|
|
||||||
return when (mode) {
|
|
||||||
ANC_MODE_OFF -> android.R.drawable.ic_lock_silent_mode
|
|
||||||
ANC_MODE_TRANSPARENCY -> android.R.drawable.ic_lock_silent_mode_off
|
|
||||||
ANC_MODE_ADAPTIVE -> android.R.drawable.ic_menu_compass
|
|
||||||
ANC_MODE_NOISE_CANCELLATION -> android.R.drawable.ic_lock_idle_charging
|
|
||||||
else -> android.R.drawable.ic_lock_silent_mode_off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLabelForMode(mode: Int): String {
|
|
||||||
return when (mode) {
|
|
||||||
ANC_MODE_OFF -> "Off"
|
|
||||||
ANC_MODE_TRANSPARENCY -> "Transparency"
|
|
||||||
ANC_MODE_ADAPTIVE -> "Adaptive"
|
|
||||||
ANC_MODE_NOISE_CANCELLATION -> "Noise Cancellation"
|
|
||||||
else -> "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user