mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-28 00:56:07 +00:00
implement music play/pause from ear detection
This commit is contained in:
BIN
android/app/release/baselineProfiles/0/app-release.dm
Normal file
BIN
android/app/release/baselineProfiles/0/app-release.dm
Normal file
Binary file not shown.
BIN
android/app/release/baselineProfiles/1/app-release.dm
Normal file
BIN
android/app/release/baselineProfiles/1/app-release.dm
Normal file
Binary file not shown.
37
android/app/release/output-metadata.json
Normal file
37
android/app/release/output-metadata.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "APK",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "me.kavishdevar.aln",
|
||||||
|
"variantName": "release",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "SINGLE",
|
||||||
|
"filters": [],
|
||||||
|
"attributes": [],
|
||||||
|
"versionCode": 1,
|
||||||
|
"versionName": "1.0",
|
||||||
|
"outputFile": "app-release.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"elementType": "File",
|
||||||
|
"baselineProfiles": [
|
||||||
|
{
|
||||||
|
"minApi": 28,
|
||||||
|
"maxApi": 30,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/1/app-release.dm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minApi": 31,
|
||||||
|
"maxApi": 2147483647,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/0/app-release.dm"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minSdkVersionForDexing": 28
|
||||||
|
}
|
||||||
@@ -4,16 +4,22 @@ import android.annotation.SuppressLint
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.bluetooth.BluetoothSocket
|
import android.bluetooth.BluetoothSocket
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.media.AudioManager
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.ParcelUuid
|
import android.os.ParcelUuid
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
import kotlin.experimental.or
|
||||||
|
|
||||||
class AirPodsService : Service() {
|
class AirPodsService : Service() {
|
||||||
inner class LocalBinder : Binder() {
|
inner class LocalBinder : Binder() {
|
||||||
@@ -27,6 +33,12 @@ class AirPodsService : Service() {
|
|||||||
var isRunning: Boolean = false
|
var isRunning: Boolean = false
|
||||||
private var socket: BluetoothSocket? = null
|
private var socket: BluetoothSocket? = null
|
||||||
|
|
||||||
|
fun sendPacket(packet: String) {
|
||||||
|
val fromHex = packet.split(" ").map { it.toInt(16).toByte() }
|
||||||
|
socket?.outputStream?.write(fromHex.toByteArray())
|
||||||
|
socket?.outputStream?.flush()
|
||||||
|
}
|
||||||
|
|
||||||
fun setANCMode(mode: Int) {
|
fun setANCMode(mode: Int) {
|
||||||
when (mode) {
|
when (mode) {
|
||||||
1 -> {
|
1 -> {
|
||||||
@@ -50,14 +62,12 @@ class AirPodsService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setAdaptiveStrength(strength: Int) {
|
fun setAdaptiveStrength(strength: Int) {
|
||||||
val bytes = byteArrayOf(0x00, 0x04, 0x00, 0x09, 0x00, 0x2E, strength.toByte(), 0x00, 0x00, 0x00)
|
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x2E, strength.toByte(), 0x00, 0x00, 0x00)
|
||||||
val hexString = bytes.joinToString(" ") { "%02X".format(it) }
|
|
||||||
Log.d("AirPodsService", "Adaptive Strength: $hexString")
|
|
||||||
socket?.outputStream?.write(bytes)
|
socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
socket?.outputStream?.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission", "InlinedApi")
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
@@ -107,6 +117,33 @@ class AirPodsService : Service() {
|
|||||||
putExtra("data", bytes)
|
putExtra("data", bytes)
|
||||||
})
|
})
|
||||||
Log.d("AirPods Parser", "Ear Detection: ${earDetectionNotification.status[0]} ${earDetectionNotification.status[1]}")
|
Log.d("AirPods Parser", "Ear Detection: ${earDetectionNotification.status[0]} ${earDetectionNotification.status[1]}")
|
||||||
|
val audioManager = this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
||||||
|
val mediaController = MediaController(audioManager)
|
||||||
|
var inEar = false
|
||||||
|
val earReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val data = intent.getByteArrayExtra("data")
|
||||||
|
if (data != null) {
|
||||||
|
inEar = if (data.find { it == 0x02.toByte() } != null || data.find { it == 0x03.toByte() } != null) {
|
||||||
|
data[0] == 0x00.toByte() || data[1] == 0x00.toByte()
|
||||||
|
} else {
|
||||||
|
data[0] == 0x00.toByte() && data[1] == 0x00.toByte()
|
||||||
|
}
|
||||||
|
Log.d("AirPods Parser", "In Ear: $inEar")
|
||||||
|
if (inEar) {
|
||||||
|
mediaController.sendPlay()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mediaController.sendPause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val earIntentFilter = IntentFilter(Notifications.EAR_DETECTION_DATA)
|
||||||
|
this@AirPodsService.registerReceiver(earReceiver, earIntentFilter,
|
||||||
|
RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else if (ancNotification.isANCData(data)) {
|
else if (ancNotification.isANCData(data)) {
|
||||||
ancNotification.setStatus(data)
|
ancNotification.setStatus(data)
|
||||||
@@ -129,6 +166,16 @@ class AirPodsService : Service() {
|
|||||||
sendBroadcast(Intent(Notifications.CA_DATA).apply {
|
sendBroadcast(Intent(Notifications.CA_DATA).apply {
|
||||||
putExtra("data", conversationAwarenessNotification.status)
|
putExtra("data", conversationAwarenessNotification.status)
|
||||||
})
|
})
|
||||||
|
if (conversationAwarenessNotification.status == 1.toByte() or 2.toByte()) {
|
||||||
|
val audioManager = this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
||||||
|
val mediaController = MediaController(audioManager)
|
||||||
|
mediaController.startSpeaking()
|
||||||
|
}
|
||||||
|
else if (conversationAwarenessNotification.status == 9.toByte() or 8.toByte()) {
|
||||||
|
val audioManager = this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
||||||
|
val mediaController = MediaController(audioManager)
|
||||||
|
mediaController.stopSpeaking()
|
||||||
|
}
|
||||||
Log.d("AirPods Parser", "Conversation Awareness: ${conversationAwarenessNotification.status}")
|
Log.d("AirPods Parser", "Conversation Awareness: ${conversationAwarenessNotification.status}")
|
||||||
}
|
}
|
||||||
else { }
|
else { }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.media.AudioManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
@@ -45,12 +46,14 @@ import androidx.compose.foundation.text.BasicTextField
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Slider
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.SliderDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.material3.VerticalDivider
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -63,6 +66,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
@@ -86,6 +90,7 @@ import com.google.accompanist.permissions.isGranted
|
|||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import com.google.accompanist.permissions.shouldShowRationale
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
import me.kavishdevar.aln.ui.theme.ALNTheme
|
import me.kavishdevar.aln.ui.theme.ALNTheme
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -397,11 +402,15 @@ fun BatteryView() {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Row {
|
Row {
|
||||||
Text(text = "\uDBC6\uDCE5", fontFamily = FontFamily(Font(R.font.sf_pro)))
|
if (left?.status != BatteryStatus.DISCONNECTED) {
|
||||||
BatteryIndicator(left?.level ?: 0, left?.status == BatteryStatus.CHARGING)
|
Text(text = "\uDBC6\uDCE5", fontFamily = FontFamily(Font(R.font.sf_pro)))
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
BatteryIndicator(left?.level ?: 0, left?.status == BatteryStatus.CHARGING)
|
||||||
Text(text = "\uDBC6\uDCE8", fontFamily = FontFamily(Font(R.font.sf_pro)))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
BatteryIndicator(right?.level ?: 0, right?.status == BatteryStatus.CHARGING)
|
}
|
||||||
|
if (right?.status != BatteryStatus.DISCONNECTED) {
|
||||||
|
Text(text = "\uDBC6\uDCE8", fontFamily = FontFamily(Font(R.font.sf_pro)))
|
||||||
|
BatteryIndicator(right?.level ?: 0, right?.status == BatteryStatus.CHARGING)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,7 +429,6 @@ fun BatteryView() {
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
BatteryIndicator(case?.level ?: 0)
|
BatteryIndicator(case?.level ?: 0)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,6 +462,7 @@ fun AirPodsSettingsScreen(paddingValues: PaddingValues, device: BluetoothDevice?
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NoiseControlSlider(service: AirPodsService) {
|
fun NoiseControlSlider(service: AirPodsService) {
|
||||||
val sliderValue = remember { mutableStateOf(0f) }
|
val sliderValue = remember { mutableStateOf(0f) }
|
||||||
@@ -475,17 +484,37 @@ fun NoiseControlSlider(service: AirPodsService) {
|
|||||||
value = sliderValue.value,
|
value = sliderValue.value,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
sliderValue.value = it
|
sliderValue.value = it
|
||||||
service.setAdaptiveStrength(it.toInt())
|
service.setAdaptiveStrength(100 - it.toInt())
|
||||||
},
|
},
|
||||||
valueRange = 0f..100f,
|
valueRange = 0f..100f,
|
||||||
steps = 3,
|
onValueChangeFinished = {
|
||||||
|
// Round the value when the user stops sliding
|
||||||
|
sliderValue.value = sliderValue.value.roundToInt().toFloat()
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
|
.height(36.dp), // Adjust height to ensure thumb fits well
|
||||||
colors = SliderDefaults.colors(
|
colors = SliderDefaults.colors(
|
||||||
thumbColor = thumbColor,
|
thumbColor = thumbColor,
|
||||||
activeTrackColor = activeTrackColor,
|
activeTrackColor = activeTrackColor,
|
||||||
inactiveTrackColor = trackColor
|
inactiveTrackColor = trackColor
|
||||||
)
|
),
|
||||||
|
thumb = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp) // Circular thumb size
|
||||||
|
.shadow(4.dp, CircleShape) // Apply shadow to the thumb
|
||||||
|
.background(thumbColor, CircleShape) // Circular thumb
|
||||||
|
)
|
||||||
|
},
|
||||||
|
track = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(12.dp)
|
||||||
|
.background(trackColor, RoundedCornerShape(6.dp))
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Labels
|
// Labels
|
||||||
@@ -599,7 +628,30 @@ fun AudioSettings(service: AirPodsService) {
|
|||||||
color = textColor.copy(alpha = 0.6f)
|
color = textColor.copy(alpha = 0.6f)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
NoiseControlSlider(service = service)
|
NoiseControlSlider(service = service)
|
||||||
|
val packet = remember { mutableStateOf ("") }
|
||||||
|
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = packet.value,
|
||||||
|
onValueChange = { packet.value = it },
|
||||||
|
modifier = Modifier.fillMaxWidth(0.75f),
|
||||||
|
)
|
||||||
|
Button(onClick = {
|
||||||
|
service.sendPacket(packet.value)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = "Send")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package me.kavishdevar.aln
|
||||||
|
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.view.KeyEvent
|
||||||
|
|
||||||
|
class MediaController (private val audioManager: AudioManager){
|
||||||
|
fun sendPause() {
|
||||||
|
if (audioManager.isMusicActive) {
|
||||||
|
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE))
|
||||||
|
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendPlay() {
|
||||||
|
if (!audioManager.isMusicActive) {
|
||||||
|
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY))
|
||||||
|
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var initialVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
||||||
|
fun startSpeaking() {
|
||||||
|
if (!audioManager.isMusicActive) {
|
||||||
|
// reduce volume to 10% of initial volume
|
||||||
|
initialVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
||||||
|
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (initialVolume * 0.1).toInt(), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopSpeaking() {
|
||||||
|
if (!audioManager.isMusicActive) {
|
||||||
|
// restore initial volume
|
||||||
|
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, initialVolume, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -135,7 +135,11 @@ class Notifications {
|
|||||||
fun setBattery(data: ByteArray) {
|
fun setBattery(data: ByteArray) {
|
||||||
first = Battery(data[7].toInt(), data[9].toInt(), data[10].toInt())
|
first = Battery(data[7].toInt(), data[9].toInt(), data[10].toInt())
|
||||||
second = Battery(data[12].toInt(), data[14].toInt(), data[15].toInt())
|
second = Battery(data[12].toInt(), data[14].toInt(), data[15].toInt())
|
||||||
case = Battery(data[17].toInt(), data[19].toInt(), data[20].toInt())
|
case = if (data[20].toInt() == BatteryStatus.DISCONNECTED && case.status != BatteryStatus.DISCONNECTED) {
|
||||||
|
Battery(data[17].toInt(), case.level, data[20].toInt())
|
||||||
|
} else {
|
||||||
|
Battery(data[17].toInt(), data[19].toInt(), data[20].toInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBattery(): List<Battery> {
|
fun getBattery(): List<Battery> {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
[versions]
|
[versions]
|
||||||
accompanistPermissions = "0.36.0"
|
accompanistPermissions = "0.36.0"
|
||||||
agp = "8.7.0-rc01"
|
agp = "8.7.0"
|
||||||
hiddenapibypass = "4.3"
|
hiddenapibypass = "4.3"
|
||||||
kotlin = "2.0.0"
|
kotlin = "2.0.0"
|
||||||
coreKtx = "1.13.1"
|
coreKtx = "1.13.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
lifecycleRuntimeKtx = "2.8.5"
|
lifecycleRuntimeKtx = "2.8.6"
|
||||||
activityCompose = "1.9.2"
|
activityCompose = "1.9.2"
|
||||||
composeBom = "2024.04.01"
|
composeBom = "2024.09.03"
|
||||||
annotations = "15.0"
|
annotations = "26.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
|
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#Mon Oct 07 22:30:36 IST 2024
|
#Mon Oct 07 22:30:36 IST 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Reference in New Issue
Block a user