Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android CHIPTool] Wi-Fi vs Thread provisioning. #4082

Merged
merged 1 commit into from
Dec 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.google.chip.chiptool.clusterclient.OnOffClientFragment
import com.google.chip.chiptool.commissioner.CommissionerActivity
import com.google.chip.chiptool.echoclient.EchoClientFragment
import com.google.chip.chiptool.provisioning.DeviceProvisioningFragment
import com.google.chip.chiptool.provisioning.ProvisionNetworkType
import com.google.chip.chiptool.setuppayloadscanner.BarcodeFragment
import com.google.chip.chiptool.setuppayloadscanner.CHIPDeviceDetailsFragment
import com.google.chip.chiptool.setuppayloadscanner.CHIPDeviceInfo
Expand All @@ -38,7 +39,9 @@ class CHIPToolActivity :
AppCompatActivity(),
BarcodeFragment.Callback,
SelectActionFragment.Callback,
CHIPDeviceDetailsFragment.Callback {
DeviceProvisioningFragment.Callback {

private var networkType: ProvisionNetworkType? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -50,24 +53,47 @@ class CHIPToolActivity :
.beginTransaction()
.add(R.id.fragment_container, fragment, fragment.javaClass.simpleName)
.commit()
} else {
networkType =
ProvisionNetworkType.fromName(savedInstanceState.getString(ARG_PROVISION_NETWORK_TYPE))
}

if (intent?.action == NfcAdapter.ACTION_NDEF_DISCOVERED)
onNfcIntent(intent)
}

override fun onStartRendezvousOverBle(deviceInfo: CHIPDeviceInfo) {
showFragment(DeviceProvisioningFragment.newInstance(deviceInfo))
override fun onSaveInstanceState(outState: Bundle) {
outState.putString(ARG_PROVISION_NETWORK_TYPE, networkType?.name)

super.onSaveInstanceState(outState)
}

override fun onCHIPDeviceInfoReceived(deviceInfo: CHIPDeviceInfo) {
showFragment(CHIPDeviceDetailsFragment.newInstance(deviceInfo))
if (networkType == null) {
showFragment(CHIPDeviceDetailsFragment.newInstance(deviceInfo))
} else {
showFragment(DeviceProvisioningFragment.newInstance(deviceInfo, networkType!!), false)
}
}

override fun onPairingComplete() {
showFragment(OnOffClientFragment.newInstance(), false)
}

override fun handleScanQrCodeClicked() {
showFragment(BarcodeFragment.newInstance())
}

override fun onProvisionWifiCredentialsClicked() {
networkType = ProvisionNetworkType.WIFI
showFragment(BarcodeFragment.newInstance(), false)
}

override fun onProvisionThreadCredentialsClicked() {
networkType = ProvisionNetworkType.THREAD
showFragment(BarcodeFragment.newInstance(), false)
}

override fun handleCommissioningClicked() {
var intent = Intent(this, CommissionerActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_COMMISSIONING)
Expand All @@ -94,12 +120,16 @@ class CHIPToolActivity :
}
}

private fun showFragment(fragment: Fragment) {
supportFragmentManager
private fun showFragment(fragment: Fragment, showOnBack: Boolean = true) {
val fragmentTransaction = supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, fragment, fragment.javaClass.simpleName)
.addToBackStack(null)
.commit()

if (showOnBack) {
fragmentTransaction.addToBackStack(null)
}

fragmentTransaction.commit()
}

private fun onNfcIntent(intent: Intent?) {
Expand Down Expand Up @@ -128,6 +158,8 @@ class CHIPToolActivity :
}

