mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-22 15:52:01 -04:00
#14: WIP we now show the new GUI properly
This commit is contained in:
@@ -36,6 +36,10 @@
|
||||
<!-- Needed to open our bluetooth connection to our paired device (after reboot) -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<!-- For android >=26 we can use the new BLE scanning API, which allows auto launching our service when our device is seen -->
|
||||
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
|
||||
<uses-permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" />
|
||||
|
||||
<!-- the xing library will try to bring this permission in but we don't want it -->
|
||||
<uses-permission
|
||||
android:name="android.permission.CAMERA"
|
||||
@@ -45,6 +49,11 @@
|
||||
android:name="android.hardware.bluetooth_le"
|
||||
android:required="true" />
|
||||
|
||||
<!-- For the modern BLE scanning API -->
|
||||
<uses-feature
|
||||
android:name="android.software.companion_device_setup"
|
||||
android:required="false" />
|
||||
|
||||
<!-- hardware acceleration is required for zxing barcode lib -->
|
||||
<application
|
||||
tools:replace="android:icon"
|
||||
|
||||
@@ -2,8 +2,12 @@ package com.geeksville.mesh
|
||||
|
||||
// import kotlinx.android.synthetic.main.tabs.*
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.companion.CompanionDeviceManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -104,6 +108,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||
const val REQUEST_ENABLE_BT = 10
|
||||
const val DID_REQUEST_PERM = 11
|
||||
const val RC_SIGN_IN = 12 // google signin completed
|
||||
const val RC_SELECT_DEVICE = 65549 // seems to be hardwired in CompanionDeviceManager
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +174,9 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.BLUETOOTH,
|
||||
Manifest.permission.BLUETOOTH_ADMIN,
|
||||
Manifest.permission.WAKE_LOCK
|
||||
Manifest.permission.WAKE_LOCK,
|
||||
Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND,
|
||||
Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND
|
||||
|
||||
// We only need this for logging to capture files for the simulator - turn off for most users
|
||||
// Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
@@ -380,6 +387,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||
/**
|
||||
* Dispatch incoming result to the correct fragment.
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
override fun onActivityResult(
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
@@ -389,12 +397,27 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||
|
||||
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
|
||||
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
|
||||
if (requestCode == RC_SIGN_IN) {
|
||||
// The Task returned from this call is always completed, no need to attach
|
||||
// a listener.
|
||||
val task: Task<GoogleSignInAccount> =
|
||||
GoogleSignIn.getSignedInAccountFromIntent(data)
|
||||
handleSignInResult(task)
|
||||
when (requestCode) {
|
||||
RC_SIGN_IN -> {
|
||||
// The Task returned from this call is always completed, no need to attach
|
||||
// a listener.
|
||||
val task: Task<GoogleSignInAccount> =
|
||||
GoogleSignIn.getSignedInAccountFromIntent(data)
|
||||
handleSignInResult(task)
|
||||
}
|
||||
RC_SELECT_DEVICE -> when (resultCode) {
|
||||
Activity.RESULT_OK -> {
|
||||
// User has chosen to pair with the Bluetooth device.
|
||||
val device: BluetoothDevice =
|
||||
data!!.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
|
||||
debug("Received BLE pairing ${device.address}")
|
||||
device.createBond()
|
||||
// ... Continue interacting with the paired device.
|
||||
}
|
||||
|
||||
else ->
|
||||
warn("BLE device select intent failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import androidx.core.content.edit
|
||||
@@ -197,6 +199,18 @@ class RadioInterfaceService : Service(), Logging {
|
||||
MeshService.startService(context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Can we use the modern BLE scan API?
|
||||
fun hasCompanionDeviceApi(context: Context): Boolean =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val res =
|
||||
context.packageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
|
||||
debug("CompanionDevice API available=$res")
|
||||
res
|
||||
} else {
|
||||
warn("CompanionDevice API not available, falling back to classic scan")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.le.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.companion.AssociationRequest
|
||||
import android.companion.BluetoothDeviceFilter
|
||||
import android.companion.CompanionDeviceManager
|
||||
import android.content.*
|
||||
import android.os.Bundle
|
||||
import android.os.ParcelUuid
|
||||
import android.view.LayoutInflater
|
||||
@@ -23,17 +24,19 @@ import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.hideKeyboard
|
||||
import com.geeksville.mesh.MainActivity
|
||||
import com.geeksville.mesh.MainActivity.Companion.RC_SELECT_DEVICE
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.RadioInterfaceService
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.android.synthetic.main.settings_fragment.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object SLogging : Logging {}
|
||||
|
||||
/// Change to a new macaddr selection, updating GUI and radio
|
||||
fun changeDeviceSelection(context: MainActivity, newAddr: String) {
|
||||
fun changeDeviceSelection(context: MainActivity, newAddr: String?) {
|
||||
RadioInterfaceService.setBondedDeviceAddress(context, newAddr)
|
||||
|
||||
// Super ugly hack. we force the activity to reconnect FIXME, find a cleaner way
|
||||
@@ -249,11 +252,20 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||
|
||||
private val scanModel: BTScanModel by activityViewModels()
|
||||
private val model: UIViewModel by activityViewModels()
|
||||
|
||||
private val hasCompanionDeviceApi: Boolean by lazy {
|
||||
RadioInterfaceService.hasCompanionDeviceApi(requireContext())
|
||||
}
|
||||
|
||||
private val deviceManager: CompanionDeviceManager by lazy {
|
||||
requireContext().getSystemService(CompanionDeviceManager::class.java)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
@@ -261,9 +273,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||
return inflater.inflate(R.layout.settings_fragment, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
/// Setup the ui widgets unrelated to BLE scanning
|
||||
private fun initCommonUI() {
|
||||
model.ownerName.observe(viewLifecycleOwner, Observer { name ->
|
||||
usernameEditText.setText(name)
|
||||
})
|
||||
@@ -304,6 +315,14 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Setup the GUI to do a classic (pre SDK 26 BLE scan)
|
||||
private fun initClassicScan() {
|
||||
// Turn off the widgets for the new API
|
||||
scanProgressBar.visibility = View.VISIBLE
|
||||
deviceRadioGroup.visibility = View.VISIBLE
|
||||
changeRadioButton.visibility = View.GONE
|
||||
|
||||
scanModel.errorText.observe(viewLifecycleOwner, Observer { errMsg ->
|
||||
if (errMsg != null) {
|
||||
@@ -343,6 +362,84 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||
})
|
||||
}
|
||||
|
||||
/// Start running the modern scan, once it has one result we enable the
|
||||
private fun startBackgroundScan() {
|
||||
// Disable the change button until our scan has some results
|
||||
changeRadioButton.isEnabled = false
|
||||
|
||||
// To skip filtering based on name and supported feature flags (UUIDs),
|
||||
// don't include calls to setNamePattern() and addServiceUuid(),
|
||||
// respectively. This example uses Bluetooth.
|
||||
val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
|
||||
.setNamePattern(Pattern.compile("Meshtastic_.*"))
|
||||
// .addServiceUuid(ParcelUuid(RadioInterfaceService.BTM_SERVICE_UUID), null)
|
||||
.build()
|
||||
|
||||
// The argument provided in setSingleDevice() determines whether a single
|
||||
// device name or a list of device names is presented to the user as
|
||||
// pairing options.
|
||||
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
|
||||
.addDeviceFilter(deviceFilter)
|
||||
.setSingleDevice(false)
|
||||
.build()
|
||||
|
||||
val mainActivity = requireActivity() as MainActivity
|
||||
|
||||
// When the app tries to pair with the Bluetooth device, show the
|
||||
// appropriate pairing request dialog to the user.
|
||||
deviceManager.associate(
|
||||
pairingRequest,
|
||||
object : CompanionDeviceManager.Callback() {
|
||||
|
||||
override fun onDeviceFound(chooserLauncher: IntentSender) {
|
||||
debug("Found one device - enabling button")
|
||||
changeRadioButton.isEnabled = true
|
||||
changeRadioButton.setOnClickListener {
|
||||
debug("User clicked BLE change button")
|
||||
startIntentSenderForResult(
|
||||
chooserLauncher,
|
||||
RC_SELECT_DEVICE, null, 0, 0, 0, null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(error: CharSequence?) {
|
||||
warn("BLE selection service failed $error")
|
||||
// changeDeviceSelection(mainActivity, null) // deselect any device
|
||||
}
|
||||
}, null
|
||||
)
|
||||
}
|
||||
|
||||
private fun initModernScan() {
|
||||
// Turn off the widgets for the classic API
|
||||
scanProgressBar.visibility = View.GONE
|
||||
deviceRadioGroup.visibility = View.GONE
|
||||
changeRadioButton.visibility = View.VISIBLE
|
||||
|
||||
val curRadio = RadioInterfaceService.getBondedDeviceAddress(requireContext())
|
||||
|
||||
if (curRadio != null) {
|
||||
scanStatusText.text = getString(R.string.current_pair).format(curRadio)
|
||||
changeRadioButton.text = getString(R.string.change_radio)
|
||||
} else {
|
||||
scanStatusText.text = getString(R.string.not_paired_yet)
|
||||
changeRadioButton.setText(R.string.select_radio)
|
||||
}
|
||||
|
||||
startBackgroundScan()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
initCommonUI()
|
||||
if (hasCompanionDeviceApi)
|
||||
initModernScan()
|
||||
else
|
||||
initClassicScan()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
scanModel.stopScan()
|
||||
@@ -350,7 +447,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
scanModel.startScan()
|
||||
if (!hasCompanionDeviceApi)
|
||||
scanModel.startScan()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
android:id="@+id/usernameEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:imeOptions="actionDone" />
|
||||
android:imeOptions="actionDone"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
@@ -66,7 +66,7 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/scanStatusText">
|
||||
app:layout_constraintTop_toBottomOf="@+id/changeRadioButton">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioButton2"
|
||||
@@ -105,5 +105,15 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/changeRadioButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/select_radio"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/scanStatusText" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -41,4 +41,8 @@
|
||||
<string name="report_a_bug">Report a bug</string>
|
||||
<string name="report_bug_text">Are you sure you want to report a bug? After reporting, please post in meshtastic.discourse.group so we can match up the report with what you found.</string>
|
||||
<string name="report">Report</string>
|
||||
<string name="select_radio">Select radio</string>
|
||||
<string name="current_pair">You are currently paired with radio %s</string>
|
||||
<string name="not_paired_yet">You have not paired a radio yet.</string>
|
||||
<string name="change_radio">Change radio</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user