This commit is contained in:
2023-09-23 19:09:54 +02:00
commit 09b3ec6d68
41 changed files with 1661 additions and 0 deletions

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

44
app/build.gradle Normal file
View File

@@ -0,0 +1,44 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 33
defaultConfig {
applicationId "com.example.oilcheckkotlin"
minSdk 28
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'com.luckycatlabs:SunriseSunsetCalculator:1.2'
}

21
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,24 @@
package com.example.oilcheckkotlin
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.oilcheckkotlin", appContext.packageName)
}
}

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.oilcheckkotlin">
<!-- for Android below 12 start -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- for Android below 12 end -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<!-- android:icon="@mipmap/ic_launcher"-->
<!-- android:roundIcon="@mipmap/ic_launcher_round"-->
<application
android:allowBackup="true"
android:icon="@drawable/draw_icon_blue"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.OilCheckKotlin">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".BLEConnectionService"></service>
<service android:name=".NLService"
android:exported="true"
android:enabled="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -0,0 +1,209 @@
package com.example.oilcheckkotlin
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.location.LocationManager
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.location.LocationManagerCompat
class BLEConnectionService : Service() {
val CHANNEL_DEFAULT_IMPORTANCE = "1"
val ONGOING_NOTIFICATION_ID = 2
var bleController: BLEController? = null
var deviceAddress: String? = null
var nlService = NLService()
private val myThread = Thread {
while (true){
Log.d("Runnable", "Working")
if(bleController == null) {
val lm = getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (!LocationManagerCompat.isLocationEnabled(lm)) {
// Start Location Settings Activity, you should explain to the user why he need to enable location before.
startActivity(Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
Log.d("Debug", "bleController null")
// bleController = BLEController.getInstance(applicationContext)
bleController = BLEController(applicationContext)
} else if(!bleController!!.isConnected()){
// not connected
Log.d("Debug", "bleController not null but not connected")
bleController!!.init()
Log.d("Debug", "tried to init ble controller")
} else{
// controller is connected
Log.d("Debug", "BLE device connected")
bleController!!.stopScanner()
}
Thread.sleep(12000)
}
}
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if(intent?.action.equals("STARTService")){
Log.d("DEBUG", "Startflag set")
myThread.start()
// setup ble connection
val channel_id =
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
createNotificationChannel("my_service", "My Background Service")
} else {
""
}
val serviceIntent: PendingIntent = Intent(this, BLEConnectionService::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE)
}
val myIntent = Intent(this, MainActivity::class.java).apply {
var flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val contentIntent: PendingIntent = PendingIntent.getActivity(this, 0, myIntent, PendingIntent.FLAG_IMMUTABLE)
val notification: Notification = Notification.Builder(this, channel_id)
.setContentTitle("Oil-Check")
.setContentText("running")
.setSmallIcon(R.drawable.icon)
.setContentIntent(serviceIntent)
// .setTicker(getText(R.string.ticker_text))
.build()
startForeground(101, notification)
// startService(Intent(this, NLService::class.java)
// .setAction("StartNLService")
// )
val nlServiceIntent: PendingIntent = Intent(this, NLService::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE)
}
val nlNotification: Notification = Notification.Builder(this, channel_id)
.setContentTitle("Oil-Check")
.setContentText("running")
.setSmallIcon(R.drawable.icon)
.setContentIntent(nlServiceIntent)
// .setTicker(getText(R.string.ticker_text))
.build()
startForeground(101, nlNotification)
// setup notification listener
// val nlServiceIntent: PendingIntent = Intent(this, NLService::class.java).let { notificationIntent ->
// PendingIntent.getActivity(this, 0, notificationIntent,
// PendingIntent.FLAG_IMMUTABLE)
// }
//
// val nlNotification: Notification = Notification.Builder(this, channel_id)
// .setContentTitle("Oil-Check")
// .setContentText("running")
// .setSmallIcon(R.drawable.icon)
// .setContentIntent(nlServiceIntent)
//// .setTicker(getText(R.string.ticker_text))
// .build()
// startForeground(101, nlNotification)
} else if (intent?.action.equals("STOPService")){
Log.d("DEBUG", "Stopflag set")
stopForeground(true)
stopSelfResult(101)
stopSelf()
} else if (intent?.action.equals("StartNLService")) {
//
}
else if(intent?.action.equals("Toggle")){
try {
bleController!!.sendData("X".toByteArray(Charsets.UTF_8))
}
catch (e: Exception){
Log.d("Debug", "tried to toggle, but failed")
}
} else if(intent?.action.equals("X")){
try {
bleController!!.sendData("X".toByteArray(Charsets.UTF_8))
}
catch (e: Exception){
Log.d("Debug", "Tried to send X, but connection failed")
}
} else if(intent?.action.equals("XX")){
try {
bleController!!.sendData("XX".toByteArray(Charsets.UTF_8))
}
catch (e: Exception){
Log.d("Debug", "Tried to send XX, but connection failed")
}
} else if(intent?.action.equals("XXX")){
try {
bleController!!.sendData("XXX".toByteArray(Charsets.UTF_8))
}
catch (e: Exception){
Log.d("Debug", "Tried to send XXX, but connection failed")
}
} else if(intent?.action.equals("XXXX")){
try {
bleController!!.sendData("XXXX".toByteArray(Charsets.UTF_8))
}
catch (e: Exception){
Log.d("Debug", "Tried to send XXXX, but connection failed")
}
} else if(intent?.action.equals("OTA")) {
try {
bleController!!.sendData("U".toByteArray(Charsets.UTF_8))
}
catch (e: Exception){
Log.d("Debug", "Tried to update firmware, but connection failed")
}
}
else{
Log.d("Debug", "Intent without arguments occured in service")
}
return START_STICKY
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String{
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_NONE)
chan.lightColor = Color.BLUE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
override fun onBind(p0: Intent?): IBinder? {
// TODO("Not yet implemented")
return null;
}
override fun onDestroy() {
super.onDestroy()
this.myThread.interrupt()
}
}

