Skip to content

Commit

Permalink
upgrades, formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
Shingyx committed Jul 20, 2024
1 parent 4e50618 commit f31d002
Show file tree
Hide file tree
Showing 18 changed files with 241 additions and 184 deletions.
35 changes: 22 additions & 13 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
name: Android CI

on: push
on:
push:
branches: [ 'master' ]
pull_request:
branches: [ 'master' ]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: 11
cache: gradle
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew buildDebug
./gradlew ktlintCheck
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: zulu
java-version: 17
cache: gradle
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew --no-daemon buildDebug assembleDebug ktlintCheck
- name: Upload debug apk
uses: actions/upload-artifact@v3
with:
name: connect-speaker-debug-apk
path: app/build/outputs/apk/debug/app-debug.apk
42 changes: 19 additions & 23 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'org.jlleitschuh.gradle.ktlint'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'org.jlleitschuh.gradle.ktlint'

def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
Expand All @@ -22,41 +20,39 @@ android {
keyPassword keystoreProperties['keyPassword']
}
}
compileSdkVersion 33

compileSdk 34
defaultConfig {
applicationId "com.github.shingyx.connectspeaker"
minSdkVersion 24
targetSdkVersion 33
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
}

buildFeatures {
viewBinding true
}
buildTypes {
debug {
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
signingConfig signingConfigs.release
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
buildFeatures {
viewBinding true
}
kotlinOptions {
jvmTarget = '1.8'
kotlin {
jvmToolchain(17)
}
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.material:material:1.8.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'com.google.android.material:material:1.12.0'
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ data class BluetoothDeviceInfo(
val name: String,
val address: String,
) : Comparable<BluetoothDeviceInfo> {
override fun toString(): String {
return name
}
override fun toString(): String = name

override fun compareTo(other: BluetoothDeviceInfo): Int {
return name.compareTo(other.name, true)
}
override fun compareTo(other: BluetoothDeviceInfo): Int = name.compareTo(other.name, true)

fun addToIntent(intent: Intent) {
intent.putExtra(EXTRA_NAME, name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import android.content.IntentFilter
class BluetoothStateReceiver(
private val onStateChanged: () -> Unit,
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
override fun onReceive(
context: Context,
intent: Intent,
) {
if (intent.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)
if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_ON) {
Expand All @@ -19,8 +22,6 @@ class BluetoothStateReceiver(
}

companion object {
fun intentFilter(): IntentFilter {
return IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
}
fun intentFilter(): IntentFilter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ class ConnectSpeakerClient private constructor(
toggleConnectionInternal()
}
} catch (e: Exception) {
val messageResId = when (e) {
is ExceptionWithStringRes -> e.stringResId
is TimeoutCancellationException -> R.string.error_timed_out
else -> R.string.error_unknown
}
val messageResId =
when (e) {
is ExceptionWithStringRes -> e.stringResId
is TimeoutCancellationException -> R.string.error_timed_out
else -> R.string.error_unknown
}
reportProgressWithResId(messageResId)
}
}
Expand All @@ -49,23 +50,28 @@ class ConnectSpeakerClient private constructor(
private suspend fun toggleConnectionInternal() {
reportProgressWithResId(R.string.starting)

val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
?.takeIf { it.isEnabled }
?: throw ExceptionWithStringRes("Bluetooth disabled", R.string.error_bluetooth_disabled)
val bluetoothAdapter =
context
.getSystemService(BluetoothManager::class.java)
.adapter
?.takeIf { it.isEnabled }
?: throw ExceptionWithStringRes("Bluetooth disabled", R.string.error_bluetooth_disabled)

val device = bluetoothAdapter.bondedDevices.find { it.address == deviceInfo.address }
?: throw ExceptionWithStringRes("Speaker not paired", R.string.error_speaker_unpaired)
val device =
bluetoothAdapter.bondedDevices.find { it.address == deviceInfo.address }
?: throw ExceptionWithStringRes("Speaker not paired", R.string.error_speaker_unpaired)

val bluetoothA2dp = getBluetoothA2dpService(bluetoothAdapter)
val isConnected = bluetoothA2dp.connectedDevices.contains(device)

val bluetoothA2dpConnector = BluetoothA2dpConnector(context, bluetoothA2dp)

val connectionStrategy = if (!isConnected) {
ConnectStrategy(bluetoothA2dpConnector)
} else {
DisconnectStrategy(bluetoothA2dpConnector)
}
val connectionStrategy =
if (!isConnected) {
ConnectStrategy(bluetoothA2dpConnector)
} else {
DisconnectStrategy(bluetoothA2dpConnector)
}
applyConnectionStrategy(connectionStrategy, device)
}

Expand All @@ -89,7 +95,10 @@ class ConnectSpeakerClient private constructor(
bluetoothAdapter.getProfileProxy(
context,
object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile?) {
override fun onServiceConnected(
profile: Int,
proxy: BluetoothProfile?,
) {
if (proxy != null && profile == BluetoothProfile.A2DP) {
deferred.complete(proxy as BluetoothA2dp)
}
Expand All @@ -107,7 +116,9 @@ class ConnectSpeakerClient private constructor(
)
}

private fun reportProgressWithResId(@StringRes messageResId: Int) {
private fun reportProgressWithResId(
@StringRes messageResId: Int,
) {
val progressMessage = context.getString(messageResId, deviceInfo.name)
reportProgress(progressMessage)
}
Expand Down Expand Up @@ -142,9 +153,12 @@ class ConnectSpeakerClient private constructor(
@SuppressLint("MissingPermission")
fun getPairedDevicesInfo(context: Context): List<BluetoothDeviceInfo>? {
try {
val bondedDevices = context.getSystemService(BluetoothManager::class.java).adapter
?.takeIf { it.isEnabled }
?.bondedDevices
val bondedDevices =
context
.getSystemService(BluetoothManager::class.java)
.adapter
?.takeIf { it.isEnabled }
?.bondedDevices

if (bondedDevices != null) {
return bondedDevices.map { BluetoothDeviceInfo(it.name, it.address) }.sorted()
Expand All @@ -155,10 +169,11 @@ class ConnectSpeakerClient private constructor(
return null
}

fun checkBluetoothConnectPermission(context: Context): Boolean {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
context, Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED
}
fun checkBluetoothConnectPermission(context: Context): Boolean =
Build.VERSION.SDK_INT < Build.VERSION_CODES.S ||
ActivityCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_CONNECT,
) == PackageManager.PERMISSION_GRANTED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ object Preferences {
return BluetoothDeviceInfo(name, address)
}
set(value) {
sharedPreferences.edit()
sharedPreferences
.edit()
.putString(KEY_DEVICE_NAME, value?.name)
.putString(KEY_DEVICE_ADDRESS, value?.address)
.apply()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import android.content.IntentFilter
class BluetoothA2dpConnectionStateReceiver(
private val onStateChanged: (connected: Boolean, device: BluetoothDevice) -> Unit,
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
override fun onReceive(
context: Context,
intent: Intent,
) {
if (intent.action == BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED) {
val state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0)
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
Expand All @@ -25,8 +28,6 @@ class BluetoothA2dpConnectionStateReceiver(
}

companion object {
fun intentFilter(): IntentFilter {
return IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)
}
fun intentFilter(): IntentFilter = IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.github.shingyx.connectspeaker.data.bluetootha2dp
import android.bluetooth.BluetoothA2dp
import android.bluetooth.BluetoothDevice
import android.content.Context
import androidx.core.content.ContextCompat
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.withTimeoutOrNull
import timber.log.Timber
Expand All @@ -11,13 +12,15 @@ class BluetoothA2dpConnector(
private val context: Context,
private val bluetoothA2dp: BluetoothA2dp,
) {
suspend fun connectDevice(device: BluetoothDevice, timeout: Long): Boolean {
return execute(device, timeout, true)
}
suspend fun connectDevice(
device: BluetoothDevice,
timeout: Long,
): Boolean = execute(device, timeout, true)

suspend fun disconnectDevice(device: BluetoothDevice, timeout: Long): Boolean {
return execute(device, timeout, false)
}
suspend fun disconnectDevice(
device: BluetoothDevice,
timeout: Long,
): Boolean = execute(device, timeout, false)

private suspend fun execute(
device: BluetoothDevice,
Expand All @@ -26,42 +29,49 @@ class BluetoothA2dpConnector(
): Boolean {
val deferred = CompletableDeferred<Boolean>()

val stateReceiver = BluetoothA2dpConnectionStateReceiver { connected, stateDevice ->
if (connected == shouldConnect && stateDevice == device) {
deferred.complete(true)
val stateReceiver =
BluetoothA2dpConnectionStateReceiver { connected, stateDevice ->
if (connected == shouldConnect && stateDevice == device) {
deferred.complete(true)
}
}
}
context.registerReceiver(stateReceiver, BluetoothA2dpConnectionStateReceiver.intentFilter())
ContextCompat.registerReceiver(
context,
stateReceiver,
BluetoothA2dpConnectionStateReceiver.intentFilter(),
ContextCompat.RECEIVER_NOT_EXPORTED,
)

val methodName = if (shouldConnect) CONNECT_METHOD_NAME else DISCONNECT_METHOD_NAME
if (!invokeBluetoothA2dpMethod(device, methodName)) {
deferred.complete(false)
}

val result = withTimeoutOrNull(timeout) {
deferred.await()
} ?: false
val result =
withTimeoutOrNull(timeout) {
deferred.await()
} ?: false
context.unregisterReceiver(stateReceiver)
return result
}

private fun invokeBluetoothA2dpMethod(
device: BluetoothDevice,
connectMethodName: String,
): Boolean {
return try {
): Boolean =
try {
// the A2DP connection methods are hidden in the public API.
val connectMethod = BluetoothA2dp::class.java.getDeclaredMethod(
connectMethodName,
BluetoothDevice::class.java,
)
val connectMethod =
BluetoothA2dp::class.java.getDeclaredMethod(
connectMethodName,
BluetoothDevice::class.java,
)
val initiated = connectMethod.invoke(bluetoothA2dp, device)
initiated == true
} catch (e: Exception) {
Timber.e(e, "Exception calling $connectMethodName")
false
}
}

companion object {
private const val CONNECT_METHOD_NAME = "connect"
Expand Down
Loading

0 comments on commit f31d002

Please sign in to comment.