diff --git a/README.md b/README.md index b795587..6ff3fe9 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ After setting up the Gradle dependency, you will be able to access two main clas First, you need to **find a Myo** with a bluetooth scan. -** ⚠️ Please note that you need to request the user the ACCESS_COARSE_LOCATION permission. If not, the scan will be empty ⚠️ ** +** ⚠️ Please note that you need to request the user the ACCESS_FINE_LOCATION permission. If not, the scan will be empty ⚠️ ** To start a bluetooth scan, you can use the `startScan()` method: @@ -109,10 +109,10 @@ myMyo.statusObservable() .observeOn(AndroidSchedulers.mainThread()) .subscribe { when (it) { - MyoStatus.CONNECTED -> { ... } - MyoStatus.CONNECTING -> { ... } - MyoStatus.READY -> { ... } - else -> { ... } // DISCONNECTED + MyoStatus.CONNECTED -> { /* ... */ } + MyoStatus.CONNECTING -> { /* ... */ } + MyoStatus.READY -> { /* ... */ } + else -> { /* ... */ } // DISCONNECTED } } ``` @@ -188,7 +188,7 @@ myMyo.keepAlive = false You can find the test app (Myo Emg Visualizer) inside the `app` module.

- + test-app

This app allows you to: @@ -210,24 +210,28 @@ Some technical features are: **Searching for a Myo** - +searching-for-a-myo **Starting the Streaming** - +start-streaming **Exporting to CSV** - +exporting-the-csv ## Building/Testing ⚙️ -### CircleCI [![CircleCI](https://circleci.com/gh/cortinico/myonnaise.svg?style=svg)](https://circleci.com/gh/cortinico/myonnaise) +### CircleCI + +[![CircleCI](https://circleci.com/gh/cortinico/myonnaise.svg?style=svg)](https://circleci.com/gh/cortinico/myonnaise) This projects is built with [**Circle CI 2.0**](https://circleci.com/gh/cortinico/myonnaise/). The CI environment takes care of building the library .AAR, the example app and to run the **JUnit** tests. Test and lint reports are exposes in the **artifacts** section at the end of every build. -### Codecov [![codecov](https://codecov.io/gh/cortinico/myonnaise/branch/master/graph/badge.svg)](https://codecov.io/gh/cortinico/myonnaise) +### Codecov + +[![codecov](https://codecov.io/gh/cortinico/myonnaise/branch/master/graph/badge.svg)](https://codecov.io/gh/cortinico/myonnaise) Circle CI is responsible of uploading Jacoco reports to [Codecov](https://codecov.io/gh/cortinico/myonnaise). When opening a Pull Request, Codecov will post a report of the diff of the test coverage. @@ -235,19 +239,12 @@ Please **don't ignore it**! PR with new features and **without** are likely to b ### Building locally -Before building, make sure you have the following **updated components** from the Android SDK: - -* tools -* platform-tools -* build-tools-28.0.1 -* android-28 - Then just clone the repo locally and build the .AAR with the following command: ```bash git clone git@github.com:cortinico/myonnaise.git cd myonnaise/ -./gradlew app:assemble +./gradlew build ``` The assembled .AAR (library) will be inside the **myonnaise/build/outputs/aar** folder. The assembled .APK (application) will be inside the **app/build/outputs/apk/debug** folder. @@ -273,4 +270,4 @@ Make sure your tests are all green ✅ locally before submitting PRs. ## License 📄 -This project is licensed under the MIT License - see the [License](License) file for details +This project is licensed under the MIT License - see the [License](LICENSE) file for details diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0ee0fa1..400b31f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,25 +3,22 @@ xmlns:tools="http://schemas.android.com/tools"> - - + tools:ignore="GoogleAppIndexingWarning"> + android:name="it.ncorti.emgvisualizer.ui.IntroActivity" + android:exported="true"> , grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - when (requestCode) { + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + when (requestCode) { REQUEST_LOCATION_CODE -> { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) { startMainActivity() } else { Toast.makeText( diff --git a/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDeviceContract.kt b/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDeviceContract.kt index f65b2a9..01bc256 100644 --- a/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDeviceContract.kt +++ b/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDeviceContract.kt @@ -9,10 +9,6 @@ interface ControlDeviceContract { fun showDeviceInformation(name: String?, address: String) - fun showConnectionProgress() - - fun hideConnectionProgress() - fun showConnected() fun showDisconnected() diff --git a/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDeviceFragment.kt b/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDeviceFragment.kt index 1b292b2..9086ca2 100644 --- a/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDeviceFragment.kt +++ b/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDeviceFragment.kt @@ -62,14 +62,6 @@ class ControlDeviceFragment : BaseFragment(), C binding.deviceAddress.text = address } - override fun showConnectionProgress() { - binding.progressConnect.animate().alpha(1.0f) - } - - override fun hideConnectionProgress() { - binding.progressConnect.animate().alpha(0.0f) - } - override fun showConnecting() { binding.deviceStatus.text = getString(R.string.connecting) } diff --git a/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDevicePresenter.kt b/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDevicePresenter.kt index 22db2ad..1e9e312 100644 --- a/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDevicePresenter.kt +++ b/app/src/main/java/it/ncorti/emgvisualizer/ui/control/ControlDevicePresenter.kt @@ -2,6 +2,7 @@ package it.ncorti.emgvisualizer.ui.control +import android.annotation.SuppressLint import com.ncorti.myonnaise.CommandList import com.ncorti.myonnaise.MYO_MAX_FREQUENCY import com.ncorti.myonnaise.MyoControlStatus @@ -12,6 +13,7 @@ import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import it.ncorti.emgvisualizer.dagger.DeviceManager +@SuppressLint("MissingPermission") class ControlDevicePresenter( override val view: ControlDeviceContract.View, private val myonnaise: Myonnaise, @@ -46,18 +48,15 @@ class ControlDevicePresenter( .subscribe { when (it) { MyoStatus.CONNECTED -> { - view.hideConnectionProgress() view.showConnected() } MyoStatus.CONNECTING -> { - view.showConnectionProgress() view.showConnecting() } MyoStatus.READY -> { view.enableControlPanel() } else -> { - view.hideConnectionProgress() view.showDisconnected() view.disableControlPanel() } diff --git a/app/src/main/java/it/ncorti/emgvisualizer/ui/export/ExportFragment.kt b/app/src/main/java/it/ncorti/emgvisualizer/ui/export/ExportFragment.kt index b168e44..f2cd9be 100644 --- a/app/src/main/java/it/ncorti/emgvisualizer/ui/export/ExportFragment.kt +++ b/app/src/main/java/it/ncorti/emgvisualizer/ui/export/ExportFragment.kt @@ -1,20 +1,16 @@ package it.ncorti.emgvisualizer.ui.export -import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.content.ContentValues import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.content.pm.PackageManager.PERMISSION_GRANTED import android.net.Uri -import android.os.Build import android.os.Bundle import android.os.Environment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast -import androidx.core.content.ContextCompat import dagger.android.support.AndroidSupportInjection import it.ncorti.emgvisualizer.BaseFragment import it.ncorti.emgvisualizer.R @@ -46,9 +42,7 @@ class ExportFragment : BaseFragment(), ExportContract. } override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = LayoutExportBinding.inflate(inflater, container, false) setHasOptionsMenu(true) @@ -74,9 +68,7 @@ class ExportFragment : BaseFragment(), ExportContract. override fun showNotStreamingErrorMessage() { Toast.makeText( - activity, - "You can't collect points if Myo is not streaming!", - Toast.LENGTH_SHORT + activity, "You can't collect points if Myo is not streaming!", Toast.LENGTH_SHORT ).show() } @@ -124,53 +116,34 @@ class ExportFragment : BaseFragment(), ExportContract. override fun saveCsvFile(content: String) { val fileName = getCsvFilename() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - // Use scoped storage for API level 29 and above - val contentResolver = context?.contentResolver - val contentValues = ContentValues().apply { - put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, fileName) - put(android.provider.MediaStore.MediaColumns.MIME_TYPE, "text/plain") - put( - android.provider.MediaStore.MediaColumns.RELATIVE_PATH, - Environment.DIRECTORY_DOCUMENTS - ) - } - val uri: Uri? = contentResolver?.insert( - android.provider.MediaStore.Files.getContentUri("external"), - contentValues + // Use scoped storage for API level 29 and above + val contentResolver = context?.contentResolver + val contentValues = ContentValues().apply { + put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, fileName) + put(android.provider.MediaStore.MediaColumns.MIME_TYPE, "text/plain") + put( + android.provider.MediaStore.MediaColumns.RELATIVE_PATH, + Environment.DIRECTORY_DOCUMENTS ) - if (uri != null) { - contentResolver.openOutputStream(uri)?.use { outputStream -> - outputStream.write(content.toByteArray()) - } - } else { - // Handle failure to create the file - error("Failed to create file for scoped storage") + } + val uri: Uri? = contentResolver?.insert( + android.provider.MediaStore.Files.getContentUri("external"), contentValues + ) + if (uri != null) { + contentResolver.openOutputStream(uri)?.use { outputStream -> + outputStream.write(content.toByteArray()) } + val userReadablePath = "${Environment.DIRECTORY_DOCUMENTS}/$fileName" + Toast.makeText(activity, "Export saved to: $userReadablePath", Toast.LENGTH_LONG).show() } else { - context?.apply { - val hasPermission = ( - ContextCompat.checkSelfPermission( - this, - WRITE_EXTERNAL_STORAGE - ) == PERMISSION_GRANTED - ) - if (hasPermission) { - writeToFile(fileName, content) - } else { - fileContentToSave = content - requestPermissions( - arrayOf(WRITE_EXTERNAL_STORAGE), - REQUEST_WRITE_EXTERNAL_CODE - ) - } - } + // Handle failure to create the file + Toast.makeText(activity, "ERROR: Impossible to save export to file", Toast.LENGTH_LONG) + .show() } } private fun writeToFile(fileName: String, content: String) { - val storageDir = - File("${Environment.getExternalStorageDirectory().absolutePath}/myo_emg") + val storageDir = File("${Environment.getExternalStorageDirectory().absolutePath}/myo_emg") storageDir.mkdir() val outfile = File(storageDir, fileName) val fileOutputStream = FileOutputStream(outfile) @@ -181,17 +154,16 @@ class ExportFragment : BaseFragment(), ExportContract. @Deprecated("Deprecated in Java") override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray + requestCode: Int, permissions: Array, grantResults: IntArray ) { when (requestCode) { REQUEST_WRITE_EXTERNAL_CODE -> { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) { fileContentToSave?.apply { writeToFile(getCsvFilename(), this) } } else { Toast.makeText( - activity, getString(R.string.write_permission_denied_message), + activity, + getString(R.string.write_permission_denied_message), Toast.LENGTH_SHORT ).show() } diff --git a/app/src/main/java/it/ncorti/emgvisualizer/ui/export/ExportPresenter.kt b/app/src/main/java/it/ncorti/emgvisualizer/ui/export/ExportPresenter.kt index 62a16bf..f1e519d 100644 --- a/app/src/main/java/it/ncorti/emgvisualizer/ui/export/ExportPresenter.kt +++ b/app/src/main/java/it/ncorti/emgvisualizer/ui/export/ExportPresenter.kt @@ -82,8 +82,8 @@ class ExportPresenter( @VisibleForTesting internal fun createCsv(buffer: ArrayList): String { val stringBuilder = StringBuilder() - buffer.forEach { - it.forEach { + buffer.forEach { floatArray -> + floatArray.forEach { stringBuilder.append(it) stringBuilder.append(";") } diff --git a/app/src/main/java/it/ncorti/emgvisualizer/ui/scan/ScanDevicePresenter.kt b/app/src/main/java/it/ncorti/emgvisualizer/ui/scan/ScanDevicePresenter.kt index 9f34f30..cae9650 100644 --- a/app/src/main/java/it/ncorti/emgvisualizer/ui/scan/ScanDevicePresenter.kt +++ b/app/src/main/java/it/ncorti/emgvisualizer/ui/scan/ScanDevicePresenter.kt @@ -1,6 +1,8 @@ package it.ncorti.emgvisualizer.ui.scan +import android.annotation.SuppressLint import android.bluetooth.BluetoothDevice +import android.util.Log import com.ncorti.myonnaise.Myonnaise import io.reactivex.Flowable import io.reactivex.android.schedulers.AndroidSchedulers @@ -12,6 +14,7 @@ import java.util.concurrent.TimeUnit const val SCAN_INTERVAL_SECONDS = 10L +@SuppressLint("MissingPermission") class ScanDevicePresenter( override val view: ScanDeviceContract.View, private val myonnaise: Myonnaise, @@ -64,12 +67,13 @@ class ScanDevicePresenter( deviceManager.scannedDeviceList.add(it) } }, - { + { error -> view.hideScanLoading() view.showScanError() if (deviceManager.scannedDeviceList.isEmpty()) { view.showEmptyListMessage() } + Log.e("NCOR", "Error", error) }, { view.hideScanLoading() diff --git a/app/src/main/res/layout/item_device.xml b/app/src/main/res/layout/item_device.xml index daa5230..a342eb1 100644 --- a/app/src/main/res/layout/item_device.xml +++ b/app/src/main/res/layout/item_device.xml @@ -50,8 +50,8 @@ android:layout_width="wrap_content" android:layout_height="40dp" android:text="@string/select" - app:layout_constraintBottom_toTopOf="@+id/image_device" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@+id/image_device" /> + app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/layout_control_device.xml b/app/src/main/res/layout/layout_control_device.xml index 5cf35f8..2b92b02 100644 --- a/app/src/main/res/layout/layout_control_device.xml +++ b/app/src/main/res/layout/layout_control_device.xml @@ -1,239 +1,235 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + android:layout_height="wrap_content"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c9cbcfd..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 406a534..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 08d2d01..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index d6675aa..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d7870a7..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 47873c1..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a8a1b40..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index e6ea677..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 893274f..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 5bede0a..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap/ic_launcher.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app/src/main/res/mipmap/ic_launcher.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap/ic_launcher_round.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to app/src/main/res/mipmap/ic_launcher_round.xml diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index c51e8ce..66d0a25 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -6,6 +6,7 @@ 12dp 16dp 40dp + 48dp 72dp @dimen/quadruple_grid_size diff --git a/app/src/test/java/it/ncorti/emgvisualizer/ui/control/ControlDevicePresenterTest.kt b/app/src/test/java/it/ncorti/emgvisualizer/ui/control/ControlDevicePresenterTest.kt index d6d05f4..6672720 100644 --- a/app/src/test/java/it/ncorti/emgvisualizer/ui/control/ControlDevicePresenterTest.kt +++ b/app/src/test/java/it/ncorti/emgvisualizer/ui/control/ControlDevicePresenterTest.kt @@ -37,8 +37,8 @@ class ControlDevicePresenterTest { mockedView = mock {} mockedMyo = mock { - on(this.mock.statusObservable()) doReturn Observable.empty() - on(this.mock.controlObservable()) doReturn Observable.empty() + on(this.mock.statusObservable()) doReturn Observable.empty() + on(this.mock.controlObservable()) doReturn Observable.empty() } mockedMyonnaise = mock {} mockedBluetoothDevice = mock { @@ -77,7 +77,6 @@ class ControlDevicePresenterTest { testPresenter.start() verify(mockedView).showConnecting() - verify(mockedView).showConnectionProgress() } @Test @@ -86,7 +85,6 @@ class ControlDevicePresenterTest { testPresenter.start() - verify(mockedView, atLeast(1)).hideConnectionProgress() verify(mockedView).showConnected() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aad90db..87ddf17 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,12 @@ [versions] -min_sdk_version = "21" -target_sdk_version = "34" -compile_sdk_version = "34" +min_sdk_version = "31" +target_sdk_version = "35" +compile_sdk_version = "35" agp = "8.7.2" androidx_recyclerview = "1.3.2" appcompat = "1.7.0" -appintro = "5.1.0" +appintro = "6.3.1" constraintlayout = "2.2.0" dagger = "2.16" detekt = "1.23.7" diff --git a/kotlin-static-analysis.gradle b/kotlin-static-analysis.gradle index cb50e59..0de1a8b 100644 --- a/kotlin-static-analysis.gradle +++ b/kotlin-static-analysis.gradle @@ -20,5 +20,5 @@ ktlint { detekt { toolVersion = "1.23.7" - config = files("../config/detekt/detekt.yml") + config.setFrom("../config/detekt/detekt.yml") } \ No newline at end of file diff --git a/myonnaise/src/main/AndroidManifest.xml b/myonnaise/src/main/AndroidManifest.xml index 3c36f86..09dbedf 100644 --- a/myonnaise/src/main/AndroidManifest.xml +++ b/myonnaise/src/main/AndroidManifest.xml @@ -1,9 +1,14 @@ - + - - - - + + + + + diff --git a/myonnaise/src/main/java/com/ncorti/myonnaise/CommandList.kt b/myonnaise/src/main/java/com/ncorti/myonnaise/CommandList.kt index 0bf9db7..3fad7a1 100644 --- a/myonnaise/src/main/java/com/ncorti/myonnaise/CommandList.kt +++ b/myonnaise/src/main/java/com/ncorti/myonnaise/CommandList.kt @@ -93,4 +93,4 @@ fun Command.isStartStreamingCommand() = (this[2] != 0x00.toByte() || this[3] != 0x00.toByte() || this[4] != 0x00.toByte()) /** Extension function to check the [Command] is a stop streaming command */ -fun Command.isStopStreamingCommand() = Arrays.equals(this, CommandList.stopStreaming()) +fun Command.isStopStreamingCommand() = this.contentEquals(CommandList.stopStreaming()) diff --git a/myonnaise/src/main/java/com/ncorti/myonnaise/Myo.kt b/myonnaise/src/main/java/com/ncorti/myonnaise/Myo.kt index b1cabcd..f6f2d12 100644 --- a/myonnaise/src/main/java/com/ncorti/myonnaise/Myo.kt +++ b/myonnaise/src/main/java/com/ncorti/myonnaise/Myo.kt @@ -33,6 +33,7 @@ enum class MyoControlStatus { * * @param device The [BluetoothDevice] that is backing this Myo. */ +@Suppress("MissingPermission") class Myo(private val device: BluetoothDevice) : BluetoothGattCallback() { /** The Device Name of this Myo */ @@ -53,7 +54,7 @@ class Myo(private val device: BluetoothDevice) : BluetoothGattCallback() { * Keep alive flag. If set to true, the library will send a [CommandList.unSleep] command * to the device every [KEEP_ALIVE_INTERVAL_MS] ms. */ - var keepAlive = true + private var keepAlive = true private var lastKeepAlive = 0L // Subjects for publishing outside Connection Status, Control Status and the Data (Float Arrays). @@ -61,7 +62,7 @@ class Myo(private val device: BluetoothDevice) : BluetoothGattCallback() { BehaviorSubject.createDefault(MyoStatus.DISCONNECTED) internal val controlStatusSubject: BehaviorSubject = BehaviorSubject.createDefault(MyoControlStatus.NOT_STREAMING) - internal val dataProcessor: PublishProcessor = PublishProcessor.create() + private val dataProcessor: PublishProcessor = PublishProcessor.create() internal var gatt: BluetoothGatt? = null private var byteReader = ByteReader() @@ -246,7 +247,8 @@ class Myo(private val device: BluetoothDevice) : BluetoothGattCallback() { gatt.readCharacteristic(readQueue.element()) } - @SuppressLint("DefaultLocale") + @Deprecated("Deprecated in Java") + @SuppressLint("DefaultLocale", "MissingPermission") override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { super.onCharacteristicRead(gatt, characteristic, status) readQueue.remove() @@ -275,10 +277,12 @@ class Myo(private val device: BluetoothDevice) : BluetoothGattCallback() { } } - if (readQueue.size > 0) + if (readQueue.size > 0) { gatt.readCharacteristic(readQueue.element()) + } } + @Deprecated("Deprecated in Java") override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { super.onCharacteristicChanged(gatt, characteristic) diff --git a/myonnaise/src/main/java/com/ncorti/myonnaise/Myonnaise.kt b/myonnaise/src/main/java/com/ncorti/myonnaise/Myonnaise.kt index 2ab33e6..b02f913 100644 --- a/myonnaise/src/main/java/com/ncorti/myonnaise/Myonnaise.kt +++ b/myonnaise/src/main/java/com/ncorti/myonnaise/Myonnaise.kt @@ -19,9 +19,10 @@ import java.util.concurrent.TimeUnit * Use this class to do a Bluetooth scan and search for a new [Myo]. * * Please note that in order to perform a Bluetooth Scan, the user needs to provide the - * [android.permission.ACCESS_COARSE_LOCATION] permission. You must request this permission to + * `android.permission.ACCESS_FINE_LOCATION` permission. You must request this permission to * the user otherwise your scan will be empty. */ +@Suppress("MissingPermission") class Myonnaise(val context: Context) { private val blManager = context.getSystemService(Activity.BLUETOOTH_SERVICE) as BluetoothManager diff --git a/myonnaise/src/test/java/com/ncorti/myonnaise/ByteReaderTest.kt b/myonnaise/src/test/java/com/ncorti/myonnaise/ByteReaderTest.kt index c7fb474..1aed91b 100644 --- a/myonnaise/src/test/java/com/ncorti/myonnaise/ByteReaderTest.kt +++ b/myonnaise/src/test/java/com/ncorti/myonnaise/ByteReaderTest.kt @@ -72,7 +72,7 @@ class ByteReaderTest { val resultArray = testReader.getBytes(8) assertEquals(8, resultArray.size) - for (i in 0 until resultArray.size) { + for (i in resultArray.indices) { assertEquals(i.toFloat(), resultArray[i]) } } diff --git a/sensorgraphview/src/main/java/com/ncorti/myonnaise/sensorgraphview/SensorGraphView.kt b/sensorgraphview/src/main/java/com/ncorti/myonnaise/sensorgraphview/SensorGraphView.kt index 79af9b5..c34f45f 100644 --- a/sensorgraphview/src/main/java/com/ncorti/myonnaise/sensorgraphview/SensorGraphView.kt +++ b/sensorgraphview/src/main/java/com/ncorti/myonnaise/sensorgraphview/SensorGraphView.kt @@ -7,6 +7,7 @@ import android.graphics.Paint import android.util.AttributeSet import android.view.View import androidx.core.content.ContextCompat +import kotlin.math.min /** Default point circle size */ private const val CIRCLE_SIZE_DEFAULT = 3 @@ -33,7 +34,7 @@ class SensorGraphView(context: Context, attrs: AttributeSet) : View(context, att var maxValue = INITIAL_MAX_VALUE var minValue = INITIAL_MIN_VALUE - val spread: Float + private val spread: Float get() = maxValue - minValue private val zeroLine: Float @@ -52,9 +53,9 @@ class SensorGraphView(context: Context, attrs: AttributeSet) : View(context, att init { val colors = context.resources.getIntArray(R.array.graph_colors) - for (i in 0 until colors.size) { + for (element in colors) { val paint = Paint() - paint.color = Color.parseColor("#${Integer.toHexString(colors[i])}") + paint.color = Color.parseColor("#${Integer.toHexString(element)}") rectPaints += paint } @@ -73,14 +74,14 @@ class SensorGraphView(context: Context, attrs: AttributeSet) : View(context, att val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight val width = when (MeasureSpec.getMode(widthMeasureSpec)) { MeasureSpec.EXACTLY -> widthSize - MeasureSpec.AT_MOST -> Math.min(desiredWidth, widthSize) + MeasureSpec.AT_MOST -> min(desiredWidth, widthSize) MeasureSpec.UNSPECIFIED -> desiredWidth else -> desiredWidth } val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom val height = when (MeasureSpec.getMode(heightMeasureSpec)) { MeasureSpec.EXACTLY -> heightSize - MeasureSpec.AT_MOST -> Math.min(desiredHeight, heightSize) + MeasureSpec.AT_MOST -> min(desiredHeight, heightSize) MeasureSpec.UNSPECIFIED -> desiredHeight else -> desiredHeight }