Skip to content

Commit

Permalink
Merge pull request #19 from Semper-Viventem/fix-ble-scanner-is-null-p…
Browse files Browse the repository at this point in the history
…roblem

Fix crash when bluetooth scanner cannot be initialized
  • Loading branch information
Semper-Viventem authored Mar 1, 2023
2 parents 1e09879 + fd48906 commit cada14f
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 172 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ android {
minSdk = 29
targetSdk = 33
versionCode = (System.currentTimeMillis() / 1000).toInt()
versionName = "0.5-alpha"
versionName = "0.6-alpha"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/f/cking/software/data/DataModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ class DataModule(
single { LocationProvider(get(), get(), get()) }
single { LocationRepository(get()) }
single { JournalRepository(get()) }
single { NotificationsHelper(get()) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import java.util.*
class BleScannerHelper(
private val devicesRepository: DevicesRepository,
private val getKnownDevicesInteractor: GetKnownDevicesInteractor,
appContext: Context,
private val appContext: Context,
) {

private val TAG = "BleScannerHelper"

private var bluetoothScanner: BluetoothLeScanner
private var bluetoothScanner: BluetoothLeScanner? = null
private val handler: Handler = Handler(Looper.getMainLooper())
private val batch = hashMapOf<String, BleScanDevice>()
private var currentScanTimeMs: Long = System.currentTimeMillis()
Expand All @@ -34,8 +34,7 @@ class BleScannerHelper(
private var scanListener: ScanListener? = null

init {
val bluetoothAdapter = appContext.getSystemService(BluetoothManager::class.java).adapter
bluetoothScanner = bluetoothAdapter.bluetoothLeScanner
tryToInitBluetoothScanner()
}

private val callback = object : ScanCallback() {
Expand Down Expand Up @@ -91,7 +90,7 @@ class BleScannerHelper(
.build()

withContext(Dispatchers.IO) {
bluetoothScanner.startScan(scanFilters, scanSettings, callback)
requireScanner().startScan(scanFilters, scanSettings, callback)
handler.postDelayed({ cancelScanning(ScanResultInternal.Success) }, scanDurationMs)
}
}
Expand All @@ -104,7 +103,7 @@ class BleScannerHelper(
@SuppressLint("MissingPermission")
private fun cancelScanning(scanResult: ScanResultInternal) {
inProgress.tryEmit(false)
bluetoothScanner.stopScan(callback)
bluetoothScanner?.stopScan(callback)


when (scanResult) {
Expand All @@ -122,6 +121,18 @@ class BleScannerHelper(
scanListener = null
}

private fun tryToInitBluetoothScanner() {
val bluetoothAdapter = appContext.getSystemService(BluetoothManager::class.java).adapter
bluetoothScanner = bluetoothAdapter?.bluetoothLeScanner
}

private fun requireScanner(): BluetoothLeScanner {
if (bluetoothScanner == null) {
tryToInitBluetoothScanner()
}
return bluetoothScanner ?: throw BluetoothIsNotInitialized()
}

interface ScanListener {
fun onSuccess(batch: List<BleScanDevice>)
fun onFailure(exception: Exception)
Expand All @@ -136,6 +147,7 @@ class BleScannerHelper(
object Canceled : ScanResultInternal
}

class BLEScanFailure(errorCode: Int): RuntimeException("BLE Scan failed with error code: $errorCode")
class BLEScanFailure(errorCode: Int) : RuntimeException("BLE Scan failed with error code: $errorCode")

class BluetoothIsNotInitialized : RuntimeException("Bluetooth scanner is not initialized")
}
181 changes: 181 additions & 0 deletions app/src/main/java/f/cking/software/data/helpers/NotificationsHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package f.cking.software.data.helpers

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import f.cking.software.R
import f.cking.software.domain.interactor.CheckProfileDetectionInteractor
import f.cking.software.ui.MainActivity
import kotlin.random.Random

class NotificationsHelper(
private val context: Context,
) {



private val notificationManager by lazy { context.getSystemService(NotificationManager::class.java) }

fun buildForegroundNotification(
knownDeviceCount: Int?,
cancelIntent: Intent,
): Notification {
createServiceChannel()

val cancelPendingIntent = PendingIntent.getService(
context,
0,
cancelIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

val openAppPendingIntent = getOpenAppIntent()

val body = if (knownDeviceCount == null) {
context.getString(R.string.ble_scanner_is_started_but_no_data)
} else if (knownDeviceCount > 0) {
context.getString(R.string.known_devices_around, knownDeviceCount.toString())
} else {
context.getString(R.string.there_are_no_devices_around)
}

return NotificationCompat.Builder(context, SERVICE_NOTIFICATION_CHANNEL)
.setContentTitle(context.getString(R.string.app_service_title, context.getString(R.string.app_name)))
.setContentText(body)
.setOngoing(true)
.setSmallIcon(R.drawable.ic_ble)
.setContentIntent(openAppPendingIntent)
.addAction(R.drawable.ic_cancel, context.getString(R.string.stop), cancelPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setVibrate(null)
.setSound(null)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.build()
}

fun notifyRadarProfile(profiles: List<CheckProfileDetectionInteractor.ProfileResult>) {
val title = if (profiles.count() == 1) {
val profile = profiles.first()
context.getString(R.string.notification_profile_is_near_you, profile.profile.name)
} else {
context.getString(R.string.notification_profiles_are_near_you, profiles.count().toString())
}

val content = profiles.flatMap { it.matched }
.joinToString(
separator = ", ",
postfix = context.getString(R.string.devices_matched_postfix)
) { it.buildDisplayName() }

val openAppPendingIntent = getOpenAppIntent()

createDeviceFoundChannel()

val notification = NotificationCompat.Builder(context, RADAR_PROFILE_CHANNEL)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(R.drawable.ic_ble)
.setContentIntent(openAppPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setGroup(RADAR_PROFILE_GROUP)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
.build()

notificationManager.notify(Random.nextInt(), notification)
}

fun notifyLocationIsTurnedOff() {
notifyError(
title = context.getString(R.string.location_is_turned_off_title),
content = context.getString(R.string.location_is_turned_off_subtitle)
)
}

fun notifyBluetoothIsTurnedOff() {
notifyError(
title = context.getString(R.string.bluetooth_is_not_available_title),
content = context.getString(R.string.bluetooth_is_not_available_content),
)
}

fun cancel(notificationId: Int) {
notificationManager.cancel(notificationId)
}

private fun notifyError(title: String, content: String?) {
val openAppPendingIntent = getOpenAppIntent()

createErrorsNotificationChannel()

val notification = NotificationCompat.Builder(context, ERRORS_CHANNEL)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(R.drawable.ic_ble)
.setContentIntent(openAppPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.build()

notificationManager.notify(Random.nextInt(), notification)
}

private fun getOpenAppIntent(): PendingIntent {
val openAppIntent = Intent(context, MainActivity::class.java)
return PendingIntent.getActivity(
context,
0,
openAppIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}

fun updateNotification(knownDeviceCount: Int, canelIntent: Intent) {
val notification = buildForegroundNotification(knownDeviceCount, canelIntent)
notificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification)
}

private fun createServiceChannel() {
val channel = NotificationChannel(
SERVICE_NOTIFICATION_CHANNEL,
context.getString(R.string.scanner_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel)
}

private fun createDeviceFoundChannel() {
val channel = NotificationChannel(
RADAR_PROFILE_CHANNEL,
context.getString(R.string.device_found_notification_channel_name),
NotificationManager.IMPORTANCE_HIGH
).apply { enableVibration(true) }
notificationManager.createNotificationChannel(channel)
}

private fun createErrorsNotificationChannel() {
val channel = NotificationChannel(
ERRORS_CHANNEL,
context.getString(R.string.errors_notifications),
NotificationManager.IMPORTANCE_HIGH
).apply { enableVibration(true) }
notificationManager.createNotificationChannel(channel)
}

companion object {
private const val SERVICE_NOTIFICATION_CHANNEL = "service_notification_channel"
private const val RADAR_PROFILE_CHANNEL = "radar_profile_channel"
private const val RADAR_PROFILE_GROUP = "radar_profile_group"
private const val ERRORS_CHANNEL = "radar_errors_channel"

const val FOREGROUND_NOTIFICATION_ID = 42
}
}
Loading

0 comments on commit cada14f

Please sign in to comment.