View File

@@ -0,0 +1,338 @@
package com.example.oilcheckkotlin
import android.Manifest
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import java.util.Locale
class BLEController(ctx: Context) : BLEControllerListener{
companion object{
var instance: BLEController? = null
fun getInstance(ctx: Context?): BLEController? {
Log.d("BLEController", "called")
if (null == instance){
instance = BLEController(ctx!!)
}
return instance
}
}
private val SCAN_PERIOD: Long = 10000
private var deviceAddress: String? = null
var ctx: Context
private var devices = hashMapOf<String, BluetoothDevice>()
private var scanner: BluetoothLeScanner? = null
var bluetoothManager: BluetoothManager? = null
private var btGattChar: BluetoothGattCharacteristic? = null
private lateinit var bluetoothGatt: BluetoothGatt
private var device: BluetoothDevice? = null
private val listeners: ArrayList<BLEControllerListener> = ArrayList<BLEControllerListener>()
private var connectionState = false
fun stopScanner(){
if (ActivityCompat.checkSelfPermission(
ctx,
Manifest.permission.BLUETOOTH_SCAN
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
try {
scanner!!.stopScan(bleCallback)
}
catch (e: Exception){
Log.d("Debug", "Scanner seems to run, stopping")
}
}
// constructor
init {
Log.d("Debug", "constructor called")
this.bluetoothManager = ctx.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
this.ctx = ctx
}
fun init(){
Log.d("Debug", "init")
this.devices?.clear()
Log.d("Debug", "get scanner")
this.scanner = this.bluetoothManager?.adapter?.bluetoothLeScanner
Log.d("Debug", "got scanner")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (ActivityCompat.checkSelfPermission(
ctx,
Manifest.permission.BLUETOOTH_SCAN
) != PackageManager.PERMISSION_GRANTED
) {
// val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
// requestBluetooth.launch(enableBtIntent)
Log.d("Debug", "permission needed")
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
} else{
Log.d("Debug", "permission granted")
}
}
Log.d("Debug", "try to scan")
this.scanner?.startScan(bleCallback)
}
var bleCallback = object: ScanCallback(){
override fun onScanResult(callbackType: Int, result: ScanResult?) {
// Log.d("Debug", "onScanResult called")
val device = result?.device
// Log.d("Debug", "device result exists")
if (!devices?.containsKey(device?.address)!! && isThisTheDevice(device)) {
Log.d("Debug", "found the device")
deviceFound(device!!)
Log.d("Debug", device.address)
}
}
override fun onBatchScanResults(results: List<ScanResult?>?) {
Log.d("Debug", "onBatchScanResults called")
for (sr in results!!) {
val device = sr?.device
if (!devices!!.containsKey(device!!.address) && isThisTheDevice(device)) {
deviceFound(device!!)
}
}
}
override fun onScanFailed(errorCode: Int) {
Log.i("[BLE]", "scan failed with errorcode: $errorCode")
}
}
private fun isThisTheDevice(device: BluetoothDevice?): Boolean {
try {
if (ActivityCompat.checkSelfPermission(
ctx,
Manifest.permission.BLUETOOTH_CONNECT
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return false
}
Log.d("BLE", device!!.name)
} catch (e: Exception) {
Log.d("BLE", "Device with null-Name found")
}
return null != device!!.name && device.name.startsWith("OilCheck")
}
private fun deviceFound(device: BluetoothDevice) {
Log.d("Debug", "deviceFound called")
// connectToDevice(device.address)
devices.put(device.address, device)
fireDeviceFound(device)
if (ActivityCompat.checkSelfPermission(
ctx,
Manifest.permission.BLUETOOTH_SCAN
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
scanner!!.stopScan(bleCallback)
}
private fun fireDeviceFound(device: BluetoothDevice) {
// for (l in listeners) {
if (ActivityCompat.checkSelfPermission(
ctx,
Manifest.permission.BLUETOOTH_CONNECT
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
Log.d("Debug", "BLEDeviceFound called")
BLEDeviceFound(device.name.trim { it <= ' ' }, device.address)
// }
}
fun connectToDevice(address: String?) {
Log.d("Debug", "connectToDevice")
this.device = devices[address!!]
if (ActivityCompat.checkSelfPermission(
ctx,
Manifest.permission.BLUETOOTH_SCAN
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
scanner!!.stopScan(bleCallback)
try {
Log.i("[BLE]", "connect to device " + device!!.getAddress())
} catch (e: java.lang.Exception) {
Log.d("Debug", "Connection to device failed :(")
}
this.bluetoothGatt = device!!.connectGatt(null, true, this.bleConnectCallback)
}
private val bleConnectCallback: BluetoothGattCallback = object : BluetoothGattCallback() {
val CHANNEL_ID = "ForegroundServiceChannel"
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
connectionState = true
Log.d("Debug", "connection state changed to connected")
if (ActivityCompat.checkSelfPermission(
ctx,
Manifest.permission.BLUETOOTH_CONNECT
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
Log.i("[BLE]", "start service discovery " + bluetoothGatt.discoverServices())
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
connectionState = false
Log.d("Debug", "connection state changed to not connected")
btGattChar = null
Log.w("[BLE]", "DISCONNECTED with status $status")
fireDisconnected()
} else {
Log.i("[BLE]", "unknown state $newState and status $status")
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (null == btGattChar) {
for (service in gatt.services) {
if (service.uuid.toString().uppercase(Locale.getDefault())
.startsWith("00001811")
) {
val gattCharacteristics = service.characteristics
for (bgc in gattCharacteristics) {
if (bgc.uuid.toString().uppercase(Locale.getDefault())
.startsWith("00002A46")
) {
val chprop = bgc.properties
if (chprop and BluetoothGattCharacteristic.PROPERTY_WRITE or (chprop and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) {
btGattChar = bgc
Log.i("[BLE]", "CONNECTED and ready to send")
fireConnected()
}
}
}
}
}
}
}
}
private fun fireDisconnected() {
for (l in listeners){
l.BLEControllerDisconnected()
}
device = null
}
fun isConnected():Boolean {
if(!connectionState){
return false
}
return true
}
fun sendData(data: ByteArray?) {
btGattChar!!.value = data
if (ActivityCompat.checkSelfPermission(
ctx,
Manifest.permission.BLUETOOTH_CONNECT
) != PackageManager.PERMISSION_GRANTED
) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
bluetoothGatt.writeCharacteristic(btGattChar)
}
private fun fireConnected() {
for (l in listeners) {
l.BLEControllerConnected()
}
}
override fun BLEControllerConnected() {
TODO("Not yet implemented")
}
override fun BLEControllerDisconnected() {
TODO("Not yet implemented")
}
override fun BLEDeviceFound(name: String?, address: String?) {
// log("Device $name found with address $address")
deviceAddress = address
Log.d("Debug", "connectToDevice called")
// btnConnect.setEnabled(true)
connectToDevice(deviceAddress)
}
}

View File

@@ -0,0 +1,14 @@
package com.example.oilcheckkotlin
/*
* (c) Matey Nenov (https://www.thinker-talk.com)
*
* Licensed under Creative Commons: By Attribution 3.0
* http://creativecommons.org/licenses/by/3.0/
*
*/
interface BLEControllerListener {
fun BLEControllerConnected()
fun BLEControllerDisconnected()
fun BLEDeviceFound(name: String?, address: String?)
}

View File

@@ -0,0 +1,58 @@
package com.example.oilcheckkotlin
import android.app.Notification
import android.app.PendingIntent
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import com.luckycatlabs.sunrisesunset.SunriseSunsetCalculator
import com.luckycatlabs.sunrisesunset.dto.Location
import java.util.Calendar
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var location = Location("52.3759", "9.7320")
var calculator = SunriseSunsetCalculator(location, "GMT+0200")
var sunrise = calculator.getOfficialSunsetForDate(Calendar.getInstance())
Log.d("Debug", sunrise)
findViewById<Button>(R.id.btnStop)
.setOnClickListener{
Log.d("Debug", "stop clicked")
val intent = Intent(this, BLEConnectionService::class.java)
.setAction("STOPService")
startService(intent)
}
findViewById<Button>(R.id.btnToggle)
.setOnClickListener {
Log.d("Debug", "Button Toggle")
val intent = Intent(this, BLEConnectionService::class.java)
.setAction("Toggle")
startService(intent)
}
findViewById<Button>(R.id.btnOTA)
.setOnClickListener{
val intent = Intent(this, BLEConnectionService::class.java)
.setAction("OTA")
startService(intent)
}
startForegroundService(
Intent(this, BLEConnectionService::class.java)
.setAction("STARTService")
)
}
}

View File

@@ -0,0 +1,213 @@
package com.example.oilcheckkotlin
import android.app.Notification
import android.content.Intent
import android.os.IBinder
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.Log
import java.util.regex.Pattern
class NLService : NotificationListenerService() {
// override fun onBind(intent: Intent?): IBinder? {
// return super.onBind(intent)
// }
//
// override fun onNotificationPosted(sbn: StatusBarNotification?) {
// // Implement what you want here
// }
//
// override fun onNotificationRemoved(sbn: StatusBarNotification?) {
// // Implement what you want here
// }
//
// private final IBinder mBinder = new LocalBinder();
override fun onBind(intent: Intent): IBinder? {
return super.onBind(intent)
}
//returns the instance of the service
// public class LocalBinder extends Binder{
// public NLService getServiceInstance(){
// return NLService.this;
// }
// }
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d("Debug: ", "BLE service onStartCommand called")
// // String input = intent.getStringExtra("inputExtra");
// val input = "Text"
// createNotificationChannel()
// val notificationIntent = Intent(this, MainActivity::class.java)
// val pendingIntent = PendingIntent.getActivity(
// this,
// 0, notificationIntent, 0
// )
// val notification = NotificationCompat.Builder(this, CHANNEL_ID)
// .setContentTitle("Oil Check")
// .setContentText(input) //.setSmallIcon(R.drawable.ic_launcher_foreground)
// .setSmallIcon(R.drawable.icon)
// .setContentIntent(pendingIntent)
// .build()
// startForeground(1, notification)
// // NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
//// notificationManager.notify(3, notification);
// try {
// bleController = BLEController.getInstance(applicationContext)
// Log.d("BLE", "bleController instance")
// } catch (e: Exception) {
// Log.d("BLE", "bleController not available")
// }
return START_NOT_STICKY
}
// private fun createNotificationChannel() {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// val serviceChannel = NotificationChannel(
// CHANNEL_ID,
// "Foreground Service Channel",
// NotificationManager.IMPORTANCE_DEFAULT
// )
// val manager = getSystemService(
// NotificationManager::class.java
// )
// manager.createNotificationChannel(serviceChannel)
// }
// }
override fun onNotificationPosted(sbn: StatusBarNotification) {
Log.d("Debug", "NL Notification fired")
// Implement what you want here
if (sbn.packageName.contains("de.blitzer.plus")) {
Log.d(sbn.packageName, "sbn.getTag()")
Log.d("MyNotification", sbn.notification.toString())
// filter for digits
try {
val notification =
sbn.notification.extras.getCharSequence(Notification.EXTRA_TEXT).toString()
// check of unit in m or km
val unitM = "(.*)[\\d][m][\\s](.*)"
val unitKm = "(.*)[\\d][k][m][\\s](.*)"
Log.d("Unit", unitM)
if (notification.matches(unitM.toRegex())) {
Log.d("Notification", "meter")
Log.d("Notification", notification)
// notification in meters
// get digits
val pattern = Pattern.compile("[\\d]+[m][\\s]")
val matcher = pattern.matcher(notification)
while (matcher.find()) {
Log.d("Matcher", "Match found")
val start = matcher.start()
val end = matcher.end()
// Log.d("Start", )
// end -2 (one for blank and one for unit(m))
val digits = notification.substring(start, end - 2)
Log.d("Matcher", digits)
val distance = digits.toInt()
Log.d("Distanz", digits)
try {
if (distance > 500) {
Log.d("Distanz", "groesser 500")
val intent = Intent(this, BLEConnectionService::class.java)
.setAction("X")
startService(intent)
// bleController!!.sendData(String("X").toByteArray())
} else if (distance > 300) {
Log.d("Distanz", "groesser 300")
val intent = Intent(this, BLEConnectionService::class.java)
.setAction("XX")
startService(intent)
} else if (distance > 150) {
Log.d("Distanz", "groesser 150")
val intent = Intent(this, BLEConnectionService::class.java)
.setAction("XXX")
startService(intent)
} else {
Log.d("Distanz", "kleiner 100")
val intent = Intent(this, BLEConnectionService::class.java)
.setAction("XXXX")
startService(intent)
}
} catch (e: Exception) {
//
Log.d("DEBUG", "Send Data failed :(")
}
// end while loop after first match
break
}
} else if (notification.matches(unitKm.toRegex())) {
// notification in kilometers
// no need to differ, to much distance
// bleController!!.sendData(String("X").toByteArray())
val intent = Intent(this, BLEConnectionService::class.java)
.setAction("X")
startService(intent)
}
// else if(sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("nicht gefunden") ||
// sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("ungenau")){
// bleController.sendData(new String("XXXXX").getBytes());
// }
// else{
// bleController.sendData(new String("XXX").getBytes());
// }
} // old version not working anymore because of update of Blitzer.de
// try {
// if (sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("1.0km") ||
// sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("1000m")) {
// bleController.sendData(new String("X").getBytes());
// }
// else if(sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("500m")){
// bleController.sendData(new String("XX").getBytes());
// }
// else if(sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("300m") ||
// sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("200m")){
// bleController.sendData(new String("XXX").getBytes());
// }
// else if(sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("100m") ||
// sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("90m") ||
// sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("80m") ||
// sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("70m") ||
// sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("50m")){
// bleController.sendData(new String("XXXX").getBytes());
// }
//// else if(sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("nicht gefunden") ||
//// sbn.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT).toString().contains("ungenau")){
//// bleController.sendData(new String("XXXXX").getBytes());
//// }
//// else{
//// bleController.sendData(new String("XXX").getBytes());
//// }
// }
catch (e: Exception) {
Log.d("DEBUG", "Send notification failed :(")
}
// try {
// bleController.sendData(new String("X").getBytes());
// }
// catch(Exception e){
//
// }
}
// Log.d(sbn.getPackageName(), "packagename");
}
override fun onNotificationRemoved(sbn: StatusBarNotification) {
// Implement what you want here
this.stopForeground(STOP_FOREGROUND_REMOVE)
}
override fun onDestroy() {
super.onDestroy()
Log.d("BLE:", "Service onDestory called")
this.stopForeground(STOP_FOREGROUND_REMOVE)
stopForeground(true)
// stopSelf();
}
companion object {
const val CHANNEL_ID = "ForegroundServiceChannel"
}
}

View File

@@ -0,0 +1,32 @@
package com.example.oilcheckkotlin
import android.R
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
class NotificationCreator {
private val NOTIFICATION_ID = 1094
private val CHANNEL_ID = "Foreground Service Channel"
private var notification: Notification? = null
fun getNotification(context: Context?, intent: PendingIntent): Notification? {
if (notification == null) {
notification = NotificationCompat.Builder(context!!, CHANNEL_ID)
.setContentTitle("Oil-Check")
.setContentText("running")
.setSmallIcon(com.example.oilcheckkotlin.R.drawable.icon)
.setContentIntent(intent)
.build()
}
return notification
}
fun getNotificationId(): Int {
return NOTIFICATION_ID
}
}

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="400dp"
android:layout_height="257dp"
android:orientation="vertical"
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteY="1dp">
<Button
android:id="@+id/btnStop"
android:layout_width="match_parent"
android:layout_height="46dp"
android:text="Stop" />
<Button
android:id="@+id/btnToggle"
android:layout_width="match_parent"
android:layout_height="55dp"
android:text="Toggle" />
<Button
android:id="@+id/btnOTA"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="OTA Update" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,5 @@
<?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" />
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?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" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.OilCheckKotlin" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">OilCheckKotlin</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.OilCheckKotlin" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,17 @@
package com.example.oilcheckkotlin
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}