新增:自动任务·快捷指令 —— 触发条件:蓝牙设备(状态变化、设备发现、连接断开) #388
parent
e4432cb037
commit
ad79f5e445
@ -0,0 +1,122 @@
|
||||
package com.idormy.sms.forwarder.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothClass
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.idormy.sms.forwarder.R
|
||||
|
||||
class BluetoothRecyclerAdapter(
|
||||
private val itemList: List<BluetoothDevice>,
|
||||
private var itemClickListener: ((Int) -> Unit)? = null,
|
||||
private var removeClickListener: ((Int) -> Unit)? = null,
|
||||
private var editClickListener: ((Int) -> Unit)? = null,
|
||||
) : RecyclerView.Adapter<BluetoothRecyclerAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.adapter_bluetooth_list_item, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = itemList[position]
|
||||
holder.bind(item)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = itemList.size
|
||||
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||
private val textDeviceName: TextView = itemView.findViewById(R.id.text_device_name)
|
||||
private val textDeviceAddress: TextView = itemView.findViewById(R.id.text_device_address)
|
||||
private val imageDeviceIcon: ImageView = itemView.findViewById(R.id.image_device_icon)
|
||||
private val editIcon: ImageView = itemView.findViewById(R.id.iv_edit)
|
||||
private val removeIcon: ImageView = itemView.findViewById(R.id.iv_remove)
|
||||
|
||||
init {
|
||||
if (removeClickListener == null) {
|
||||
removeIcon.visibility = View.GONE
|
||||
} else {
|
||||
removeIcon.setOnClickListener(this)
|
||||
}
|
||||
|
||||
if (editClickListener == null) {
|
||||
editIcon.visibility = View.GONE
|
||||
} else {
|
||||
editIcon.setOnClickListener(this)
|
||||
}
|
||||
|
||||
if (itemClickListener != null) {
|
||||
itemView.setOnClickListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun bind(device: BluetoothDevice) {
|
||||
// 设置设备名称和地址
|
||||
textDeviceName.text = device.name ?: "Unknown Device"
|
||||
textDeviceAddress.text = device.address
|
||||
|
||||
// 根据设备类型设置图标
|
||||
val deviceType = getDeviceType(device)
|
||||
val iconResId = when (deviceType) {
|
||||
DeviceType.CELLPHONE -> R.drawable.ic_bt_cellphone
|
||||
DeviceType.HEADPHONES -> R.drawable.ic_bt_headphones
|
||||
DeviceType.HEADSET_HFP -> R.drawable.ic_bt_headset_hfp
|
||||
DeviceType.IMAGING -> R.drawable.ic_bt_imaging
|
||||
DeviceType.LAPTOP -> R.drawable.ic_bt_laptop
|
||||
DeviceType.MISC_HID -> R.drawable.ic_bt_misc_hid
|
||||
DeviceType.NETWORK_PAN -> R.drawable.ic_bt_network_pan
|
||||
DeviceType.WRISTBAND -> R.drawable.ic_bt_wristband
|
||||
else -> R.drawable.ic_bt_bluetooth
|
||||
}
|
||||
imageDeviceIcon.setImageResource(iconResId)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
val position = adapterPosition
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
when (v?.id) {
|
||||
R.id.iv_edit -> editClickListener?.let { it(position) }
|
||||
R.id.iv_remove -> removeClickListener?.let { it(position) }
|
||||
else -> itemClickListener?.let { it(position) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun getDeviceType(device: BluetoothDevice): DeviceType {
|
||||
val deviceClass = device.bluetoothClass?.majorDeviceClass ?: BluetoothClass.Device.Major.MISC
|
||||
@Suppress("DUPLICATE_LABEL_IN_WHEN")
|
||||
return when (deviceClass) {
|
||||
BluetoothClass.Device.Major.PHONE -> DeviceType.CELLPHONE
|
||||
BluetoothClass.Device.Major.AUDIO_VIDEO -> DeviceType.HEADPHONES
|
||||
BluetoothClass.Device.Major.PERIPHERAL -> DeviceType.HEADSET_HFP
|
||||
BluetoothClass.Device.Major.IMAGING -> DeviceType.IMAGING
|
||||
BluetoothClass.Device.Major.COMPUTER -> DeviceType.LAPTOP
|
||||
BluetoothClass.Device.Major.PERIPHERAL -> DeviceType.MISC_HID
|
||||
BluetoothClass.Device.Major.NETWORKING -> DeviceType.NETWORK_PAN
|
||||
BluetoothClass.Device.Major.WEARABLE -> DeviceType.WRISTBAND
|
||||
else -> DeviceType.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class DeviceType {
|
||||
CELLPHONE,
|
||||
HEADPHONES,
|
||||
HEADSET_HFP,
|
||||
IMAGING,
|
||||
LAPTOP,
|
||||
MISC_HID,
|
||||
NETWORK_PAN,
|
||||
WRISTBAND,
|
||||
UNKNOWN
|
||||
}
|
||||
}
|
@ -0,0 +1,325 @@
|
||||
package com.idormy.sms.forwarder.fragment.condition
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.gson.Gson
|
||||
import com.hjq.permissions.OnPermissionCallback
|
||||
import com.hjq.permissions.Permission
|
||||
import com.hjq.permissions.XXPermissions
|
||||
import com.idormy.sms.forwarder.R
|
||||
import com.idormy.sms.forwarder.adapter.BluetoothRecyclerAdapter
|
||||
import com.idormy.sms.forwarder.core.BaseFragment
|
||||
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionBluetoothBinding
|
||||
import com.idormy.sms.forwarder.entity.condition.BluetoothSetting
|
||||
import com.idormy.sms.forwarder.service.BluetoothScanService
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.TASK_CONDITION_BLUETOOTH
|
||||
import com.idormy.sms.forwarder.utils.XToastUtils
|
||||
import com.xuexiang.xaop.annotation.SingleClick
|
||||
import com.xuexiang.xpage.annotation.Page
|
||||
import com.xuexiang.xrouter.annotation.AutoWired
|
||||
import com.xuexiang.xrouter.launcher.XRouter
|
||||
import com.xuexiang.xui.utils.CountDownButtonHelper
|
||||
import com.xuexiang.xui.widget.actionbar.TitleBar
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
|
||||
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
|
||||
|
||||
|
||||
@Page(name = "Bluetooth")
|
||||
@Suppress("PrivatePropertyName", "SameParameterValue", "DEPRECATION")
|
||||
class BluetoothFragment : BaseFragment<FragmentTasksConditionBluetoothBinding?>(), View.OnClickListener {
|
||||
|
||||
private val TAG: String = BluetoothFragment::class.java.simpleName
|
||||
private var titleBar: TitleBar? = null
|
||||
private var mCountDownHelper: CountDownButtonHelper? = null
|
||||
|
||||
private lateinit var bluetoothAdapter: BluetoothAdapter
|
||||
private lateinit var bluetoothRecyclerAdapter: BluetoothRecyclerAdapter
|
||||
private var discoveredDevices: MutableList<BluetoothDevice> = mutableListOf()
|
||||
private val bluetoothReceiver = object : BroadcastReceiver() {
|
||||
@SuppressLint("MissingPermission", "NotifyDataSetChanged")
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val action: String? = intent?.action
|
||||
|
||||
when (action) {
|
||||
BluetoothDevice.ACTION_FOUND -> {
|
||||
val device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
|
||||
device?.let {
|
||||
Log.d(TAG, "Discovered device: ${it.name} - ${it.address}")
|
||||
if (!discoveredDevices.contains(it)) {
|
||||
discoveredDevices.add(it)
|
||||
bluetoothRecyclerAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
|
||||
Log.d(TAG, "Bluetooth scan finished, discoveredDevices: $discoveredDevices")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
@AutoWired(name = KEY_EVENT_DATA_CONDITION)
|
||||
var eventData: String? = null
|
||||
|
||||
override fun initArgs() {
|
||||
XRouter.getInstance().inject(this)
|
||||
}
|
||||
|
||||
override fun viewBindingInflate(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
): FragmentTasksConditionBluetoothBinding {
|
||||
return FragmentTasksConditionBluetoothBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun initTitle(): TitleBar? {
|
||||
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_bluetooth)
|
||||
return titleBar
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控件
|
||||
*/
|
||||
override fun initViews() {
|
||||
//测试按钮增加倒计时,避免重复点击
|
||||
mCountDownHelper = CountDownButtonHelper(binding!!.btnStartDiscovery, 12)
|
||||
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
|
||||
override fun onCountDown(time: Int) {
|
||||
binding!!.btnStartDiscovery.text = String.format(getString(R.string.seconds_n), time)
|
||||
}
|
||||
|
||||
override fun onFinished() {
|
||||
requireActivity().unregisterReceiver(bluetoothReceiver)
|
||||
binding!!.btnStartDiscovery.text = getString(R.string.start_discovery)
|
||||
}
|
||||
})
|
||||
|
||||
binding!!.rgBluetoothAction.setOnCheckedChangeListener { _, checkedId ->
|
||||
Log.d(TAG, "rgBluetoothState checkedId:$checkedId")
|
||||
when (checkedId) {
|
||||
R.id.rb_action_state_changed -> {
|
||||
binding!!.layoutBluetoothState.visibility = View.VISIBLE
|
||||
binding!!.layoutDiscoveryFinished.visibility = View.GONE
|
||||
binding!!.layoutDeviceAddress.visibility = View.GONE
|
||||
}
|
||||
|
||||
R.id.rb_action_discovery_finished -> {
|
||||
binding!!.layoutBluetoothState.visibility = View.GONE
|
||||
binding!!.layoutDiscoveryFinished.visibility = View.VISIBLE
|
||||
binding!!.layoutDeviceAddress.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding!!.layoutBluetoothState.visibility = View.GONE
|
||||
binding!!.layoutDiscoveryFinished.visibility = View.GONE
|
||||
binding!!.layoutDeviceAddress.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
checkSetting(true)
|
||||
}
|
||||
|
||||
Log.d(TAG, "initViews eventData:$eventData")
|
||||
if (eventData != null) {
|
||||
val settingVo = Gson().fromJson(eventData, BluetoothSetting::class.java)
|
||||
Log.d(TAG, "initViews settingVo:$settingVo")
|
||||
binding!!.tvDescription.text = settingVo.description
|
||||
binding!!.rgBluetoothAction.check(settingVo.getActionCheckId())
|
||||
binding!!.rgBluetoothState.check(settingVo.getStateCheckId())
|
||||
binding!!.rgDiscoveryResult.check(settingVo.getResultCheckId())
|
||||
binding!!.etDeviceAddress.setText(settingVo.device)
|
||||
} else {
|
||||
binding!!.rgBluetoothAction.check(R.id.rb_action_state_changed)
|
||||
binding!!.rgBluetoothState.check(R.id.rb_state_on)
|
||||
binding!!.rgDiscoveryResult.check(R.id.rb_discovered)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initListeners() {
|
||||
binding!!.btnStartDiscovery.setOnClickListener(this)
|
||||
binding!!.btnDel.setOnClickListener(this)
|
||||
binding!!.btnSave.setOnClickListener(this)
|
||||
binding!!.rgBluetoothState.setOnCheckedChangeListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.rgDiscoveryResult.setOnCheckedChangeListener { _, _ ->
|
||||
checkSetting(true)
|
||||
}
|
||||
binding!!.etDeviceAddress.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
checkSetting(true)
|
||||
}
|
||||
})
|
||||
|
||||
binding!!.recyclerDevices.layoutManager = LinearLayoutManager(requireContext())
|
||||
bluetoothRecyclerAdapter = BluetoothRecyclerAdapter(discoveredDevices, { position ->
|
||||
val device = discoveredDevices[position]
|
||||
binding!!.etDeviceAddress.setText(device.address)
|
||||
})
|
||||
binding!!.recyclerDevices.adapter = bluetoothRecyclerAdapter
|
||||
|
||||
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (bluetoothAdapter == null) {
|
||||
XToastUtils.error(getString(R.string.bluetooth_not_supported))
|
||||
return
|
||||
}
|
||||
|
||||
// 启动蓝牙搜索
|
||||
// startBluetoothDiscovery()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
|
||||
if (bluetoothReceiver.isOrderedBroadcast) {
|
||||
requireActivity().unregisterReceiver(bluetoothReceiver)
|
||||
}
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@SingleClick
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
when (v.id) {
|
||||
|
||||
R.id.btn_start_discovery -> {
|
||||
if (!SettingUtils.enableBluetooth) {
|
||||
MaterialDialog.Builder(requireContext())
|
||||
.iconRes(R.drawable.auto_task_icon_location)
|
||||
.title(R.string.enable_bluetooth)
|
||||
.content(R.string.enable_bluetooth_dialog)
|
||||
.cancelable(false)
|
||||
.positiveText(R.string.lab_yes)
|
||||
.negativeText(R.string.lab_no)
|
||||
.onPositive { _: MaterialDialog?, _: DialogAction? ->
|
||||
XXPermissions.with(this)
|
||||
.permission(Permission.BLUETOOTH_SCAN)
|
||||
.permission(Permission.BLUETOOTH_CONNECT)
|
||||
.permission(Permission.BLUETOOTH_ADVERTISE)
|
||||
.permission(Permission.ACCESS_FINE_LOCATION)
|
||||
.request(object : OnPermissionCallback {
|
||||
override fun onGranted(permissions: List<String>, all: Boolean) {
|
||||
startBluetoothDiscovery()
|
||||
Log.d(TAG, "onGranted: permissions=$permissions, all=$all")
|
||||
if (!all) {
|
||||
XToastUtils.warning(getString(R.string.toast_granted_part))
|
||||
}
|
||||
SettingUtils.enableBluetooth = true
|
||||
val serviceIntent = Intent(requireContext(), BluetoothScanService::class.java)
|
||||
serviceIntent.action = ACTION_START
|
||||
requireContext().startService(serviceIntent)
|
||||
}
|
||||
|
||||
override fun onDenied(permissions: List<String>, never: Boolean) {
|
||||
Log.e(TAG, "onDenied: permissions=$permissions, never=$never")
|
||||
if (never) {
|
||||
XToastUtils.error(getString(R.string.toast_denied_never))
|
||||
XXPermissions.startPermissionActivity(requireContext(), permissions)
|
||||
} else {
|
||||
XToastUtils.error(getString(R.string.toast_denied))
|
||||
}
|
||||
}
|
||||
})
|
||||
}.show()
|
||||
return
|
||||
}
|
||||
|
||||
startBluetoothDiscovery()
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_del -> {
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
|
||||
R.id.btn_save -> {
|
||||
val settingVo = checkSetting()
|
||||
val intent = Intent()
|
||||
intent.putExtra(KEY_BACK_DESCRIPTION_CONDITION, settingVo.description)
|
||||
intent.putExtra(KEY_BACK_DATA_CONDITION, Gson().toJson(settingVo))
|
||||
setFragmentResult(TASK_CONDITION_BLUETOOTH, intent)
|
||||
popToBack()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onClick error:$e")
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission", "NotifyDataSetChanged")
|
||||
private fun startBluetoothDiscovery() {
|
||||
try {
|
||||
mCountDownHelper?.start()
|
||||
|
||||
if (bluetoothAdapter.isDiscovering) {
|
||||
bluetoothAdapter.cancelDiscovery()
|
||||
}
|
||||
|
||||
// 注册广播接收器
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(BluetoothDevice.ACTION_FOUND)
|
||||
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
|
||||
}
|
||||
requireActivity().registerReceiver(bluetoothReceiver, filter)
|
||||
|
||||
discoveredDevices.clear()
|
||||
bluetoothRecyclerAdapter.notifyDataSetChanged()
|
||||
bluetoothAdapter.startDiscovery()
|
||||
} catch (e: Exception) {
|
||||
mCountDownHelper?.finish()
|
||||
XToastUtils.error(e.message.toString(), 30000)
|
||||
Log.e(TAG, "startBluetoothDiscovery error:$e")
|
||||
}
|
||||
}
|
||||
|
||||
//检查设置
|
||||
private fun checkSetting(updateView: Boolean = false): BluetoothSetting {
|
||||
val actionCheckId = binding!!.rgBluetoothAction.checkedRadioButtonId
|
||||
val deviceAddress = binding!!.etDeviceAddress.text.toString().trim()
|
||||
if (actionCheckId != R.id.rb_action_state_changed &&
|
||||
(deviceAddress.isEmpty() || !BluetoothAdapter.checkBluetoothAddress(deviceAddress))
|
||||
) {
|
||||
if (updateView) {
|
||||
binding!!.etDeviceAddress.error = getString(R.string.mac_error)
|
||||
} else {
|
||||
throw Exception(getString(R.string.invalid_bluetooth_mac_address))
|
||||
}
|
||||
} else {
|
||||
binding!!.etDeviceAddress.error = null
|
||||
}
|
||||
|
||||
val stateCheckId = binding!!.rgBluetoothState.checkedRadioButtonId
|
||||
val resultCheckId = binding!!.rgDiscoveryResult.checkedRadioButtonId
|
||||
val settingVo = BluetoothSetting(actionCheckId, stateCheckId, resultCheckId, deviceAddress)
|
||||
if (updateView) {
|
||||
binding!!.tvDescription.text = settingVo.description
|
||||
}
|
||||
|
||||
return settingVo
|
||||
}
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
package com.idormy.sms.forwarder.receiver
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Handler
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.workDataOf
|
||||
import com.google.gson.Gson
|
||||
import com.idormy.sms.forwarder.App
|
||||
import com.idormy.sms.forwarder.service.BluetoothScanService
|
||||
import com.idormy.sms.forwarder.utils.ACTION_RESTART
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.ACTION_STOP
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
import com.idormy.sms.forwarder.utils.SettingUtils
|
||||
import com.idormy.sms.forwarder.utils.TASK_CONDITION_BLUETOOTH
|
||||
import com.idormy.sms.forwarder.utils.TaskWorker
|
||||
import com.idormy.sms.forwarder.utils.task.TaskUtils
|
||||
import com.idormy.sms.forwarder.workers.BluetoothWorker
|
||||
|
||||
@Suppress("PrivatePropertyName", "DEPRECATION")
|
||||
@SuppressLint("MissingPermission")
|
||||
class BluetoothReceiver : BroadcastReceiver() {
|
||||
|
||||
private val TAG: String = BluetoothReceiver::class.java.simpleName
|
||||
private val handler = Handler()
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (context == null || intent == null) return
|
||||
|
||||
when (val action = intent.action) {
|
||||
// 发现设备
|
||||
BluetoothDevice.ACTION_FOUND -> {
|
||||
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
||||
device?.let {
|
||||
if (ActivityCompat.checkSelfPermission(App.context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) return
|
||||
if (SettingUtils.bluetoothIgnoreAnonymous && it.name.isNullOrEmpty()) return
|
||||
|
||||
//TODO: 实测这里一台设备会收到两次广播
|
||||
Log.d(TAG, "Discovered device: ${it.name} - ${it.address}")
|
||||
val discoveredDevices = TaskUtils.discoveredDevices
|
||||
discoveredDevices[it.address] = it.name ?: ""
|
||||
TaskUtils.discoveredDevices = discoveredDevices
|
||||
}
|
||||
}
|
||||
|
||||
// 扫描完成
|
||||
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
|
||||
//TODO: 放在这里去判断是否已经发现某个设备(避免 ACTION_FOUND 重复广播)
|
||||
Log.d(TAG, "Bluetooth scan finished, discoveredDevices: ${TaskUtils.discoveredDevices}")
|
||||
if (TaskUtils.discoveredDevices.isNotEmpty()) {
|
||||
handleWorkRequest(context, action, Gson().toJson(TaskUtils.discoveredDevices))
|
||||
}
|
||||
|
||||
restartBluetoothService(ACTION_STOP)
|
||||
if (SettingUtils.enableBluetooth) {
|
||||
Log.d(TAG, "Bluetooth scan finished, restart in ${SettingUtils.bluetoothScanInterval}ms")
|
||||
handler.postDelayed({
|
||||
restartBluetoothService(ACTION_START)
|
||||
}, SettingUtils.bluetoothScanInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// 蓝牙状态变化
|
||||
BluetoothAdapter.ACTION_STATE_CHANGED -> {
|
||||
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
|
||||
handleBluetoothStateChanged(state)
|
||||
handleWorkRequest(context, action, state.toString())
|
||||
}
|
||||
|
||||
// 蓝牙扫描模式变化
|
||||
BluetoothAdapter.ACTION_SCAN_MODE_CHANGED -> {
|
||||
if (SettingUtils.enableBluetooth) {
|
||||
restartBluetoothService()
|
||||
}
|
||||
}
|
||||
|
||||
// 本地蓝牙名称变化
|
||||
BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED -> {
|
||||
}
|
||||
|
||||
// 蓝牙连接状态变化
|
||||
BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED -> {
|
||||
}
|
||||
|
||||
// 蓝牙设备的配对状态变化
|
||||
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
|
||||
}
|
||||
|
||||
// 蓝牙设备连接
|
||||
BluetoothDevice.ACTION_ACL_CONNECTED -> {
|
||||
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
||||
if (device != null) {
|
||||
Log.d(TAG, "Connected device: ${device.name} - ${device.address}")
|
||||
TaskUtils.connectedDevices[device.address] = device.name
|
||||
handleWorkRequest(context, action, Gson().toJson(mutableMapOf(device.address to device.name)))
|
||||
}
|
||||
}
|
||||
|
||||
// 蓝牙设备断开连接
|
||||
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
|
||||
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
||||
if (device != null) {
|
||||
Log.d(TAG, "Disconnected device: ${device.name} - ${device.address}")
|
||||
TaskUtils.connectedDevices.remove(device.address)
|
||||
handleWorkRequest(context, action, Gson().toJson(mutableMapOf(device.address to device.name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理蓝牙状态变化
|
||||
private fun handleBluetoothStateChanged(state: Int) {
|
||||
when (state) {
|
||||
// 蓝牙已关闭
|
||||
BluetoothAdapter.STATE_OFF -> {
|
||||
Log.d(TAG, "BluetoothAdapter.STATE_OFF")
|
||||
TaskUtils.bluetoothState = state
|
||||
// 停止扫描 & 删除任何挂起的延迟扫描任务
|
||||
restartBluetoothService(ACTION_STOP)
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
// 蓝牙已打开
|
||||
BluetoothAdapter.STATE_ON -> {
|
||||
Log.d(TAG, "BluetoothAdapter.STATE_ON")
|
||||
TaskUtils.bluetoothState = state
|
||||
if (SettingUtils.enableBluetooth) {
|
||||
restartBluetoothService(ACTION_START)
|
||||
}
|
||||
}
|
||||
|
||||
// 蓝牙正在打开
|
||||
BluetoothAdapter.STATE_TURNING_ON -> {
|
||||
Log.d(TAG, "BluetoothAdapter.STATE_TURNING_ON")
|
||||
}
|
||||
|
||||
// 蓝牙正在关闭
|
||||
BluetoothAdapter.STATE_TURNING_OFF -> {
|
||||
Log.d(TAG, "BluetoothAdapter.STATE_TURNING_OFF")
|
||||
}
|
||||
|
||||
// 蓝牙正在连接
|
||||
BluetoothAdapter.STATE_CONNECTING -> {
|
||||
Log.d(TAG, "BluetoothAdapter.STATE_CONNECTING")
|
||||
}
|
||||
|
||||
// 蓝牙已连接
|
||||
BluetoothAdapter.STATE_CONNECTED -> {
|
||||
Log.d(TAG, "BluetoothAdapter.STATE_CONNECTED")
|
||||
}
|
||||
|
||||
// 蓝牙正在断开连接
|
||||
BluetoothAdapter.STATE_DISCONNECTING -> {
|
||||
Log.d(TAG, "BluetoothAdapter.STATE_DISCONNECTING")
|
||||
}
|
||||
|
||||
// 蓝牙已断开连接
|
||||
BluetoothAdapter.STATE_DISCONNECTED -> {
|
||||
Log.d(TAG, "BluetoothAdapter.STATE_DISCONNECTED")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//重启蓝牙扫描服务
|
||||
private fun restartBluetoothService(action: String = ACTION_RESTART) {
|
||||
Log.d(TAG, "restartBluetoothService, action: $action")
|
||||
val serviceIntent = Intent(App.context, BluetoothScanService::class.java)
|
||||
serviceIntent.action = action
|
||||
App.context.startService(serviceIntent)
|
||||
}
|
||||
|
||||
private fun handleWorkRequest(context: Context, action: String, msg: String) {
|
||||
val request = OneTimeWorkRequestBuilder<BluetoothWorker>()
|
||||
.setInputData(
|
||||
workDataOf(
|
||||
TaskWorker.CONDITION_TYPE to TASK_CONDITION_BLUETOOTH,
|
||||
TaskWorker.ACTION to action,
|
||||
TaskWorker.MSG to msg,
|
||||
)
|
||||
).build()
|
||||
WorkManager.getInstance(context).enqueue(request)
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package com.idormy.sms.forwarder.service
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Service
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.ActivityCompat
|
||||
import com.idormy.sms.forwarder.utils.ACTION_RESTART
|
||||
import com.idormy.sms.forwarder.utils.ACTION_START
|
||||
import com.idormy.sms.forwarder.utils.ACTION_STOP
|
||||
import com.idormy.sms.forwarder.utils.Log
|
||||
|
||||
@Suppress("PrivatePropertyName", "DEPRECATION")
|
||||
class BluetoothScanService : Service() {
|
||||
|
||||
private val TAG: String = BluetoothScanService::class.java.simpleName
|
||||
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
|
||||
|
||||
companion object {
|
||||
var isRunning = false
|
||||
}
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent == null) return START_NOT_STICKY
|
||||
Log.i(TAG, "onStartCommand: ${intent.action}")
|
||||
|
||||
when (intent.action) {
|
||||
ACTION_START -> startDiscovery()
|
||||
ACTION_STOP -> stopDiscovery()
|
||||
ACTION_RESTART -> {
|
||||
stopDiscovery()
|
||||
startDiscovery()
|
||||
}
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
// 开始扫描蓝牙设备
|
||||
private fun startDiscovery() {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
|
||||
return
|
||||
}
|
||||
if (isRunning) return
|
||||
bluetoothAdapter?.startDiscovery()
|
||||
isRunning = true
|
||||
}
|
||||
|
||||
// 停止蓝牙扫描
|
||||
private fun stopDiscovery() {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
|
||||
return
|
||||
}
|
||||
if (!isRunning) return
|
||||
bluetoothAdapter?.cancelDiscovery()
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
isRunning = false
|
||||
stopDiscovery()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.idormy.sms.forwarder.utils
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
@Suppress("DEPRECATION", "MemberVisibilityCanBePrivate")
|
||||
object BluetoothUtils {
|
||||
|
||||
/**
|
||||
* 检查应用是否具有蓝牙权限
|
||||
*/
|
||||
fun hasBluetoothPermission(context: Context): Boolean {
|
||||
return ContextCompat.checkSelfPermission(context, android.Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_GRANTED
|
||||
&& ContextCompat.checkSelfPermission(context, android.Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查蓝牙是否已启用
|
||||
*/
|
||||
fun isBluetoothEnabled(): Boolean {
|
||||
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
|
||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查设备是否支持蓝牙功能
|
||||
*/
|
||||
fun hasBluetoothCapability(context: Context): Boolean {
|
||||
if (!hasBluetoothPermission(context)) {
|
||||
Log.e("BluetoothUtils", "hasBluetoothCapability: no bluetooth permission")
|
||||
return false
|
||||
}
|
||||
|
||||
return context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="25.0dip"
|
||||
android:height="25.0dip"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="25.0"
|
||||
android:viewportHeight="25.0">
|
||||
<path
|
||||
android:fillColor="#ff2eafff"
|
||||
android:pathData="M6.66,0.66L18.66,0.66A6,6 0,0 1,24.66 6.66L24.66,18.66A6,6 0,0 1,18.66 24.66L6.66,24.66A6,6 0,0 1,0.66 18.66L0.66,6.66A6,6 0,0 1,6.66 0.66z" />
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M13.285,5.328C12.461,4.618 11.189,5.207 11.189,6.298V8.095C11.189,8.1 11.188,8.105 11.188,8.11V10.365L7.469,8.362C7.035,8.11 6.48,8.26 6.23,8.696C5.979,9.133 6.128,9.691 6.562,9.942L10.845,12.284L6.563,14.625C6.128,14.876 5.979,15.434 6.23,15.871C6.48,16.307 7.035,16.457 7.469,16.205L11.188,14.202V18.269C11.188,19.36 12.461,19.949 13.285,19.239L17.651,15.773C18.317,15.2 18.219,14.137 17.459,13.696L14.697,12.283L17.46,10.871C18.219,10.43 18.317,9.367 17.652,8.794L13.285,5.328ZM13.003,16.472C13.003,16.467 13.003,16.462 13.003,16.457V13.523L15.887,14.891L13.003,17.079V16.472ZM15.887,9.676L13.003,11.044V7.488L15.887,9.676Z" />
|
||||
</vector>
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="25.0dip"
|
||||
android:height="25.0dip"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="25.0"
|
||||
android:viewportHeight="25.0">
|
||||
<path
|
||||
android:fillColor="#ffe6e6e6"
|
||||
android:pathData="M6.66,0.66L18.66,0.66A6,6 0,0 1,24.66 6.66L24.66,18.66A6,6 0,0 1,18.66 24.66L6.66,24.66A6,6 0,0 1,0.66 18.66L0.66,6.66A6,6 0,0 1,6.66 0.66z" />
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M13.285,5.328C12.461,4.618 11.189,5.207 11.189,6.298V8.095C11.189,8.1 11.188,8.105 11.188,8.11V10.365L7.469,8.362C7.035,8.11 6.48,8.26 6.23,8.696C5.979,9.133 6.128,9.691 6.562,9.942L10.845,12.284L6.563,14.625C6.128,14.876 5.979,15.434 6.23,15.871C6.48,16.307 7.035,16.457 7.469,16.205L11.188,14.202V18.269C11.188,19.36 12.461,19.949 13.285,19.239L17.651,15.773C18.317,15.2 18.219,14.137 17.459,13.696L14.697,12.283L17.46,10.871C18.219,10.43 18.317,9.367 17.652,8.794L13.285,5.328ZM13.003,16.472C13.003,16.467 13.003,16.462 13.003,16.457V13.523L15.887,14.891L13.003,17.079V16.472ZM15.887,9.676L13.003,11.044V7.488L15.887,9.676Z" />
|
||||
</vector>
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dip"
|
||||
android:height="24.0dip"
|
||||
android:viewportWidth="76.0"
|
||||
android:viewportHeight="76.0">
|
||||
<path
|
||||
android:fillColor="#ff99a1bd"
|
||||
android:pathData="M0,38C0,17.0132 17.0132,0 38,0V0C58.9868,0 76,17.0132 76,38V38C76,58.9868 58.9868,76 38,76V76C17.0132,76 0,58.9868 0,38V38Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M23,32.3444L35.8889,37.855V22L52,30.6042L36.1574,38.0483L52,45.4924L35.8889,54V38.145L23,43.6556"
|
||||
android:strokeWidth="4.0"
|
||||
android:strokeColor="#ffffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dip"
|
||||
android:height="24.0dip"
|
||||
android:viewportWidth="76.0"
|
||||
android:viewportHeight="76.0">
|
||||
<path
|
||||
android:fillColor="#ff13d4b2"
|
||||
android:pathData="M0,38C0,17.0132 17.0132,0 38,0V0C58.9868,0 76,17.0132 76,38V38C76,58.9868 58.9868,76 38,76V76C17.0132,76 0,58.9868 0,38V38Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M31,21L45,21A6,6 0,0 1,51 27L51,49A6,6 0,0 1,45 55L31,55A6,6 0,0 1,25 49L25,27A6,6 0,0 1,31 21z"
|
||||
android:strokeWidth="4.0"
|
||||
android:strokeColor="#ffffffff" />
|
||||
</vector>
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dip"
|
||||
android:height="24.0dip"
|
||||
android:viewportWidth="76.0"
|
||||
android:viewportHeight="76.0">
|
||||
<path
|
||||
android:fillColor="#ff2eafff"
|
||||
android:pathData="M0,38C0,17.0132 17.0132,0 38,0V0C58.9868,0 76,17.0132 76,38V38C76,58.9868 58.9868,76 38,76V76C17.0132,76 0,58.9868 0,38V38Z" />
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M38,19C30.268,19 24,25.268 24,33V49C24,50.1046 24.8954,51 26,51C27.1046,51 28,50.1046 28,49V33C28,27.4772 32.4772,23 38,23C43.5228,23 48,27.4772 48,33V49C48,50.1046 48.8954,51 50,51C51.1046,51 52,50.1046 52,49V33C52,25.268 45.732,19 38,19ZM20,39C21.1046,39 22,39.8954 22,41L22,49C22,50.1046 21.1046,51 20,51C18.8954,51 18,50.1046 18,49V41C18,39.8954 18.8954,39 20,39ZM56,39C57.1046,39 58,39.8954 58,41V49C58,50.1046 57.1046,51 56,51C54.8954,51 54,50.1046 54,49V41C54,39.8954 54.8954,39 56,39Z" />
|
||||
</vector>
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dip"
|
||||
android:height="24.0dip"
|
||||
android:viewportWidth="76.0"
|
||||
android:viewportHeight="76.0">
|
||||
<path
|
||||
android:fillColor="#ff2eafff"
|
||||
android:pathData="M0,38C0,17.0132 17.0132,0 38,0V0C58.9868,0 76,17.0132 76,38V38C76,58.9868 58.9868,76 38,76V76C17.0132,76 0,58.9868 0,38V38Z" />
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M38,19C30.268,19 24,25.268 24,33V49C24,50.1046 24.8954,51 26,51C27.1046,51 28,50.1046 28,49V33C28,27.4772 32.4772,23 38,23C43.5228,23 48,27.4772 48,33V45C48,46.1046 47.1046,47 46,47H40C38.8954,47 38,47.8954 38,49C38,50.1046 38.8954,51 40,51H46C49.3137,51 52,48.3137 52,45V33C52,25.268 45.732,19 38,19ZM20,39C21.1046,39 22,39.8954 22,41L22,49C22,50.1046 21.1046,51 20,51C18.8954,51 18,50.1046 18,49V41C18,39.8954 18.8954,39 20,39ZM56,39C57.1046,39 58,39.8954 58,41V49C58,50.1046 57.1046,51 56,51C54.8954,51 54,50.1046 54,49V41C54,39.8954 54.8954,39 56,39Z" />
|
||||
</vector>
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dip"
|
||||
android:height="24.0dip"
|
||||
android:viewportWidth="76.0"
|
||||
android:viewportHeight="76.0">
|
||||
<path
|
||||
android:fillColor="#fffa9a2a"
|
||||
android:pathData="M0,38C0,17.0132 17.0132,0 38,0V0C58.9868,0 76,17.0132 76,38V38C76,58.9868 58.9868,76 38,76V76C17.0132,76 0,58.9868 0,38V38Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M20,46L20,32A4,4 0,0 1,24 28L52,28A4,4 0,0 1,56 32L56,46A4,4 0,0 1,52 50L24,50A4,4 0,0 1,20 46z"
|
||||
android:strokeWidth="4.0"
|
||||
android:strokeColor="#ffffffff" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,28L29,24.8C29,23.1198 29,22.2798 29.327,21.638C29.6146,21.0735 30.0735,20.6146 30.638,20.327C31.2798,20 32.1198,20 33.8,20L42.2,20C43.8802,20 44.7202,20 45.362,20.327C45.9265,20.6146 46.3854,21.0735 46.673,21.638C47,22.2798 47,23.1198 47,24.8L47,28L29,28Z"
|
||||
android:strokeWidth="4.0"
|
||||
android:strokeColor="#ffffffff" />
|
||||
<path
|
||||
android:fillColor="#fffa9a2a"
|
||||
android:pathData="M33.8,57C32.1198,57 31.2798,57 30.638,56.673C30.0735,56.3854 29.6146,55.9265 29.327,55.362C29,54.7202 29,53.8802 29,52.2L29,40L47,40L47,52.2C47,53.8802 47,54.7202 46.673,55.362C46.3854,55.9265 45.9265,56.3854 45.362,56.673C44.7202,57 43.8802,57 42.2,57L33.8,57Z"
|
||||
android:strokeWidth="4.0"
|
||||
android:strokeColor="#ffffffff" />
|
||||
</vector>
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dip"
|
||||
android:height="24.0dip"
|
||||
android:viewportWidth="76.0"
|
||||
android:viewportHeight="76.0">
|
||||
<path
|
||||
android:fillColor="#ff13d4b2"
|
||||
android:pathData="M0,38C0,17.0132 17.0132,0 38,0V0C58.9868,0 76,17.0132 76,38V38C76,58.9868 58.9868,76 38,76V76C17.0132,76 0,58.9868 0,38V38Z" />
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M29.48,23L29.4003,23L29.4003,23C28.3105,23 27.3704,22.9999 26.5969,23.0652C25.78,23.1341 24.9686,23.2865 24.1924,23.6974C23.1295,24.2602 22.2602,25.1295 21.6974,26.1924C21.2864,26.9686 21.1341,27.78 21.0652,28.5969C20.9999,29.3704 21,30.3105 21,31.4002V31.4003L21,31.48V44.4286L18.5535,46.7586L18.4972,46.8122L18.4971,46.8123C18.0787,47.2106 17.6711,47.5988 17.373,47.9391C17.1115,48.2377 16.5727,48.8811 16.5077,49.7848C16.4417,50.7028 16.8007,51.6003 17.4816,52.2195C18.152,52.8291 18.9858,52.9235 19.3811,52.9593C19.8316,53.0002 20.3945,53.0001 20.9723,53L21.05,53H54.95L55.0278,53C55.6055,53.0001 56.1684,53.0002 56.619,52.9593C57.0142,52.9235 57.8481,52.8291 58.5184,52.2195C59.1993,51.6003 59.5583,50.7028 59.4923,49.7848C59.4273,48.8811 58.8885,48.2377 58.627,47.9391C58.329,47.5988 57.9213,47.2106 57.5029,46.8123L57.4466,46.7586L55,44.4286V31.48V31.4003V31.4002C55,30.3104 55.0001,29.3704 54.9348,28.5969C54.8659,27.78 54.7136,26.9686 54.3026,26.1924C53.7398,25.1295 52.8705,24.2602 51.8076,23.6974C51.0314,23.2865 50.22,23.1341 49.4031,23.0652C48.6296,22.9999 47.6895,23 46.5997,23L46.52,23H29.48ZM26.0641,27.2325C26.1661,27.1785 26.3727,27.0983 26.9332,27.051C27.5183,27.0017 28.288,27 29.48,27H46.52C47.712,27 48.4817,27.0017 49.0668,27.051C49.6273,27.0983 49.8339,27.1785 49.9359,27.2325C50.2902,27.4201 50.5799,27.7098 50.7675,28.0641C50.8215,28.1661 50.9017,28.3727 50.949,28.9332C50.9983,29.5183 51,30.288 51,31.48V43H25V31.48C25,30.288 25.0017,29.5183 25.051,28.9332C25.0983,28.3727 25.1785,28.1661 25.2325,28.0641C25.4201,27.7098 25.7098,27.4201 26.0641,27.2325ZM24.1,47L22,49H54L51.9,47H24.1Z" />
|
||||
</vector>
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dip"
|
||||
android:height="24.0dip"
|
||||
android:viewportWidth="76.0"
|
||||
android:viewportHeight="76.0">
|
||||
<path
|
||||
android:fillColor="#ff565bdf"
|
||||
android:pathData="M0,38C0,17.0132 17.0132,0 38,0V0C58.9868,0 76,17.0132 76,38V38C76,58.9868 58.9868,76 38,76V76C17.0132,76 0,58.9868 0,38V38Z" />
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M46.5,32.5C46.5,33.6046 45.6046,34.5 44.5,34.5C43.3954,34.5 42.5,33.6046 42.5,32.5C42.5,31.3954 43.3954,30.5 44.5,30.5C45.6046,30.5 46.5,31.3954 46.5,32.5ZM42.5,36.5C42.5,37.6046 41.6046,38.5 40.5,38.5C39.3954,38.5 38.5,37.6046 38.5,36.5C38.5,35.3954 39.3954,34.5 40.5,34.5C41.6046,34.5 42.5,35.3954 42.5,36.5ZM48.5,38.5C49.6046,38.5 50.5,37.6046 50.5,36.5C50.5,35.3954 49.6046,34.5 48.5,34.5C47.3954,34.5 46.5,35.3954 46.5,36.5C46.5,37.6046 47.3954,38.5 48.5,38.5ZM46.5,40.5C46.5,41.6046 45.6046,42.5 44.5,42.5C43.3954,42.5 42.5,41.6046 42.5,40.5C42.5,39.3954 43.3954,38.5 44.5,38.5C45.6046,38.5 46.5,39.3954 46.5,40.5ZM30,31C30.8284,31 31.5,31.6716 31.5,32.5V35H34C34.8284,35 35.5,35.6716 35.5,36.5C35.5,37.3284 34.8284,38 34,38H31.5V40.5C31.5,41.3284 30.8284,42 30,42C29.1716,42 28.5,41.3284 28.5,40.5V38H26C25.1716,38 24.5,37.3284 24.5,36.5C24.5,35.6716 25.1716,35 26,35H28.5V32.5C28.5,31.6716 29.1716,31 30,31Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M20,32.6C20,29.2397 20,27.5595 20.654,26.2761C21.2292,25.1471 22.1471,24.2292 23.2761,23.654C24.5595,23 26.2397,23 29.6,23H45.4C48.7603,23 50.4405,23 51.7239,23.654C52.8529,24.2292 53.7708,25.1471 54.346,26.2761C55,27.5595 55,29.2397 55,32.6V46.0043C55,49.3157 52.3157,52 49.0043,52V52C47.8588,52 46.7407,51.6685 45.7453,51.1016C43.6741,49.9223 39.9263,48 37.5,48C35.0737,48 31.3259,49.9223 29.2547,51.1016C28.2593,51.6685 27.1412,52 25.9957,52V52C22.6843,52 20,49.3157 20,46.0043V32.6Z"
|
||||
android:strokeWidth="4.0"
|
||||
android:strokeColor="#ffffffff" />
|
||||
</vector>
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dip"
|
||||
android:height="24.0dip"
|
||||
android:viewportWidth="76.0"
|
||||
android:viewportHeight="76.0">
|
||||
<path
|
||||
android:fillColor="#ff99a1bd"
|
||||
android:pathData="M0,38C0,17.0132 17.0132,0 38,0V0C58.9868,0 76,17.0132 76,38V38C76,58.9868 58.9868,76 38,76V76C17.0132,76 0,58.9868 0,38V38Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M23,32.3444L35.8889,37.855V22L52,30.6042L36.1574,38.0483L52,45.4924L35.8889,54V38.145L23,43.6556"
|
||||
android:strokeWidth="4.0"
|
||||
android:strokeColor="#ffffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:pathData="M59.5,38.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0" />
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:pathData="M14.5,38.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0" />
|
||||
</vector>
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dip"
|
||||
android:height="24.0dip"
|
||||
android:viewportWidth="76.0"
|
||||
android:viewportHeight="76.0">
|
||||
<path
|
||||
android:fillColor="#fff36537"
|
||||
android:pathData="M38,0L38,0A38,38 0,0 1,76 38L76,38A38,38 0,0 1,38 76L38,76A38,38 0,0 1,0 38L0,38A38,38 0,0 1,38 0z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M51.2306,29C52.9783,31.5642 54,34.6628 54,38C54,46.8366 46.8366,54 38,54C29.1634,54 22,46.8366 22,38C22,34.6628 23.0217,31.5642 24.7694,29"
|
||||
android:strokeWidth="4.0"
|
||||
android:strokeColor="#ffffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:pathData="M28,22.6667C28,21.7462 28.7462,21 29.6667,21H46.3333C47.2538,21 48,21.7462 48,22.6667C48,24.5076 46.5076,26 44.6667,26H31.3333C29.4924,26 28,24.5076 28,22.6667Z" />
|
||||
</vector>
|
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/xui_config_color_white"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?attr/xui_config_color_separator_light" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="@dimen/config_padding_5dp"
|
||||
android:paddingBottom="@dimen/config_padding_5dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_device_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/auto_task_icon_bluetooth"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_device_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_device_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_edit"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:src="@drawable/ic_edit"
|
||||
app:tint="@color/toast_info_color"
|
||||
tools:ignore="ContentDescription,PrivateResource,ImageContrastCheck" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_remove"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_delete"
|
||||
app:tint="@color/toast_error_color"
|
||||
tools:ignore="ContentDescription,PrivateResource" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,258 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/xui_config_color_background"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:overScrollMode="never">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_margin="10dp"
|
||||
android:contentDescription="@string/task_bluetooth"
|
||||
app:srcCompat="@drawable/auto_task_icon_bluetooth"
|
||||
tools:ignore="ImageContrastCheck" />
|
||||
|
||||
<LinearLayout
|
||||
style="@style/BarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/task_bluetooth"
|
||||
android:textSize="@dimen/text_size_big"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/task_bluetooth_tips"
|
||||
android:textSize="@dimen/text_size_mini"
|
||||
tools:ignore="SmallSp" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="?attr/xui_config_color_separator_light" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rg_bluetooth_action"
|
||||
style="@style/rg_style"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/config_padding_5dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_action_state_changed"
|
||||
style="@style/rg_rb_style_match"
|
||||
android:checked="true"
|
||||
android:text="@string/bluetooth_state_changed"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_bluetooth_state"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="@dimen/config_margin_10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/specified_state"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rg_bluetooth_state"
|
||||
style="@style/rg_style"
|
||||
android:layout_marginStart="15dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_state_on"
|
||||
style="@style/rg_rb_style"
|
||||
android:checked="true"
|
||||
android:text="@string/state_on"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_state_off"
|
||||
style="@style/rg_rb_style"
|
||||
android:text="@string/state_off"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_action_discovery_finished"
|
||||
style="@style/rg_rb_style_match"
|
||||
android:text="@string/bluetooth_discovery_finished"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_discovery_finished"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="@dimen/config_margin_10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/specified_result"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rg_discovery_result"
|
||||
style="@style/rg_style"
|
||||
android:layout_marginStart="15dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_discovered"
|
||||
style="@style/rg_rb_style"
|
||||
android:checked="true"
|
||||
android:text="@string/discovered"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_undiscovered"
|
||||
style="@style/rg_rb_style"
|
||||
android:text="@string/undiscovered"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_action_acl_connected"
|
||||
style="@style/rg_rb_style_match"
|
||||
android:text="@string/bluetooth_acl_connected"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_action_acl_disconnected"
|
||||
style="@style/rg_rb_style_match"
|
||||
android:text="@string/bluetooth_acl_disconnected"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_device_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="@dimen/config_margin_10dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/specified_device"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
|
||||
android:id="@+id/et_device_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_weight="1"
|
||||
android:hint="@string/mac_hint"
|
||||
android:singleLine="true"
|
||||
app:met_clearButton="true"
|
||||
app:met_errorMessage="@string/mac_error"
|
||||
app:met_regexp="@string/mac_regex"
|
||||
app:met_validateOnFocusLost="true" />
|
||||
|
||||
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
|
||||
android:id="@+id/btn_start_discovery"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:padding="5dp"
|
||||
android:text="@string/start_discovery"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_mini"
|
||||
app:sb_color_unpressed="@color/colorPrimary"
|
||||
app:sb_ripple_color="@color/white"
|
||||
app:sb_ripple_duration="500"
|
||||
app:sb_shape_type="rectangle"
|
||||
tools:ignore="SmallSp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_devices"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/config_margin_5dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
|
||||
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
|
||||
android:id="@+id/btn_del"
|
||||
style="@style/SuperButton.Gray.Icon.Spacing"
|
||||
android:drawableStart="@drawable/ic_delete"
|
||||
android:text="@string/discard"
|
||||
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
|
||||
|
||||
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
|
||||
android:id="@+id/btn_save"
|
||||
style="@style/SuperButton.Blue.Icon.Spacing"
|
||||
android:drawableStart="@drawable/ic_save"
|
||||
android:text="@string/submit"
|
||||
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
Loading…
Reference in New Issue