diff --git a/app/src/main/java/eu/ztsh/garmin/Garmin.kt b/app/src/main/java/eu/ztsh/garmin/Garmin.kt index 27f9b03..7065e71 100644 --- a/app/src/main/java/eu/ztsh/garmin/Garmin.kt +++ b/app/src/main/java/eu/ztsh/garmin/Garmin.kt @@ -17,6 +17,7 @@ import java.io.IOException import java.util.* import java.util.concurrent.Executors import java.util.concurrent.SynchronousQueue +import java.util.concurrent.atomic.AtomicBoolean @SuppressLint("MissingPermission") class Garmin( @@ -47,6 +48,14 @@ class Garmin( locations.start() } + fun reconnect() { + Log.d(TAG, "reconnect pressed") + if (!connection.isConnecting.get()) { + Log.d(TAG, "reconnection enqueued") + context.runAsync { connection.tryConnect() } + } + } + fun close() { connection.close() @@ -184,7 +193,9 @@ class Garmin( private inner class ConnectThread : Thread() { + val isConnecting = AtomicBoolean(false) private val queue: SynchronousQueue = SynchronousQueue() + private val state = AtomicBoolean(false) private var current: IntArray = intArrayOf() private val socket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) { @@ -192,18 +203,37 @@ class Garmin( device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")) } + fun tryConnect() { + Log.d(TAG, "Connection requested") + if (!isConnecting.get()) { + Log.d(TAG, "Trying to connect...") + isConnecting.set(true) + context.checkBt() + // Cancel discovery because it otherwise slows down the connection. + adapter.cancelDiscovery() + try { + socket?.connect() + context.toastConnectionStatus(true) + sleep(3000) + readAll() + send(intArrayOf(0x07, 0x01)) // Set GPS to true + send(intArrayOf(0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) // Clear screen + state.set(true) + } catch (e: IOException) { + Log.d(TAG, "Not connected", e) + context.toastConnectionStatus(false) + state.set(false) + } + queue.clear() + current = intArrayOf() + isConnecting.set(false) + } + } + override fun run() { - // Cancel discovery because it otherwise slows down the connection. - context.checkBt() - adapter.cancelDiscovery() - try { - socket?.connect() - context.setConnectionStatus(true) - sleep(3000) - readAll() - send(intArrayOf(0x07, 0x01)) // Set GPS to true - send(intArrayOf(0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) // Clear screen - while (true) { + tryConnect() + while (true) { + if (state.get()) { val newCurrent = queue.poll() if (newCurrent == null) { Log.d(TAG, "run (${currentThread().name}): Sleep...") @@ -212,15 +242,9 @@ class Garmin( current = newCurrent } send(current) - } - } catch (e: IOException) { - Log.d(TAG, "Not connected", e) - context.setConnectionStatus(false) - while (true) { - // Just dequeue - // TODO: Add option to reconnect - queue.poll() - sleep(900) + } else { + queue.clear() + sleep(2000) } } } @@ -253,9 +277,13 @@ class Garmin( } private fun sendRaw(buff: IntArray) { - buff.forEach { socket!!.outputStream.write(it) } - socket!!.outputStream.flush() - readAll() + try { + buff.forEach { socket!!.outputStream.write(it) } + socket!!.outputStream.flush() + readAll() + } catch (e: Exception) { + Log.d(TAG, "sendRaw: ${e.message}") + } } } diff --git a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt index c2f5e9d..4a04353 100644 --- a/app/src/main/java/eu/ztsh/garmin/MainActivity.kt +++ b/app/src/main/java/eu/ztsh/garmin/MainActivity.kt @@ -17,6 +17,7 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.lifecycleScope import com.mapbox.navigation.base.options.NavigationOptions import com.mapbox.navigation.core.MapboxNavigation import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp @@ -25,6 +26,9 @@ import com.mapbox.navigation.core.lifecycle.requireMapboxNavigation import eu.ztsh.garmin.databinding.ActivityMainBinding import eu.ztsh.garmin.mapbox.MapControl import eu.ztsh.garmin.util.PermissionsHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.lang.ref.WeakReference @@ -124,7 +128,15 @@ class MainActivity : AppCompatActivity() { return true } - fun setConnectionStatus(success: Boolean) { + fun runAsync(action: () -> Unit) { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + action() + } + } + } + + fun toastConnectionStatus(success: Boolean) { this.runOnUiThread { if (success) { Toast.makeText(this, "Garmin connected", Toast.LENGTH_LONG).show() diff --git a/app/src/main/java/eu/ztsh/garmin/UI.kt b/app/src/main/java/eu/ztsh/garmin/UI.kt index c16bae5..0277486 100644 --- a/app/src/main/java/eu/ztsh/garmin/UI.kt +++ b/app/src/main/java/eu/ztsh/garmin/UI.kt @@ -59,4 +59,6 @@ class UI(b: ActivityMainBinding) { val searchResultsView = b.searchResults val searchPlaceView = b.searchPlaces val queryEditText = b.query + + val reconnect = b.reconnect } \ No newline at end of file diff --git a/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt index 440731c..8921dab 100644 --- a/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt +++ b/app/src/main/java/eu/ztsh/garmin/mapbox/MapControl.kt @@ -22,6 +22,7 @@ import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSou import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler import com.mapbox.navigation.ui.maps.camera.state.NavigationCameraState import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider +import eu.ztsh.garmin.Garmin import eu.ztsh.garmin.MainActivity import eu.ztsh.garmin.UI import eu.ztsh.garmin.mock.ReplayResources @@ -105,6 +106,12 @@ class MapControl( locationObserver = LocationObserver(this) routeProgressObserver = routeControl.routeProgressObserver voiceInstructionsObserver = voiceControl.voiceInstructionsObserver + + + ui.reconnect.setOnClickListener { + Garmin.instance.reconnect() + ui.reconnect.showTextAndExtend(UI.BUTTON_ANIMATION_DURATION) + } } fun routeToPoint(point: Point) { diff --git a/app/src/main/java/eu/ztsh/garmin/view/ExtendableButton.kt b/app/src/main/java/eu/ztsh/garmin/view/ExtendableButton.kt new file mode 100644 index 0000000..7ada0b3 --- /dev/null +++ b/app/src/main/java/eu/ztsh/garmin/view/ExtendableButton.kt @@ -0,0 +1,119 @@ +package eu.ztsh.garmin.view + +import android.content.Context +import android.content.res.TypedArray +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.annotation.StyleRes +import androidx.annotation.UiThread +import androidx.constraintlayout.widget.ConstraintLayout +import com.mapbox.navigation.ui.components.R +import eu.ztsh.garmin.R.styleable.* +import com.mapbox.navigation.ui.components.databinding.ExtendableButtonLayoutBinding +import com.mapbox.navigation.ui.utils.internal.ExtendableButtonHelper + +/** + * Mapbox extendable generic. + */ +@UiThread +class ExtendableButton : ConstraintLayout { + + private val binding = ExtendableButtonLayoutBinding.inflate(LayoutInflater.from(context), this) + private val helper = ExtendableButtonHelper( + binding.buttonText, + context.resources.getDimensionPixelSize(R.dimen.mapbox_button_size), + context.resources.getDimension(R.dimen.mapbox_recenterButton_minExtendedWidth), + ) + private lateinit var expandedText: String + + /** + * + * @param context Context + * @constructor + */ + constructor(context: Context) : super(context) + + /** + * + * @param context Context + * @param attrs AttributeSet? + * @constructor + */ + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + initAttributes(attrs) + } + + /** + * + * @param context Context + * @param attrs AttributeSet? + * @param defStyleAttr Int + * @constructor + */ + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) { + initAttributes(attrs) + } + + /** + * Allows you to change the style of [ExtendableButton]. + * @param style Int + */ + fun updateStyle(@StyleRes style: Int) { + val typedArray = context.obtainStyledAttributes( + style, + ExtendableButton + ) + applyAttributes(typedArray) + typedArray.recycle() + } + + /** + * Invoke the function to show optional text associated with the view. + * @param duration for the view to be in the extended mode before it starts to shrink. + * @param text for the view to show in the extended mode. + */ + @JvmOverloads + fun showTextAndExtend( + duration: Long, + text: String = expandedText, + ) { + if (!helper.isAnimationRunning) { + helper.showTextAndExtend(text, duration) + } + } + + private fun initAttributes(attrs: AttributeSet?) { + val typedArray = context.obtainStyledAttributes( + attrs, + ExtendableButton, + 0, + 0 + ) + applyAttributes(typedArray) + typedArray.recycle() + } + + private fun applyAttributes(typedArray: TypedArray) { + typedArray.getDrawable(ExtendableButton_btn_icon) + .also { binding.buttonIcon.setImageDrawable(it) } + + typedArray.getDrawable( + R.styleable.MapboxRecenterButton_recenterButtonBackground, + )?.let { background -> + binding.buttonIcon.background = background + binding.buttonText.background = background + } + + typedArray.getColorStateList( + R.styleable.MapboxRecenterButton_recenterButtonTextColor, + )?.let { binding.buttonText.setTextColor(it) } + + typedArray.apply { + expandedText = getString(ExtendableButton_expanded_text) ?: "DUNNO" + } + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index bfea266..83cf4b1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -127,4 +127,15 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/routeOverview" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..ef249b8 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 774ae0a..b845fca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,4 +7,6 @@ Error happened during request Not implemented yet + Reconnect +