#14: WIP we now show the new GUI properly

This commit is contained in:
geeksville
2020-04-18 16:30:30 -07:00
parent 840dbd491e
commit b4bf682df0
6 changed files with 177 additions and 19 deletions

View File

@@ -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"

View File

@@ -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")
}
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}

View File

@@ -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>

View File

@@ -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>