From b4bf682df019043e7742283729c17ae16feebe7c Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 18 Apr 2020 16:30:30 -0700 Subject: [PATCH] #14: WIP we now show the new GUI properly --- app/src/main/AndroidManifest.xml | 9 ++ .../java/com/geeksville/mesh/MainActivity.kt | 37 ++++-- .../mesh/service/RadioInterfaceService.kt | 14 +++ .../geeksville/mesh/ui/SettingsFragment.kt | 116 ++++++++++++++++-- app/src/main/res/layout/settings_fragment.xml | 16 ++- app/src/main/res/values/strings.xml | 4 + 6 files changed, 177 insertions(+), 19 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0d635790c..fa10121e0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,6 +36,10 @@ + + + + + + + = - 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 = + 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") + } } } diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index 00b3709e5..c9314a633 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -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 + } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index df8555913..cb0536750 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -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() } } diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index 8ab7b2831..2ad55e6ed 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -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" /> + app:layout_constraintTop_toBottomOf="@+id/changeRadioButton"> +