Skip to content

Commit

Permalink
[Android CHIPTool] Wi-Fi vs Thread provisioning.
Browse files Browse the repository at this point in the history
Add distinct buttons to Android app to provision
Wi-Fi vs Thread network during CHIP device setup.
  • Loading branch information
vidhis88 committed Dec 7, 2020
1 parent eba006a commit 0f96b39
Show file tree
Hide file tree
Showing 16 changed files with 351 additions and 396 deletions.
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

0 comments on commit 0f96b39

Please sign in to comment.