<commit_before>/*
 *   Copyright (c) 2020 Project CHIP Authors
 *   All rights reserved.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.google.chip.chiptool.provisioning

import android.bluetooth.BluetoothGatt
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.chip.chiptool.ChipClient
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.DeviceIdUtil
import com.google.chip.chiptool.util.FragmentUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch

@ExperimentalCoroutinesApi
class DeviceProvisioningFragment : Fragment() {

  private lateinit var deviceInfo: CHIPDeviceInfo

  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(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View {
    deviceInfo = checkNotNull(requireArguments().getParcelable(ARG_DEVICE_INFO))
    return inflater.inflate(R.layout.single_fragment_container, container, false).apply {
      if (savedInstanceState == null) {
        if (deviceInfo.ipAddress != null) {
          pairDeviceWithAddress()
        } else {
          startConnectingToDevice()
        }
      }
    }
  }

  override fun onStop() {
    super.onStop()
    gatt = null
    scope.cancel()
  }

  private fun pairDeviceWithAddress() {
    // IANA CHIP port
    val port = 5540
    val id = DeviceIdUtil.getNextAvailableId(requireContext())
    val deviceController = ChipClient.getDeviceController(requireContext())
    DeviceIdUtil.setNextAvailableId(requireContext(), id + 1)
    deviceController.setCompletionListener(ConnectionCallback())
    deviceController.pairDeviceWithAddress(
      id,
      deviceInfo.ipAddress,
      port,
      deviceInfo.discriminator,
      deviceInfo.setupPinCode,
      null
    )
  }

  private fun startConnectingToDevice() {
    if (gatt != null) {
      return
    }

    scope.launch {
      val deviceController = ChipClient.getDeviceController(requireContext())
      val bluetoothManager = BluetoothManager()

      showMessage(
        R.string.rendezvous_over_ble_scanning_text,
        deviceInfo.discriminator.toString()
      )
      val device = bluetoothManager.getBluetoothDevice(deviceInfo.discriminator) ?: run {
        showMessage(R.string.rendezvous_over_ble_scanning_failed_text)
        return@launch
      }

      showMessage(
        R.string.rendezvous_over_ble_connecting_text,
        device.name ?: device.address.toString()
      )
      gatt = bluetoothManager.connect(requireContext(), device)

      showMessage(R.string.rendezvous_over_ble_pairing_text)
      deviceController.setCompletionListener(ConnectionCallback())

      val deviceId = DeviceIdUtil.getNextAvailableId(requireContext())
      deviceController.pairDevice(gatt, deviceId, deviceInfo.setupPinCode)
      DeviceIdUtil.setNextAvailableId(requireContext(), deviceId + 1)
    }
  }

  private fun showMessage(msgResId: Int, stringArgs: String? = null) {
    requireActivity().runOnUiThread {
      val context = requireContext()
      Toast.makeText(context, context.getString(msgResId, stringArgs), Toast.LENGTH_SHORT)
        .show()
    }
  }

  inner class ConnectionCallback : GenericChipDeviceListener() {
    override fun onConnectDeviceComplete() {
      Log.d(TAG, "onConnectDeviceComplete")
    }

    /**
     * This would only happen in the on-network case. In other cases, we need network commissioning
     * first before this callback can be invoked, so we would use the callback implementation in
     * EnterNetworkFragment.
     */
    override fun onCommissioningComplete(nodeId: Long, errorCode: Int) {
      Log.d(TAG, "Commissioning complete for $nodeId with errorCode $errorCode")
      FragmentUtil.getHost(this@DeviceProvisioningFragment, Callback::class.java)
        ?.onCommissioningComplete(0)
    }

    override fun onStatusUpdate(status: Int) {
      Log.d(TAG, "Pairing status update: $status")
    }

    override fun onPairingComplete(code: Int) {
      Log.d(TAG, "onPairingComplete: $code")

      if (code == STATUS_PAIRING_SUCCESS) {
        childFragmentManager.beginTransaction()
            .add(R.id.fragment_container, EnterNetworkFragment.newInstance(networkType))
            .commit()
      } else {
        showMessage(R.string.rendezvous_over_ble_pairing_failure_text)
      }
    }

    override fun onOpCSRGenerationComplete(csr: ByteArray) {
      Log.d(TAG, String(csr))
    }

    override fun onPairingDeleted(code: Int) {
      Log.d(TAG, "onPairingDeleted: $code")
    }

    override fun onCloseBleComplete() {
      Log.d(TAG, "onCloseBleComplete")
    }

    override fun onError(error: Throwable?) {
      Log.d(TAG, "onError: $error")
    }
  }

  /** Callback from [DeviceProvisioningFragment] notifying any registered listeners. */<commit_msg>Stop using destroyed fragment in pairing complete callback (#9729)<commit_after>/*
 *   Copyright (c) 2020 Project CHIP Authors
 *   All rights reserved.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.google.chip.chiptool.provisioning

import android.bluetooth.BluetoothGatt
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.chip.chiptool.ChipClient
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.DeviceIdUtil
import com.google.chip.chiptool.util.FragmentUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch

@ExperimentalCoroutinesApi
class DeviceProvisioningFragment : Fragment() {

  private lateinit var deviceInfo: CHIPDeviceInfo

  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(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View {
    deviceInfo = checkNotNull(requireArguments().getParcelable(ARG_DEVICE_INFO))
    return inflater.inflate(R.layout.single_fragment_container, container, false).apply {
      if (savedInstanceState == null) {
        if (deviceInfo.ipAddress != null) {
          pairDeviceWithAddress()
        } else {
          startConnectingToDevice()
        }
      }
    }
  }

  override fun onStop() {
    super.onStop()
    gatt = null
    scope.cancel()
  }

  private fun pairDeviceWithAddress() {
    // IANA CHIP port
    val port = 5540
    val id = DeviceIdUtil.getNextAvailableId(requireContext())
    val deviceController = ChipClient.getDeviceController(requireContext())
    DeviceIdUtil.setNextAvailableId(requireContext(), id + 1)
    deviceController.setCompletionListener(ConnectionCallback())
    deviceController.pairDeviceWithAddress(
      id,
      deviceInfo.ipAddress,
      port,
      deviceInfo.discriminator,
      deviceInfo.setupPinCode,
      null
    )
  }

  private fun startConnectingToDevice() {
    if (gatt != null) {
      return
    }

    scope.launch {
      val deviceController = ChipClient.getDeviceController(requireContext())
      val bluetoothManager = BluetoothManager()

      showMessage(
        R.string.rendezvous_over_ble_scanning_text,
        deviceInfo.discriminator.toString()
      )
      val device = bluetoothManager.getBluetoothDevice(deviceInfo.discriminator) ?: run {
        showMessage(R.string.rendezvous_over_ble_scanning_failed_text)
        return@launch
      }

      showMessage(
        R.string.rendezvous_over_ble_connecting_text,
        device.name ?: device.address.toString()
      )
      gatt = bluetoothManager.connect(requireContext(), device)

      showMessage(R.string.rendezvous_over_ble_pairing_text)
      deviceController.setCompletionListener(ConnectionCallback())

      val deviceId = DeviceIdUtil.getNextAvailableId(requireContext())
      deviceController.pairDevice(gatt, deviceId, deviceInfo.setupPinCode)
      DeviceIdUtil.setNextAvailableId(requireContext(), deviceId + 1)
    }
  }

  private fun showMessage(msgResId: Int, stringArgs: String? = null) {
    requireActivity().runOnUiThread {
      val context = requireContext()
      Toast.makeText(context, context.getString(msgResId, stringArgs), Toast.LENGTH_SHORT)
        .show()
    }
  }

  inner class ConnectionCallback : GenericChipDeviceListener() {
    override fun onConnectDeviceComplete() {
      Log.d(TAG, "onConnectDeviceComplete")
    }

    /**
     * This would only happen in the on-network case. In other cases, we need network commissioning
     * first before this callback can be invoked, so we would use the callback implementation in
     * EnterNetworkFragment.
     */
    override fun onCommissioningComplete(nodeId: Long, errorCode: Int) {
      Log.d(TAG, "Commissioning complete for $nodeId with errorCode $errorCode")
      FragmentUtil.getHost(this@DeviceProvisioningFragment, Callback::class.java)
        ?.onCommissioningComplete(0)
    }

    override fun onStatusUpdate(status: Int) {
      Log.d(TAG, "Pairing status update: $status")
    }

    override fun onPairingComplete(code: Int) {
      Log.d(TAG, "onPairingComplete: $code")

      // In IP commissioning, commissioning complete will already be called at this point, and the
      // next fragment will be shown. As a result, this fragment will be in a destroyed state and
      // should not be operated on.
      if (deviceInfo.ipAddress != null) {
        return
      }

      if (code == STATUS_PAIRING_SUCCESS) {
        childFragmentManager.beginTransaction()
            .add(R.id.fragment_container, EnterNetworkFragment.newInstance(networkType))
            .commit()
      } else {
        showMessage(R.string.rendezvous_over_ble_pairing_failure_text)
      }
    }

    override fun onOpCSRGenerationComplete(csr: ByteArray) {
      Log.d(TAG, String(csr))
    }

    override fun onPairingDeleted(code: Int) {
      Log.d(TAG, "onPairingDeleted: $code")
    }

    override fun onCloseBleComplete() {
      Log.d(TAG, "onCloseBleComplete")
    }

    override fun onError(error: Throwable?) {
      Log.d(TAG, "onError: $error")
    }
  }

  /** Callback from [DeviceProvisioningFragment] notifying any registered listeners. */
{'fraction_non_alphanumeric': 0.08640497845859263, 'fraction_numerical': 0.0027126216690601564, 'mean_word_length': 3.5865349432857667, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3146810', 'n_tokens_mistral': 3682, 'n_tokens_neox': 3557, 'n_words': 822}