Skip to content

Commit

Permalink
Add Android Open Commissioning Callback API (#19441)
Browse files Browse the repository at this point in the history
* Add Android Open Commissioning Callback API

* Restyled by google-java-format

* Restyled by clang-format

* Restyled by gn

* Fix code below reviewer comment

* Restyled by whitespace

* Restyled by clang-format

Co-authored-by: Restyled.io <[email protected]>
  • Loading branch information
2 people authored and pull[bot] committed Oct 3, 2023
1 parent bad418b commit 1488156
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import chip.devicecontroller.ChipClusters
import chip.devicecontroller.ChipDeviceController
import chip.devicecontroller.OpenCommissioningCallback
import com.google.chip.chiptool.ChipClient
import com.google.chip.chiptool.GenericChipDeviceListener
import com.google.chip.chiptool.R
import kotlinx.android.synthetic.main.multi_admin_client_fragment.discriminatorEd
import kotlinx.android.synthetic.main.multi_admin_client_fragment.timeoutEd
import kotlinx.android.synthetic.main.multi_admin_client_fragment.multiAdminClusterCommandStatus
import kotlinx.android.synthetic.main.multi_admin_client_fragment.setupPinCodeEd
import kotlinx.android.synthetic.main.multi_admin_client_fragment.view.basicCommissioningMethodBtn
import kotlinx.android.synthetic.main.multi_admin_client_fragment.view.enhancedCommissioningMethodBtn
import kotlinx.android.synthetic.main.multi_admin_client_fragment.view.revokeBtn
import kotlinx.android.synthetic.main.on_off_client_fragment.*
import kotlinx.coroutines.*

class MultiAdminClientFragment : Fragment() {
Expand Down Expand Up @@ -53,8 +54,10 @@ class MultiAdminClientFragment : Fragment() {
// TODO: use the discriminator and setupPinCode that was used to commission the device
val testDiscriminator = "3840"
val testSetupPinCode = 20202021L
val testDuration = 180
discriminatorEd.setText(testDiscriminator)
setupPinCodeEd.setText(testSetupPinCode.toString())
timeoutEd.setText(testDuration.toString())
}

inner class ChipControllerCallback : GenericChipDeviceListener() {
Expand All @@ -78,23 +81,41 @@ class MultiAdminClientFragment : Fragment() {
}

private suspend fun sendBasicCommissioningCommandClick() {
val testDuration = 100
deviceController.openPairingWindow(
val testDuration = timeoutEd.text.toString().toInt()
deviceController.openPairingWindowCallback(
ChipClient.getConnectedDevicePointer(
requireContext(),
addressUpdateFragment.deviceId
), testDuration
), testDuration,
object:OpenCommissioningCallback {
override fun onError(status: Int, deviceId: Long) {
showMessage("OpenBasicCommissioning Fail! \nDevice ID : $deviceId\nErrorCode : $status")
}

override fun onSuccess(deviceId: Long, manualPairingCode: String?, qrCode: String?) {
showMessage("OpenBasicCommissioning Success! \n Node ID: $deviceId")
}
}
)
}

private suspend fun sendEnhancedCommissioningCommandClick() {
val testDuration = 100
val testDuration = timeoutEd.text.toString().toInt()
val testIteration = 1000
val devicePointer =
ChipClient.getConnectedDevicePointer(requireContext(), addressUpdateFragment.deviceId)
deviceController.openPairingWindowWithPIN(
deviceController.openPairingWindowWithPINCallback(
devicePointer, testDuration, testIteration.toLong(),
discriminatorEd.text.toString().toInt(), setupPinCodeEd.text.toString().toULong().toLong()
discriminatorEd.text.toString().toInt(), setupPinCodeEd.text.toString().toULong().toLong(),
object:OpenCommissioningCallback {
override fun onError(status: Int, deviceId: Long) {
showMessage("OpenCommissioning Fail! \nDevice ID : $deviceId\nErrorCode : $status")
}

override fun onSuccess(deviceId: Long, manualPairingCode: String?, qrCode: String?) {
showMessage("OpenCommissioning Success! \n Node ID: $deviceId\n\tManual : $manualPairingCode\n\tQRCode : $qrCode")
}
}
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

<EditText
android:id="@+id/timeoutEd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/addressUpdateFragment"
android:inputType="number"
android:textSize="20sp"
android:hint="@string/enter_commissioning_timeout_hint_text"/>

<Button
android:id="@+id/basicCommissioningMethodBtn"
android:layout_width="0dp"
Expand All @@ -22,7 +34,7 @@
android:text="@string/basic_commissioning_method_btn_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/addressUpdateFragment"/>
app:layout_constraintTop_toBottomOf="@id/timeoutEd"/>

<EditText
android:id="@+id/discriminatorEd"
Expand Down
1 change: 1 addition & 0 deletions src/android/CHIPTool/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
<string name="wildcard_btn_text">Wildcard</string>

<string name="multi_admin_client_btn_text">Multi-admin cluster</string>
<string name="enter_commissioning_timeout_hint_text">Enter Commissioning timeout</string>
<string name="basic_commissioning_method_btn_text">Basic Commissioning Method</string>
<string name="enter_discriminator_hint_text">Enter Discriminator</string>
<string name="enter_setup_pin_code_hint_text">Enter Setup PIN Code</string>
Expand Down
178 changes: 178 additions & 0 deletions src/controller/java/AndroidCommissioningWindowOpener.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright (c) 2022 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.
*/

#include "AndroidCommissioningWindowOpener.h"

#include <app-common/zap-generated/cluster-objects.h>
#include <controller-clusters/zap-generated/CHIPClusters.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/JniReferences.h>
#include <lib/support/JniTypeWrappers.h>
#include <protocols/secure_channel/PASESession.h>
#include <setup_payload/ManualSetupPayloadGenerator.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>

using namespace chip::app::Clusters;
using namespace chip::System::Clock;

namespace chip {
namespace Controller {

AndroidCommissioningWindowOpener::AndroidCommissioningWindowOpener(DeviceController * controller, jobject jCallbackObject) :
CommissioningWindowOpener(controller), mOnOpenCommissioningWindowCallback(OnOpenCommissioningWindowResponse, this),
mOnOpenBasicCommissioningWindowCallback(OnOpenBasicCommissioningWindowResponse, this)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
mJavaCallback = env->NewGlobalRef(jCallbackObject);

jclass callbackClass = env->GetObjectClass(jCallbackObject);

mOnSuccessMethod = env->GetMethodID(callbackClass, "onSuccess", "(JLjava/lang/String;Ljava/lang/String;)V");
if (mOnSuccessMethod == nullptr)
{
ChipLogError(Controller, "Failed to access callback 'onSuccess' method");
env->ExceptionClear();
}

mOnErrorMethod = env->GetMethodID(callbackClass, "onError", "(IJ)V");
if (mOnErrorMethod == nullptr)
{
ChipLogError(Controller, "Failed to access callback 'onError' method");
env->ExceptionClear();
}
}

AndroidCommissioningWindowOpener::~AndroidCommissioningWindowOpener()
{
ChipLogError(Controller, "Delete AndroidCommissioningWindowOpener");
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
env->DeleteGlobalRef(mJavaCallback);
}

CHIP_ERROR AndroidCommissioningWindowOpener::OpenBasicCommissioningWindow(DeviceController * controller, NodeId deviceId,
Seconds16 timeout, jobject jcallback)
{
// Not using Platform::New because we want to keep our constructor private.
auto * opener = new AndroidCommissioningWindowOpener(controller, jcallback);
if (opener == nullptr)
{
return CHIP_ERROR_NO_MEMORY;
}

CHIP_ERROR err = opener->CommissioningWindowOpener::OpenBasicCommissioningWindow(
deviceId, timeout, &opener->mOnOpenBasicCommissioningWindowCallback);
if (err != CHIP_NO_ERROR)
{
delete opener;
}
// Else will clean up when the callback is called.
return err;
}

CHIP_ERROR AndroidCommissioningWindowOpener::OpenCommissioningWindow(DeviceController * controller, NodeId deviceId,
Seconds16 timeout, uint32_t iteration, uint16_t discriminator,
Optional<uint32_t> setupPIN, Optional<ByteSpan> salt,
jobject jcallback, SetupPayload & payload,
bool readVIDPIDAttributes)
{
// Not using Platform::New because we want to keep our constructor private.
auto * opener = new AndroidCommissioningWindowOpener(controller, jcallback);
if (opener == nullptr)
{
return CHIP_ERROR_NO_MEMORY;
}

CHIP_ERROR err = opener->CommissioningWindowOpener::OpenCommissioningWindow(
deviceId, timeout, iteration, discriminator, setupPIN, salt, &opener->mOnOpenCommissioningWindowCallback, payload,
readVIDPIDAttributes);
if (err != CHIP_NO_ERROR)
{
delete opener;
}
// Else will clean up when the callback is called.
return err;
}

void AndroidCommissioningWindowOpener::OnOpenCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status,
chip::SetupPayload payload)
{
auto * self = static_cast<AndroidCommissioningWindowOpener *>(context);
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();

VerifyOrExit(self->mJavaCallback != nullptr, ChipLogError(Controller, "mJavaCallback is not allocated."));

if (status == CHIP_NO_ERROR)
{
std::string QRCode;
std::string manualPairingCode;

SuccessOrExit(ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(manualPairingCode));
SuccessOrExit(QRCodeSetupPayloadGenerator(payload).payloadBase38Representation(QRCode));

if (self->mOnSuccessMethod != nullptr)
{
UtfString jManualPairingCode(env, manualPairingCode.c_str());
UtfString jQRCode(env, QRCode.c_str());
env->CallVoidMethod(self->mJavaCallback, self->mOnSuccessMethod, static_cast<jlong>(deviceId),
jManualPairingCode.jniValue(), jQRCode.jniValue());
}
}
else
{
if (self->mOnErrorMethod != nullptr)
{
env->CallVoidMethod(self->mJavaCallback, self->mOnErrorMethod, static_cast<jint>(status.GetValue()),
static_cast<jlong>(deviceId));
}
}
exit:
delete self;
}

void AndroidCommissioningWindowOpener::OnOpenBasicCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status)
{
auto * self = static_cast<AndroidCommissioningWindowOpener *>(context);

if (self->mJavaCallback != nullptr)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
if (status == CHIP_NO_ERROR)
{
if (self->mOnSuccessMethod != nullptr)
{
UtfString jManualPairingCode(env, "");
UtfString jQRCode(env, "");
env->CallVoidMethod(self->mJavaCallback, self->mOnSuccessMethod, static_cast<jlong>(deviceId),
jManualPairingCode.jniValue(), jQRCode.jniValue());
}
}
else
{
if (self->mOnErrorMethod != nullptr)
{
env->CallVoidMethod(self->mJavaCallback, self->mOnErrorMethod, static_cast<jint>(status.GetValue()),
static_cast<jlong>(deviceId));
}
}
}

delete self;
}

} // namespace Controller
} // namespace chip
61 changes: 61 additions & 0 deletions src/controller/java/AndroidCommissioningWindowOpener.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2022 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.
*/

#pragma once

#include <controller/CommissioningWindowOpener.h>
#include <jni.h>

namespace chip {
namespace Controller {

/**
* A helper class that can be used by consumers that don't care about the callback from the
* open-commissioning-window process and just want automatic cleanup of the CommissioningWindowOpener when done
* with it.
*/
class AndroidCommissioningWindowOpener : private CommissioningWindowOpener
{
public:
// Takes the same arguments as CommissioningWindowOpener::OpenBasicCommissioningWindow except without the
// callback.
static CHIP_ERROR OpenBasicCommissioningWindow(DeviceController * controller, NodeId deviceId, System::Clock::Seconds16 timeout,
jobject jcallback);
// Takes the same arguments as CommissioningWindowOpener::OpenCommissioningWindow except without the
// callback.
static CHIP_ERROR OpenCommissioningWindow(DeviceController * controller, NodeId deviceId, System::Clock::Seconds16 timeout,
uint32_t iteration, uint16_t discriminator, Optional<uint32_t> setupPIN,
Optional<ByteSpan> salt, jobject jcallback, SetupPayload & payload,
bool readVIDPIDAttributes = false);

private:
AndroidCommissioningWindowOpener(DeviceController * controller, jobject javaCallbackObject);
~AndroidCommissioningWindowOpener();

static void OnOpenCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status, chip::SetupPayload payload);
static void OnOpenBasicCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status);

chip::Callback::Callback<chip::Controller::OnOpenCommissioningWindow> mOnOpenCommissioningWindowCallback;
chip::Callback::Callback<chip::Controller::OnOpenBasicCommissioningWindow> mOnOpenBasicCommissioningWindowCallback;

jobject mJavaCallback;
jmethodID mOnSuccessMethod = nullptr;
jmethodID mOnErrorMethod = nullptr;
};

} // Namespace Controller
} // namespace chip
3 changes: 3 additions & 0 deletions src/controller/java/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ shared_library("jni") {
"AndroidCallbacks.h",
"AndroidClusterExceptions.cpp",
"AndroidClusterExceptions.h",
"AndroidCommissioningWindowOpener.cpp",
"AndroidCommissioningWindowOpener.h",
"AndroidDeviceControllerWrapper.cpp",
"AndroidDeviceControllerWrapper.h",
"AndroidOperationalCredentialsIssuer.cpp",
Expand Down Expand Up @@ -89,6 +91,7 @@ android_library("java") {
"src/chip/devicecontroller/GetConnectedDeviceCallbackJni.java",
"src/chip/devicecontroller/NetworkCredentials.java",
"src/chip/devicecontroller/NetworkLocation.java",
"src/chip/devicecontroller/OpenCommissioningCallback.java",
"src/chip/devicecontroller/PaseVerifierParams.java",
"src/chip/devicecontroller/ReportCallback.java",
"src/chip/devicecontroller/ReportCallbackJni.java",
Expand Down
Loading

0 comments on commit 1488156

Please sign in to comment.