Compare commits

...

6 Commits

Author SHA1 Message Date
David Baker
60c2c8e222 HACK: Grant all webview permissions 2022-06-14 18:33:15 +01:00
David Baker
9c81cf463b Allow media playback in webview without user interaction
also catch exception connecting to bluetooth socket
2022-06-14 18:05:40 +01:00
Johannes Marbach
d42fd395b0 Stop alerting socket read failures 2022-06-14 14:13:37 +02:00
Johannes Marbach
afcdf0b56f Use life-cycle scope / isActive / yield to properly cancel background job 2022-06-14 14:09:48 +02:00
David Baker
16ac259f4a Tweaks to make the rfcomm code work & send messages over postmessage 2022-06-06 18:49:51 +01:00
Johannes Marbach
8667831c8d Attempt to connect PTT device via RFCOMM 2022-06-03 10:48:20 +02:00
2 changed files with 173 additions and 2 deletions

View File

@ -16,9 +16,14 @@
package im.vector.app.features.widgets
import android.Manifest
import android.app.Activity
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothSocket
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
@ -27,9 +32,14 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.webkit.PermissionRequest
import android.webkit.WebMessage
import android.webkit.WebView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
@ -42,16 +52,27 @@ import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.openUrlInExternalBrowser
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.FragmentRoomWidgetBinding
import im.vector.app.features.webview.WebEventListener
import im.vector.app.features.widgets.webview.WebviewPermissionUtils
import im.vector.app.features.widgets.webview.clearAfterWidget
import im.vector.app.features.widgets.webview.setupForWidget
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.terms.TermsService
import timber.log.Timber
import java.io.IOException
import java.net.URISyntaxException
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import java.util.UUID
import javax.inject.Inject
@Parcelize
@ -80,6 +101,7 @@ class WidgetFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
WebView.setWebContentsDebuggingEnabled(true);
views.widgetWebView.setupForWidget(this)
if (fragmentArgs.kind.isAdmin()) {
viewModel.getPostAPIMediator().setWebView(views.widgetWebView)
@ -266,6 +288,7 @@ class WidgetFragment @Inject constructor(
override fun onPageFinished(url: String) {
viewModel.handle(WidgetAction.OnWebViewLoadingSuccess(url))
connectBluetoothDevice()
}
override fun onPageError(url: String, errorCode: Int, description: String) {
@ -281,13 +304,14 @@ class WidgetFragment @Inject constructor(
}
override fun onPermissionRequest(request: PermissionRequest) {
permissionUtils.promptForPermissions(
/*permissionUtils.promptForPermissions(
title = R.string.room_widget_resource_permission_title,
request = request,
context = requireContext(),
activity = requireActivity(),
activityResultLauncher = permissionResultLauncher
)
)*/
request.grant(request.resources);
}
private fun displayTerms(displayTerms: WidgetViewEvents.DisplayTerms) {
@ -340,4 +364,149 @@ class WidgetFragment @Inject constructor(
private fun revokeWidget() {
viewModel.handle(WidgetAction.RevokeWidget)
}
// Bluetooth hacks
private fun getRequiredBluetoothPermissions(): List<String> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return listOf(Manifest.permission.BLUETOOTH_CONNECT)
}
return listOf(Manifest.permission.BLUETOOTH)
}
private val bluetoothPermissionLauncher = registerForPermissionsResult { allGranted, _ ->
if (allGranted) {
onBluetoothPermissionGranted()
} else {
informInWebView("Could not acquire Bluetooth permissions")
}
}
private var startedBluetoothConnection = false
private fun connectBluetoothDevice() {
if (startedBluetoothConnection) {
return
}
startedBluetoothConnection = true
if (checkPermissions(getRequiredBluetoothPermissions(), requireActivity(), bluetoothPermissionLauncher)) {
onBluetoothPermissionGranted()
}
}
private var bluetoothSocket: BluetoothSocket? = null
@RequiresApi(Build.VERSION_CODES.M)
private fun onBluetoothPermissionGranted() {
val manager = requireContext().getSystemService<BluetoothManager>()
val device = manager?.adapter?.bondedDevices?.firstOrNull {
//it.bluetoothClass.hasService(0x6666)
it.name.contains("PTT") || it.name.contains("B01")
}
if (device == null) {
val devices = manager?.adapter?.bondedDevices?.joinToString { "${it.name} (${it.address})" }
informInWebView("Could not locate PTT device among bonded devices $devices")
return
}
//informInWebView("Connected to PTT device ${device.name} (${device.address})")
Timber.i("Connected to PTT device ${device.name} (${device.address})")
bluetoothSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
// Alternatively: device.createInsecureRfcommSocketToServiceRecord(...)
try {
bluetoothSocket?.connect()
} catch (e: IOException) {
informInWebView("Failed to open RFCOMM socket: $e")
return;
}
//informInWebView("Opened RFCOMM socket")
//informInWebView("Created socket")
lifecycleScope.launch {
withContext(Dispatchers.IO) {
var inputStream = bluetoothSocket?.inputStream
val inputBuffer = ByteArray(1024)
/*try {
bluetoothSocket?.connect()
inputStream = bluetoothSocket?.inputStream
} catch (e: IOException){
informInWebView("Failed to open RFCOMM socket: $e")
bluetoothSocket = null
return@async;
}*/
//informInWebView("Opened RFCOMM socket")
while (isActive) {
/*if (bluetoothSocket?.isConnected != true) {
continue
}*/
try {
val numbytes = inputStream?.read(inputBuffer)
val strData = StandardCharsets.UTF_8.decode(numbytes?.let { ByteBuffer.wrap(inputBuffer, 0, it) });
//informInWebView("read $numbytes bytes: $strData, strdata is " + strData.length + " bytes, byte 6 is " + strData[6].code)
val widgetUri = Uri.parse(fragmentArgs.baseUrl)
//val widgetUri = Uri.EMPTY
if (strData.startsWith("+PTT=P")) {
//informInWebView("ptt down")
//val msg = JSONObject()
//msg.put("pttbutton", true)
yield()
requireActivity().runOnUiThread {
//views.widgetWebView.postWebMessage(WebMessage(msg.toString()), widgetUri)
views.widgetWebView.postWebMessage(WebMessage("pttp"), widgetUri)
}
} else if (strData.startsWith("+PTT=R")) {
//informInWebView("ptt up")
//val msg = JSONObject()
//msg.put("pttbutton", false)
yield()
requireActivity().runOnUiThread {
//views.widgetWebView.postWebMessage(WebMessage(msg.toString()), widgetUri)
views.widgetWebView.postWebMessage(WebMessage("pttr"), widgetUri)
}
}
} catch (e: IOException) {
yield()
// informInWebView("Failed to read from socket: $e")
break
}
//informInWebView("data: " + inputBuffer.to)
//val strData = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(inputBuffer, 0, num));
//informInWebView("<$strData>")
//informInWebView("got data " + StandardCharsets.UTF_8.decode(ByteBuffer.wrap(inputBuffer)))
/*if (inputBuffer[5].toInt() == 80) {
informInWebView("Start talking inferred from ${inputBuffer.slice(0..32)}...")
} else {
informInWebView("Stop talking inferred from ${inputBuffer.slice(0..32)}...")
}*/
}
}
}
}
fun informInWebView(message: String) {
requireActivity().runOnUiThread {
views.widgetWebView.evaluateJavascript("alert('${message}');", null)
}
}
override fun onDestroy() {
super.onDestroy()
bluetoothSocket?.close()
}
}

View File

@ -66,6 +66,8 @@ fun WebView.setupForWidget(eventListener: WebEventListener) {
val cookieManager = CookieManager.getInstance()
cookieManager.setAcceptThirdPartyCookies(this, false)
settings.mediaPlaybackRequiresUserGesture = false
}
fun WebView.clearAfterWidget() {