|
|
@@ -1,458 +0,0 @@
|
|
|
-package com.example.watch.ui.activity
|
|
|
-
|
|
|
-import android.Manifest
|
|
|
-import android.app.Dialog
|
|
|
-import android.bluetooth.BluetoothAdapter
|
|
|
-import android.bluetooth.BluetoothDevice
|
|
|
-import android.bluetooth.BluetoothManager
|
|
|
-import android.content.Context
|
|
|
-import android.content.DialogInterface
|
|
|
-import android.content.pm.PackageManager
|
|
|
-import android.os.Bundle
|
|
|
-import android.os.Handler
|
|
|
-import android.text.TextUtils
|
|
|
-import android.view.Gravity
|
|
|
-import android.view.LayoutInflater
|
|
|
-import android.view.View
|
|
|
-import android.view.ViewGroup
|
|
|
-import android.widget.*
|
|
|
-import androidx.appcompat.app.AlertDialog
|
|
|
-import androidx.core.app.ActivityCompat
|
|
|
-import androidx.core.content.ContextCompat
|
|
|
-import androidx.fragment.app.DialogFragment
|
|
|
-import com.android.chileaf.WearManager
|
|
|
-import com.android.chileaf.bluetooth.scanner.ScanCallback
|
|
|
-import com.android.chileaf.bluetooth.scanner.ScanResult
|
|
|
-import com.example.watch.R
|
|
|
-import com.example.watch.ui.activity.ScannerFragment.OnDeviceSelectedListener
|
|
|
-import timber.log.Timber
|
|
|
-import java.util.*
|
|
|
-
|
|
|
-/**
|
|
|
- * ScannerFragment class scan required BLE devices and shows them in a list. This class scans and filter
|
|
|
- * devices with standard BLE Service UUID and devices with custom BLE Service UUID. It contains a
|
|
|
- * list and a button to scan/cancel. There is a interface [OnDeviceSelectedListener] which is
|
|
|
- * implemented by activity in order to receive selected device. The scanning will continue to scan
|
|
|
- * for 5 seconds and then stop.
|
|
|
- */
|
|
|
-class ScannerFragment : DialogFragment() {
|
|
|
- private var mBluetoothAdapter: BluetoothAdapter? = null
|
|
|
- private var mListener: OnDeviceSelectedListener? = null
|
|
|
- private var mAdapter: DeviceListAdapter? = null
|
|
|
- private val mHandler = Handler()
|
|
|
- private var mScanButton: Button? = null
|
|
|
- private var mPermissionRationale: View? = null
|
|
|
- private var mIsScanning = false
|
|
|
-
|
|
|
- fun getInstance(): ScannerFragment? {
|
|
|
- val fragment = ScannerFragment()
|
|
|
- val args = Bundle()
|
|
|
- fragment.arguments = args
|
|
|
- return fragment
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Interface required to be implemented by activity.
|
|
|
- */
|
|
|
- interface OnDeviceSelectedListener {
|
|
|
- /**
|
|
|
- * Fired when user selected the device.
|
|
|
- *
|
|
|
- * @param device the device to connect to
|
|
|
- * @param name the device name. Unfortunately on some devices [BluetoothDevice.getName]
|
|
|
- * always returns `null`, i.e. Sony Xperia Z1 (C6903) with Android 4.3.
|
|
|
- * The name has to be parsed manually form the Advertisement packet.
|
|
|
- */
|
|
|
- fun onDeviceSelected(device: BluetoothDevice?, name: String?)
|
|
|
-
|
|
|
- /**
|
|
|
- * Fired when scanner dialog has been cancelled without selecting a device.
|
|
|
- */
|
|
|
- fun onDialogCanceled() {}
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * This will make sure that [OnDeviceSelectedListener] interface is implemented by activity.
|
|
|
- */
|
|
|
- override fun onAttach(context: Context) {
|
|
|
- super.onAttach(context)
|
|
|
- try {
|
|
|
- mListener = context as OnDeviceSelectedListener
|
|
|
- } catch (e: ClassCastException) {
|
|
|
- throw ClassCastException("$context must implement OnDeviceSelectedListener")
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
- super.onCreate(savedInstanceState)
|
|
|
- val manager =
|
|
|
- requireContext().getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
|
|
- if (manager != null) {
|
|
|
- mBluetoothAdapter = manager.adapter
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override fun onDestroyView() {
|
|
|
- stopScan()
|
|
|
- super.onDestroyView()
|
|
|
- }
|
|
|
-
|
|
|
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
|
- val builder = AlertDialog.Builder(requireContext())
|
|
|
- val dialogView: View =
|
|
|
- LayoutInflater.from(activity).inflate(R.layout.fragment_device_scan, null)
|
|
|
- val listview = dialogView.findViewById<ListView>(R.id.list)
|
|
|
- listview.emptyView = dialogView.findViewById(R.id.empty)
|
|
|
- listview.adapter = activity?.let {
|
|
|
- DeviceListAdapter(it)
|
|
|
- .also({ mAdapter = it })
|
|
|
- }
|
|
|
- builder.setTitle(R.string.scanner_title)
|
|
|
- val dialog = builder.setView(dialogView).create()
|
|
|
- listview.onItemClickListener =
|
|
|
- AdapterView.OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
|
|
|
- stopScan()
|
|
|
- dialog.dismiss()
|
|
|
- val d: ExtendedBluetoothDevice =
|
|
|
- mAdapter?.getItem(position) as ExtendedBluetoothDevice
|
|
|
- mListener?.onDeviceSelected(d.device, d.name)
|
|
|
- }
|
|
|
- mPermissionRationale =
|
|
|
- dialogView.findViewById(R.id.permission_rationale) // this is not null only on API23+
|
|
|
- mScanButton = dialogView.findViewById(R.id.action_cancel)
|
|
|
- mScanButton?.setOnClickListener(View.OnClickListener { v: View ->
|
|
|
- if (v.id == R.id.action_cancel) {
|
|
|
- if (mIsScanning) {
|
|
|
- dialog.cancel()
|
|
|
- } else {
|
|
|
- startScan()
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
- addBoundDevices()
|
|
|
- if (savedInstanceState == null) startScan()
|
|
|
- return dialog
|
|
|
- }
|
|
|
-
|
|
|
- override fun onCancel(dialog: DialogInterface) {
|
|
|
- super.onCancel(dialog)
|
|
|
- mListener?.onDialogCanceled()
|
|
|
- }
|
|
|
-
|
|
|
- override fun onRequestPermissionsResult(
|
|
|
- requestCode: Int,
|
|
|
- permissions: Array<String>,
|
|
|
- grantResults: IntArray
|
|
|
- ) {
|
|
|
- when (requestCode) {
|
|
|
- REQUEST_PERMISSION_REQ_CODE -> {
|
|
|
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
|
- // We have been granted the Manifest.permission.ACCESS_COARSE_LOCATION permission. Now we may proceed with scanning.
|
|
|
- startScan()
|
|
|
- } else {
|
|
|
- mPermissionRationale!!.visibility = View.VISIBLE
|
|
|
- Toast.makeText(activity, R.string.no, Toast.LENGTH_SHORT)
|
|
|
- .show()
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Scan for 5 seconds and then stop scanning when a BluetoothLE device is found then mLEScanCallback
|
|
|
- * is activated This will perform regular scan for custom BLE Service UUID and then filter out.
|
|
|
- * using class ScannerServiceParser
|
|
|
- */
|
|
|
- private fun startScan() {
|
|
|
- // Since Android 6.0 we need to obtain either Manifest.permission.ACCESS_COARSE_LOCATION or Manifest.permission.ACCESS_FINE_LOCATION to be able to scan for
|
|
|
- // Bluetooth LE devices. This is related to beacons as proximity devices.
|
|
|
- // On API older than Marshmallow the following code does nothing.
|
|
|
- if (ContextCompat.checkSelfPermission(
|
|
|
- requireContext(),
|
|
|
- Manifest.permission.ACCESS_COARSE_LOCATION
|
|
|
- ) != PackageManager.PERMISSION_GRANTED
|
|
|
- ) {
|
|
|
- // When user pressed Deny and still wants to use this functionality, show the rationale
|
|
|
- if (ActivityCompat.shouldShowRequestPermissionRationale(
|
|
|
- requireActivity(),
|
|
|
- Manifest.permission.ACCESS_COARSE_LOCATION
|
|
|
- ) && mPermissionRationale!!.visibility == View.GONE
|
|
|
- ) {
|
|
|
- mPermissionRationale!!.visibility = View.VISIBLE
|
|
|
- return
|
|
|
- }
|
|
|
- requestPermissions(
|
|
|
- arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
|
|
|
- REQUEST_PERMISSION_REQ_CODE
|
|
|
- )
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- // Hide the rationale message, we don't need it anymore.
|
|
|
- if (mPermissionRationale != null) mPermissionRationale!!.visibility = View.GONE
|
|
|
- mAdapter?.clearDevices()
|
|
|
- mScanButton?.setText(R.string.scanner_action_cancel)
|
|
|
- WearManager.getInstance(activity).startScan(scanCallback)
|
|
|
- mIsScanning = true
|
|
|
- mHandler.postDelayed({
|
|
|
- if (mIsScanning) {
|
|
|
- stopScan()
|
|
|
- }
|
|
|
- }, SCAN_DURATION)
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Stop scan if user tap Cancel button
|
|
|
- */
|
|
|
- private fun stopScan() {
|
|
|
- if (mIsScanning) {
|
|
|
- mScanButton?.setText(R.string.scanner_action_scan)
|
|
|
- WearManager.getInstance(activity).stopScan()
|
|
|
- mIsScanning = false
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private val scanCallback: ScanCallback = object : ScanCallback() {
|
|
|
- override fun onBatchScanResults(results: List<ScanResult>) {
|
|
|
- mAdapter?.update(results)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun addBoundDevices() {
|
|
|
- val devices = mBluetoothAdapter!!.bondedDevices
|
|
|
- mAdapter?.addBondedDevices(devices)
|
|
|
- }
|
|
|
-
|
|
|
- private class DeviceListAdapter(private val mContext: Context) :
|
|
|
- BaseAdapter() {
|
|
|
- private val mListBondedValues: ArrayList<com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice> =
|
|
|
- ArrayList<com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice>()
|
|
|
- private val mListValues: ArrayList<com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice> =
|
|
|
- ArrayList<com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice>()
|
|
|
-
|
|
|
- /**
|
|
|
- * Sets a list of bonded devices.
|
|
|
- *
|
|
|
- * @param devices list of bonded devices.
|
|
|
- */
|
|
|
- fun addBondedDevices(devices: Set<BluetoothDevice>) {
|
|
|
- val bondedDevices: MutableList<com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice> =
|
|
|
- mListBondedValues
|
|
|
- for (device in devices) {
|
|
|
- if (matchDeviceName(device.name)) {
|
|
|
- bondedDevices.add(
|
|
|
- com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice(
|
|
|
- device
|
|
|
- )
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- notifyDataSetChanged()
|
|
|
- }
|
|
|
-
|
|
|
- private fun matchDeviceName(name: String?): Boolean {
|
|
|
- if (name != null && !TextUtils.isEmpty(name)) {
|
|
|
- for (filterName in FILTER_NAMES) {
|
|
|
- if (name.toUpperCase().startsWith(filterName)) {
|
|
|
- return true
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Updates the list of not bonded devices.
|
|
|
- *
|
|
|
- * @param results list of results from the scanner
|
|
|
- */
|
|
|
- fun update(results: List<ScanResult>) {
|
|
|
- for (result in results) {
|
|
|
- Timber.e(result.toString())
|
|
|
- val device: com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice? =
|
|
|
- findDevice(result)
|
|
|
- if (device == null) {
|
|
|
- if (hasPairAddress(result)) {
|
|
|
- mListValues.add(
|
|
|
- com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice(
|
|
|
- result
|
|
|
- )
|
|
|
- )
|
|
|
- }
|
|
|
- } else if (result.scanRecord != null) {
|
|
|
- device.name = result.scanRecord!!.deviceName
|
|
|
- device.rssi = result.rssi
|
|
|
- }
|
|
|
- }
|
|
|
- notifyDataSetChanged()
|
|
|
- }
|
|
|
-
|
|
|
- private fun hasPairAddress(result: ScanResult): Boolean {
|
|
|
- val record = result.scanRecord
|
|
|
- val name = if (record != null) record.deviceName else ""
|
|
|
- return matchDeviceName(name)
|
|
|
- }
|
|
|
-
|
|
|
- private fun findDevice(result: ScanResult): com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice? {
|
|
|
- for (device in mListBondedValues) if (device.matches(result) && hasPairAddress(result)) return device
|
|
|
- for (device in mListValues) if (device.matches(result)) return device
|
|
|
- return null
|
|
|
- }
|
|
|
-
|
|
|
- fun clearDevices() {
|
|
|
- mListValues.clear()
|
|
|
- notifyDataSetChanged()
|
|
|
- }
|
|
|
-
|
|
|
- override fun getCount(): Int {
|
|
|
- val bondedCount = mListBondedValues.size + 1 // 1 for the title
|
|
|
- val availableCount =
|
|
|
- if (mListValues.isEmpty()) 2 else mListValues.size + 1 // 1 for title, 1 for empty text
|
|
|
- return if (bondedCount == 1) availableCount else bondedCount + availableCount
|
|
|
- }
|
|
|
-
|
|
|
- override fun getItem(position: Int): Any {
|
|
|
- val bondedCount = mListBondedValues.size + 1 // 1 for the title
|
|
|
- return if (mListBondedValues.isEmpty()) {
|
|
|
- if (position == 0) R.string.scanner_subtitle_not_bonded else mListValues[position - 1]
|
|
|
- } else {
|
|
|
- if (position == 0) return R.string.scanner_subtitle_bonded
|
|
|
- if (position < bondedCount) return mListBondedValues[position - 1]
|
|
|
- if (position == bondedCount) R.string.scanner_subtitle_not_bonded else mListValues[position - bondedCount - 1]
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override fun getViewTypeCount(): Int {
|
|
|
- return 3
|
|
|
- }
|
|
|
-
|
|
|
- override fun areAllItemsEnabled(): Boolean {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- override fun isEnabled(position: Int): Boolean {
|
|
|
- return getItemViewType(position) == com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.Companion.TYPE_ITEM
|
|
|
- }
|
|
|
-
|
|
|
- override fun getItemViewType(position: Int): Int {
|
|
|
- if (position == 0) return com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.Companion.TYPE_TITLE
|
|
|
- if (!mListBondedValues.isEmpty() && position == mListBondedValues.size + 1) return com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.Companion.TYPE_TITLE
|
|
|
- return if (position == count - 1 && mListValues.isEmpty()) com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.Companion.TYPE_EMPTY else com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.Companion.TYPE_ITEM
|
|
|
- }
|
|
|
-
|
|
|
- override fun getItemId(position: Int): Long {
|
|
|
- return position.toLong()
|
|
|
- }
|
|
|
-
|
|
|
- override fun getView(position: Int, oldView: View, parent: ViewGroup): View {
|
|
|
- val inflater = LayoutInflater.from(mContext)
|
|
|
- val type = getItemViewType(position)
|
|
|
- var view = oldView
|
|
|
- when (type) {
|
|
|
- com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.Companion.TYPE_EMPTY -> if (view == null) {
|
|
|
- view = TextView(mContext)
|
|
|
- val empty = view
|
|
|
- empty.gravity = Gravity.CENTER_HORIZONTAL
|
|
|
- empty.text = mContext.getString(R.string.scanner_empty)
|
|
|
- }
|
|
|
- com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.Companion.TYPE_TITLE -> {
|
|
|
- if (view == null) {
|
|
|
- view = TextView(mContext)
|
|
|
- }
|
|
|
- val title = view as TextView
|
|
|
- title.gravity = Gravity.CENTER_HORIZONTAL
|
|
|
- title.setText((getItem(position) as Int))
|
|
|
- }
|
|
|
- else -> {
|
|
|
-// if (view == null) {
|
|
|
-// view = inflater.inflate(R.layout.item_device_list, parent, false)
|
|
|
-// val holder: com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.ViewHolder =
|
|
|
-// com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.ViewHolder()
|
|
|
-//
|
|
|
-// holder.name = view.findViewById<TextView>(R.id.name)
|
|
|
-// holder.address = view.findViewById<TextView>(R.id.address)
|
|
|
-// holder.signal = view.findViewById<TextView>(R.id.rssi)
|
|
|
-// view.tag = holder
|
|
|
-// }
|
|
|
- val device: com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice =
|
|
|
- getItem(position) as com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice
|
|
|
- val holder: com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.ViewHolder =
|
|
|
- view.tag as com.example.watch.ui.activity.ScannerFragment.DeviceListAdapter.ViewHolder
|
|
|
- val name: String? = device.name
|
|
|
- holder.name?.setText(name ?: mContext.getString(R.string.not_available))
|
|
|
- holder.address?.setText(device.device.getAddress())
|
|
|
- if (!device.isBonded || device.rssi != com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice.Companion.NO_RSSI) {
|
|
|
- holder.signal?.setText(device.rssi.toString() + "dBm")
|
|
|
- holder.signal?.setVisibility(View.VISIBLE)
|
|
|
- } else {
|
|
|
- holder.signal?.setVisibility(View.GONE)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return view
|
|
|
- }
|
|
|
-
|
|
|
- private inner class ViewHolder {
|
|
|
- var name: TextView? = null
|
|
|
- var address: TextView? = null
|
|
|
- var signal: TextView? = null
|
|
|
- }
|
|
|
-
|
|
|
- companion object {
|
|
|
- private const val TYPE_TITLE = 0
|
|
|
- private const val TYPE_ITEM = 1
|
|
|
- private const val TYPE_EMPTY = 2
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private class ExtendedBluetoothDevice {
|
|
|
- var name: String?
|
|
|
- var rssi: Int
|
|
|
- var isBonded: Boolean
|
|
|
- val device: BluetoothDevice
|
|
|
-
|
|
|
- constructor(scanResult: ScanResult) {
|
|
|
- device = scanResult.device
|
|
|
- name = if (scanResult.scanRecord != null) scanResult.scanRecord!!.deviceName else null
|
|
|
- rssi = scanResult.rssi
|
|
|
- isBonded = false
|
|
|
- }
|
|
|
-
|
|
|
- constructor(device: BluetoothDevice) {
|
|
|
- this.device = device
|
|
|
- name = device.name
|
|
|
- rssi =
|
|
|
- com.example.watch.ui.activity.ScannerFragment.ExtendedBluetoothDevice.Companion.NO_RSSI
|
|
|
- isBonded = true
|
|
|
- }
|
|
|
-
|
|
|
- fun matches(scanResult: ScanResult): Boolean {
|
|
|
- return device.address == scanResult.device.address
|
|
|
- }
|
|
|
-
|
|
|
- companion object {
|
|
|
- const val NO_RSSI = -1000
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- companion object {
|
|
|
- @JvmName("getInstance1")
|
|
|
- fun getInstance(): ScannerFragment {
|
|
|
- val fragment = ScannerFragment()
|
|
|
- val args = Bundle()
|
|
|
- fragment.arguments = args
|
|
|
- return fragment
|
|
|
- }
|
|
|
-
|
|
|
- private const val SCAN_DURATION: Long = 5000
|
|
|
- private const val REQUEST_PERMISSION_REQ_CODE = 34
|
|
|
- private val FILTER_NAMES = arrayOf("CL831", "SE2")
|
|
|
- val instance: ScannerFragment
|
|
|
- get() {
|
|
|
- val fragment = ScannerFragment()
|
|
|
- val args = Bundle()
|
|
|
- fragment.arguments = args
|
|
|
- return fragment
|
|
|
- }
|
|
|
- }
|
|
|
-}
|