companion object {
private const val ARG_PROVISION_NETWORK_TYPE = "provision_network_type"

var REQUEST_CODE_COMMISSIONING = 0xB003
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ class SelectActionFragment : Fragment() {
): View {
return inflater.inflate(R.layout.select_action_fragment, container, false).apply {
scanQrBtn.setOnClickListener { getCallback()?.handleScanQrCodeClicked() }
commissioningBtn.setOnClickListener { getCallback()?.handleCommissioningClicked() }
provisionWifiCredentialsBtn.setOnClickListener {
getCallback()?.onProvisionWifiCredentialsClicked()
}
provisionThreadCredentialsBtn.setOnClickListener {
getCallback()?.onProvisionThreadCredentialsClicked()
}
otCommissioningBtn.setOnClickListener { getCallback()?.handleCommissioningClicked() }
echoClientBtn.setOnClickListener { getCallback()?.handleEchoClientClicked() }
onOffClusterBtn.setOnClickListener { getCallback()?.handleOnOffClicked() }
attestationTestBtn.setOnClickListener { getCallback()?.handleAttestationTestClicked() }
Expand All @@ -48,6 +54,10 @@ class SelectActionFragment : Fragment() {
interface Callback {
/** Notifies listener of Scan QR code button click. */
fun handleScanQrCodeClicked()
/** Notifies listener of provision-Wifi-credentials button click. */
fun onProvisionWifiCredentialsClicked()
/** Notifies listener of provision-Thread-credentials button click. */
fun onProvisionThreadCredentialsClicked()
/** Notifies listener of Commissioning button click. */
fun handleCommissioningClicked()
/** Notifies listener of Echo client button click. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.google.chip.chiptool.GenericChipDeviceListener
import com.google.chip.chiptool.R
import com.google.chip.chiptool.bluetooth.BluetoothManager
import com.google.chip.chiptool.setuppayloadscanner.CHIPDeviceInfo
import com.google.chip.chiptool.util.FragmentUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -45,6 +46,11 @@ class DeviceProvisioningFragment : Fragment() {

private var gatt: BluetoothGatt? = null

private val networkType: ProvisionNetworkType
get() = requireNotNull(
ProvisionNetworkType.fromName(arguments?.getString(ARG_PROVISION_NETWORK_TYPE))
)

private val scope = CoroutineScope(Dispatchers.Main + Job())

override fun onCreateView(
Expand Down Expand Up @@ -104,12 +110,6 @@ class DeviceProvisioningFragment : Fragment() {
}
}

private fun closeChildFragments() {
childFragmentManager.fragments.forEach {
f -> childFragmentManager.beginTransaction().remove(f).commit()
}
}

inner class ConnectionCallback : GenericChipDeviceListener() {
override fun onConnectDeviceComplete() {
Log.d(TAG, "onConnectDeviceComplete")
Expand All @@ -118,17 +118,16 @@ class DeviceProvisioningFragment : Fragment() {
override fun onStatusUpdate(status: Int) {
Log.i(TAG, "Pairing status update: $status");

when (status) {
STATUS_NETWORK_PROVISIONING_SUCCESS -> {
showMessage(R.string.rendezvous_over_ble_provisioning_success_text)
closeChildFragments()
}
if (status == STATUS_NETWORK_PROVISIONING_SUCCESS) {
showMessage(R.string.rendezvous_over_ble_provisioning_success_text)
FragmentUtil.getHost(this@DeviceProvisioningFragment, Callback::class.java)
?.onPairingComplete()
}
}

override fun onNetworkCredentialsRequested() {
childFragmentManager.beginTransaction()
.add(R.id.fragment_container, EnterNetworkFragment.newInstance())
.add(R.id.fragment_container, EnterNetworkFragment.newInstance(networkType))
.commit()
}

Expand All @@ -149,15 +148,28 @@ class DeviceProvisioningFragment : Fragment() {
}
}

/** Callback from [DeviceProvisioningFragment] notifying any registered listeners. */
interface Callback {
/** Notifies that pairing has been completed. */
fun onPairingComplete()
}

companion object {
private const val TAG = "DeviceProvisioningFragment"
private const val ARG_DEVICE_INFO = "device_info"
private const val ARG_PROVISION_NETWORK_TYPE = "provision_network_type"
private const val STATUS_NETWORK_PROVISIONING_SUCCESS = 2

fun newInstance(deviceInfo: CHIPDeviceInfo): DeviceProvisioningFragment {
fun newInstance(
deviceInfo: CHIPDeviceInfo,
networkType: ProvisionNetworkType
): DeviceProvisioningFragment {
return DeviceProvisioningFragment().apply {
arguments = Bundle(1).apply { putParcelable(ARG_DEVICE_INFO, deviceInfo) }
arguments = Bundle(2).apply {
putParcelable(ARG_DEVICE_INFO, deviceInfo)
putString(ARG_PROVISION_NETWORK_TYPE, networkType.name)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,131 @@
* limitations under the License.
*
*/

package com.google.chip.chiptool.provisioning

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.google.android.material.tabs.TabLayout
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.R
import kotlinx.android.synthetic.main.enter_network_fragment.view.*
import kotlinx.android.synthetic.main.enter_thread_network_fragment.channelEd
import kotlinx.android.synthetic.main.enter_thread_network_fragment.masterKeyEd
import kotlinx.android.synthetic.main.enter_thread_network_fragment.panIdEd
import kotlinx.android.synthetic.main.enter_thread_network_fragment.xpanIdEd
import kotlinx.android.synthetic.main.enter_wifi_network_fragment.*
import kotlinx.android.synthetic.main.enter_wifi_network_fragment.view.*

/**
* Fragment to collect Wi-Fi network information from user and send it to device being provisioned.
*/
class EnterNetworkFragment : Fragment() {

private val networkType: ProvisionNetworkType
get() = requireNotNull(
ProvisionNetworkType.fromName(arguments?.getString(ARG_PROVISION_NETWORK_TYPE))
)

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.enter_network_fragment, container, false).apply {
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab) = Unit
override fun onTabUnselected(tab: TabLayout.Tab) = Unit
override fun onTabSelected(tab: TabLayout.Tab) {
when (tab.text.toString()) {
TAB_CAPTION_WIFI -> selectPage(EnterWifiNetworkFragment.newInstance())
TAB_CAPTION_THREAD -> selectPage(EnterThreadNetworkFragment.newInstance())
}
}
})
): View? {
val layoutRes = when (networkType) {
ProvisionNetworkType.WIFI -> R.layout.enter_wifi_network_fragment
ProvisionNetworkType.THREAD -> R.layout.enter_thread_network_fragment
}

return inflater.inflate(layoutRes, container, false).apply {
saveNetworkBtn.setOnClickListener { onSaveNetworkClicked() }
}
}

selectPage(EnterWifiNetworkFragment.newInstance())
private fun onSaveNetworkClicked() {
if (networkType == ProvisionNetworkType.WIFI) {
saveWifiNetwork()
} else {
saveThreadNetwork()
}
}

private fun selectPage(fragment: Fragment) {
childFragmentManager.beginTransaction().replace(R.id.page, fragment).commit()
private fun saveWifiNetwork() {
val ssid = ssidEd.text
val pwd = pwdEd.text

if (ssid.isNullOrBlank() || pwd.isNullOrBlank()) {
Toast.makeText(requireContext(), "Ssid and password required.", Toast.LENGTH_SHORT).show()
return
}

ChipClient.getDeviceController().apply {
sendWiFiCredentials(ssid.toString(), pwd.toString())
}
}

private fun saveThreadNetwork() {
val channelStr = channelEd.text
val panIdStr = panIdEd.text

if (channelStr.isNullOrBlank()) {
Toast.makeText(requireContext(), "Channel is empty", Toast.LENGTH_SHORT).show()
return
}

if (panIdStr.isNullOrBlank()) {
Toast.makeText(requireContext(), "PAN ID is empty", Toast.LENGTH_SHORT).show()
return
}

if (xpanIdEd.text.isNullOrBlank()) {
Toast.makeText(requireContext(), "XPAN ID is empty", Toast.LENGTH_SHORT).show()
return
}

val xpanIdStr = xpanIdEd.text.toString().filterNot { c -> c == ':' }
if (xpanIdStr.length != NUM_XPANID_BYTES * 2) {
Toast.makeText(requireContext(), "Extended PAN ID is invalid", Toast.LENGTH_SHORT).show()
return
}

if (masterKeyEd.text.isNullOrBlank()) {
Toast.makeText(requireContext(), "Master Key is empty", Toast.LENGTH_SHORT).show()
return
}

val masterKeyStr = masterKeyEd.text.toString().filterNot { c -> c == ':' }
if (masterKeyStr.length != NUM_MASTER_KEY_BYTES * 2) {
Toast.makeText(requireContext(), "Master key is invalid", Toast.LENGTH_SHORT).show()
return
}

ChipClient.getDeviceController().sendThreadCredentials(
channelStr.toString().toInt(),
panIdStr.toString().toInt(16),
xpanIdStr.hexToByteArray(),
masterKeyStr.hexToByteArray())
}

private fun String.hexToByteArray(): ByteArray {
return chunked(2).map{ byteStr -> byteStr.toUByte(16).toByte()}.toByteArray()
}

companion object {
private const val TAB_CAPTION_WIFI = "Wi-Fi"
private const val TAB_CAPTION_THREAD = "Thread"
private const val TAG = "EnterNetworkFragment"
private const val ARG_PROVISION_NETWORK_TYPE = "provision_network_type"
private const val NUM_XPANID_BYTES = 8
private const val NUM_MASTER_KEY_BYTES = 16

fun newInstance() = EnterNetworkFragment()
fun newInstance(provisionNetworkType: ProvisionNetworkType): EnterNetworkFragment {
return EnterNetworkFragment().apply {
arguments = Bundle(1).apply {
putString(ARG_PROVISION_NETWORK_TYPE, provisionNetworkType.name)
}
}
}
}

}
}
Loading