Compare commits
6 Commits
develop
...
dbkr/ptt-r
Author | SHA1 | Date | |
---|---|---|---|
|
60c2c8e222 | ||
|
9c81cf463b | ||
|
d42fd395b0 | ||
|
afcdf0b56f | ||
|
16ac259f4a | ||
|
8667831c8d |
@ -16,9 +16,14 @@
|
|||||||
|
|
||||||
package im.vector.app.features.widgets
|
package im.vector.app.features.widgets
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import android.bluetooth.BluetoothSocket
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -27,9 +32,14 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.webkit.PermissionRequest
|
import android.webkit.PermissionRequest
|
||||||
|
import android.webkit.WebMessage
|
||||||
|
import android.webkit.WebView
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.Success
|
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.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.platform.OnBackPressed
|
import im.vector.app.core.platform.OnBackPressed
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
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.openUrlInExternalBrowser
|
||||||
|
import im.vector.app.core.utils.registerForPermissionsResult
|
||||||
import im.vector.app.databinding.FragmentRoomWidgetBinding
|
import im.vector.app.databinding.FragmentRoomWidgetBinding
|
||||||
import im.vector.app.features.webview.WebEventListener
|
import im.vector.app.features.webview.WebEventListener
|
||||||
import im.vector.app.features.widgets.webview.WebviewPermissionUtils
|
import im.vector.app.features.widgets.webview.WebviewPermissionUtils
|
||||||
import im.vector.app.features.widgets.webview.clearAfterWidget
|
import im.vector.app.features.widgets.webview.clearAfterWidget
|
||||||
import im.vector.app.features.widgets.webview.setupForWidget
|
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 kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.IOException
|
||||||
import java.net.URISyntaxException
|
import java.net.URISyntaxException
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@ -80,6 +101,7 @@ class WidgetFragment @Inject constructor(
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
|
WebView.setWebContentsDebuggingEnabled(true);
|
||||||
views.widgetWebView.setupForWidget(this)
|
views.widgetWebView.setupForWidget(this)
|
||||||
if (fragmentArgs.kind.isAdmin()) {
|
if (fragmentArgs.kind.isAdmin()) {
|
||||||
viewModel.getPostAPIMediator().setWebView(views.widgetWebView)
|
viewModel.getPostAPIMediator().setWebView(views.widgetWebView)
|
||||||
@ -266,6 +288,7 @@ class WidgetFragment @Inject constructor(
|
|||||||
|
|
||||||
override fun onPageFinished(url: String) {
|
override fun onPageFinished(url: String) {
|
||||||
viewModel.handle(WidgetAction.OnWebViewLoadingSuccess(url))
|
viewModel.handle(WidgetAction.OnWebViewLoadingSuccess(url))
|
||||||
|
connectBluetoothDevice()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageError(url: String, errorCode: Int, description: String) {
|
override fun onPageError(url: String, errorCode: Int, description: String) {
|
||||||
@ -281,13 +304,14 @@ class WidgetFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPermissionRequest(request: PermissionRequest) {
|
override fun onPermissionRequest(request: PermissionRequest) {
|
||||||
permissionUtils.promptForPermissions(
|
/*permissionUtils.promptForPermissions(
|
||||||
title = R.string.room_widget_resource_permission_title,
|
title = R.string.room_widget_resource_permission_title,
|
||||||
request = request,
|
request = request,
|
||||||
context = requireContext(),
|
context = requireContext(),
|
||||||
activity = requireActivity(),
|
activity = requireActivity(),
|
||||||
activityResultLauncher = permissionResultLauncher
|
activityResultLauncher = permissionResultLauncher
|
||||||
)
|
)*/
|
||||||
|
request.grant(request.resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displayTerms(displayTerms: WidgetViewEvents.DisplayTerms) {
|
private fun displayTerms(displayTerms: WidgetViewEvents.DisplayTerms) {
|
||||||
@ -340,4 +364,149 @@ class WidgetFragment @Inject constructor(
|
|||||||
private fun revokeWidget() {
|
private fun revokeWidget() {
|
||||||
viewModel.handle(WidgetAction.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,8 @@ fun WebView.setupForWidget(eventListener: WebEventListener) {
|
|||||||
|
|
||||||
val cookieManager = CookieManager.getInstance()
|
val cookieManager = CookieManager.getInstance()
|
||||||
cookieManager.setAcceptThirdPartyCookies(this, false)
|
cookieManager.setAcceptThirdPartyCookies(this, false)
|
||||||
|
|
||||||
|
settings.mediaPlaybackRequiresUserGesture = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun WebView.clearAfterWidget() {
|
fun WebView.clearAfterWidget() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user