Skip to content

Commit

Permalink
bluetooth: server script check (fixes #1020) (#1045)
Browse files Browse the repository at this point in the history
Co-authored-by: roshan <[email protected]>
Co-authored-by: dogi <[email protected]>
  • Loading branch information
3 people authored Aug 14, 2020
1 parent 554212e commit 434b536
Show file tree
Hide file tree
Showing 13 changed files with 226 additions and 7 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ gen/
/build
/captures
.externalNativeBuild
app/src/main/assets/bluetooth-server.txt
.vagrant
1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/build
/src/main/assets/bluetooth-server.txt
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.connectbot:sshlib:2.2.9'

implementation 'com.github.kbiakov:CodeView-Android:1.3.2'


def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.treehouses.remote.Fragments

import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Bundle
Expand All @@ -15,11 +16,13 @@ import androidx.core.content.ContextCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import io.treehouses.remote.R
import io.treehouses.remote.callback.HomeInteractListener
import io.treehouses.remote.utils.KeyUtils
import io.treehouses.remote.utils.SaveUtils

class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener {
private var preferenceChangeListener: OnSharedPreferenceChangeListener? = null
private lateinit var listener : HomeInteractListener
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

setPreferencesFromResource(R.xml.app_preferences, rootKey)
Expand All @@ -30,6 +33,7 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic
val clearServices = findPreference<Preference>("clear_services")
val clearSSHHosts = findPreference<Preference>("ssh_hosts")
val clearSSHKeys = findPreference<Preference>("ssh_keys")
val showBluetoothFile = findPreference<Preference>("bluetooth_file")

setClickListener(clearCommandsList)
setClickListener(resetCommandsList)
Expand All @@ -38,6 +42,7 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic
setClickListener(clearServices)
setClickListener(clearSSHHosts)
setClickListener(clearSSHKeys)
setClickListener(showBluetoothFile)

preferenceChangeListener = OnSharedPreferenceChangeListener { sharedPreferences, key ->
if (key == "dark_mode") {
Expand Down Expand Up @@ -87,6 +92,7 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic
"reactivate_tutorials" -> reactivateTutorialsPrompt()
"ssh_hosts" -> clearSSHHosts()
"ssh_keys" -> clearSSHKeys()
"bluetooth_file" -> openBluetoothFile()
}
return false
}
Expand All @@ -109,6 +115,9 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic

private fun clearSSHKeys() = createAlertDialog("Clear All SSH Keys", "Would you like to delete all SSH Keys?", "Clear", CLEAR_SSH_KEYS)

private fun openBluetoothFile() {
listener.openCallFragment(ShowBluetoothFile())
}
private fun createAlertDialog(title: String, message: String, positive: String, ID: Int) {
val dialog = AlertDialog.Builder(ContextThemeWrapper(context, R.style.CustomAlertDialogStyle))
.setTitle(title)
Expand Down Expand Up @@ -172,6 +181,12 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClic
Toast.makeText(context, "Tutorials reactivated", Toast.LENGTH_LONG).show()
}

override fun onAttach(context: Context) {
super.onAttach(context)
listener = if (context is HomeInteractListener) context
else throw Exception("Context does not implement HomeInteractListener")
}

companion object {
private const val CLEAR_COMMANDS_ID = 1
private const val RESET_COMMANDS_ID = 2
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.treehouses.remote.Fragments

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import io.github.kbiakov.codeview.CodeView
import io.github.kbiakov.codeview.adapters.Options
import io.github.kbiakov.codeview.highlight.ColorTheme
import io.treehouses.remote.databinding.CodeViewBinding
import io.treehouses.remote.databinding.FragmentShowBluetoothFileBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class ShowBluetoothFile : Fragment() {
private lateinit var bind : FragmentShowBluetoothFileBinding

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
bind = FragmentShowBluetoothFileBinding.inflate(inflater, container, false)
return bind.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bind.pbar.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.IO) {
val code = context?.assets?.open("bluetooth-server.txt")?.bufferedReader().use { it?.readText() }
Log.e("GOT CODE", code)
withContext(Dispatchers.Main) {
if (code == null) {
bind.fileNotFound.visibility = View.VISIBLE
} else {
withContext(Dispatchers.Default) {
val codeView = createCodeView(code)
withContext(Dispatchers.Main) {
bind.scriptContainer.addView(codeView, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
bind.pbar.visibility = View.GONE
}
}
}
}
}
}

fun createCodeView(code : String) : CodeView {
val codeView = CodeViewBinding.inflate(layoutInflater).root
codeView.setOptions(Options.Default.get(requireContext())
.withLanguage("python")
.withCode(code)
.withTheme(ColorTheme.MONOKAI))
return codeView
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import io.treehouses.remote.Network.ParseDbService
import io.treehouses.remote.bases.BaseFragment
import io.treehouses.remote.callback.SetDisconnect
import io.treehouses.remote.utils.LogUtils
import io.treehouses.remote.utils.Matcher
import io.treehouses.remote.utils.SaveUtils
import io.treehouses.remote.utils.SaveUtils.Screens
import io.treehouses.remote.utils.Utils
import okhttp3.internal.Util
import java.nio.charset.Charset
import java.util.*

open class BaseHomeFragment : BaseFragment() {
Expand Down Expand Up @@ -198,4 +203,30 @@ open class BaseHomeFragment : BaseFragment() {
private fun CreateAlertDialog(context: Context?, id:Int, title: String): AlertDialog.Builder {
return AlertDialog.Builder(ContextThemeWrapper(context, id)).setTitle(title)
}

protected fun syncBluetooth(serverHash: String) {
val inputStream = context?.assets?.open("bluetooth-server.txt")
val localString = inputStream?.bufferedReader().use { it?.readText() }
inputStream?.close()
val hashed = Utils.hashString(localString!!)
Log.e("HASHED", serverHash)
if (Matcher.isError(serverHash)) {
CreateAlertDialog(requireContext(), R.style.CustomAlertDialogStyle, "Upgrade Bluetooth").setMessage("There is a new version of bluetooth available. Please upgrade to receive the latest changes.")
.setPositiveButton("Upgrade") { _, _ ->
listener.sendMessage("treehouses upgrade bluetooth\n")
}
.setNegativeButton("Cancel") {dialog, _ -> dialog.dismiss()}.create().show()
}
else if (hashed.trim() != serverHash.trim()) {
CreateAlertDialog(context, R.style.CustomAlertDialogStyle, "Re-sync Bluetooth Server")
.setMessage("The bluetooth server on the Raspberry Pi does not match the one on your device. Would you like to update the CLI bluetooth server?")
.setPositiveButton("Upgrade") { _, _ ->
Log.e("ENCODED", Utils.compressString(localString))
listener.sendMessage("remotesync ${Utils.compressString(localString).replace("\n","" )} cnysetomer\n")
Toast.makeText(requireContext(), "Bluetooth Upgraded. Please restart Bluetooth to apply the changes.", Toast.LENGTH_LONG).show()
}.setNegativeButton("Cancel") { dialog: DialogInterface, _: Int ->
dialog.dismiss()
}.show()
}
}
}
18 changes: 12 additions & 6 deletions app/src/main/kotlin/io/treehouses/remote/ui/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.*
import androidx.preference.PreferenceManager
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
Expand All @@ -18,7 +19,6 @@ import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import io.treehouses.remote.*
import io.treehouses.remote.Constants.REQUEST_ENABLE_BT
import io.treehouses.remote.Fragments.AboutFragment
Expand All @@ -43,6 +43,7 @@ class HomeFragment : BaseHomeFragment(), SetDisconnect {
private var selectedLed = 0
private var checkVersionSent = false
private var internetSent = false
private var hashSent = false
private var connectionState = false
private var testConnectionResult = false
private var networkSsid = ""
Expand Down Expand Up @@ -174,12 +175,13 @@ class HomeFragment : BaseHomeFragment(), SetDisconnect {
showLogDialog(preferences!!)
transition(true)
connectionState = true
checkVersionSent = true
listener.sendMessage(getString(R.string.TREEHOUSES_REMOTE_VERSION, BuildConfig.VERSION_CODE))
listener.sendMessage("remotehash")
hashSent = true
Tutorials.homeTutorials(bind, requireActivity())
} else {
transition(false)
connectionState = false
hashSent = false
MainApplication.logSent = false
}
mChatService.updateHandler(mHandler)
Expand Down Expand Up @@ -243,11 +245,15 @@ class HomeFragment : BaseHomeFragment(), SetDisconnect {

private fun readMessage(output: String) {
notificationListener = try { context as NotificationCallback?
} catch (e: ClassCastException) {
throw ClassCastException("Activity must implement NotificationListener")
}
} catch (e: ClassCastException) { throw ClassCastException("Activity must implement NotificationListener") }
val s = match(output)
when {
hashSent -> {
syncBluetooth(output)
hashSent = false
checkVersionSent = true
listener.sendMessage(getString(R.string.TREEHOUSES_REMOTE_VERSION, BuildConfig.VERSION_CODE))
}
s == RESULTS.ERROR && !output.toLowerCase(Locale.ROOT).contains("error") -> {
showUpgradeCLI()
internetSent = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ object Matcher {
private fun toLC(string: String) : String {return string.toLowerCase(Locale.ROOT).trim(); }

fun isError(output: String): Boolean {
val keys = listOf("error ", "unknown command", "usage: ", "not a valid option", "error: ")
val keys = listOf("error ", "unknown command", "usage: ", "not a valid option", "error: ", "not found")
if (output.contains("{") || output.contains("}")) return false
for (k in keys) if (toLC(output).contains(k)) return true
return false
Expand Down
28 changes: 28 additions & 0 deletions app/src/main/kotlin/io/treehouses/remote/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ package io.treehouses.remote.utils
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.util.Base64
import android.widget.Toast
import java.io.ByteArrayOutputStream
import java.net.NetworkInterface
import java.nio.charset.Charset
import java.security.MessageDigest
import java.util.*
import java.util.zip.DeflaterOutputStream


object Utils {
fun Context.copyToClipboard(clickedData: String) {
Expand Down Expand Up @@ -50,4 +56,26 @@ object Utils {
fun Context?.toast(s: String, duration: Int = Toast.LENGTH_LONG): Toast {
return Toast.makeText(this, s, duration).apply { show() }
}

fun hashString(toHash: String) : String {
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(toHash.toByteArray(Charset.forName("UTF-8")))

val hexString = StringBuffer()
for (c in hash) {
val hex = Integer.toHexString(0xff and c.toInt())
if (hex.length == 1) hexString.append('0')
hexString.append(hex)
}
return hexString.toString()
}

fun compressString(toCompress: String) : String{
val baos = ByteArrayOutputStream()
val dos = DeflaterOutputStream(baos)
dos.write(toCompress.toByteArray(Charset.forName("UTF-8")))
dos.flush()
dos.close()
return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
}
}
7 changes: 7 additions & 0 deletions app/src/main/res/layout/code_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<io.github.kbiakov.codeview.CodeView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/codeView"
android:layout_width="match_parent"
android:layout_height="match_parent">

</io.github.kbiakov.codeview.CodeView>
57 changes: 57 additions & 0 deletions app/src/main/res/layout/fragment_show_bluetooth_file.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/title"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="@dimen/padding_large"
android:text="bluetooth-server.py"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/scriptContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"/>

<ProgressBar
android:id="@+id/pbar"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />

<LinearLayout
android:id="@+id/fileNotFound"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/ic_update_alert" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="File Not Found!" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
8 changes: 8 additions & 0 deletions app/src/main/res/xml/app_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,13 @@

</PreferenceCategory>

<PreferenceCategory android:title="Advanced" android:layout="@layout/custom_pref_category">
<Preference
android:layout="@layout/custom_pref_top"
android:icon="@drawable/bluetooth"
android:key="bluetooth_file"
android:title="View Bluetooth Server File"/>
</PreferenceCategory>


</PreferenceScreen>
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ buildscript {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
def assets = new File('app/src/main/assets')
assets.mkdir()
def f = new File('app/src/main/assets/bluetooth-server.txt')
f.createNewFile()
new URL('https://raw.githubusercontent.com/treehouses/control/master/server.py').withInputStream{ i -> f.withOutputStream{ it << i }}
}

allprojects {
Expand Down

0 comments on commit 434b536

Please sign in to comment.