Skip to content

Commit

Permalink
[Android] Use CASE for control (project-chip#8447)
Browse files Browse the repository at this point in the history
* Use CASE in Android controller

* Add GetConnectedDeviceCallback struct and corresponding Java interface/wrapper class.
  • Loading branch information
austinh0 authored and pull[bot] committed Sep 3, 2021
1 parent 062fcf7 commit 25df383
Show file tree
Hide file tree
Showing 12 changed files with 335 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@
*/
package com.google.chip.chiptool

import android.util.Log
import chip.devicecontroller.ChipDeviceController
import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback
import java.lang.IllegalStateException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

/** Lazily instantiates [ChipDeviceController] and holds a reference to it. */
object ChipClient {

private const val TAG = "ChipClient"
private lateinit var chipDeviceController: ChipDeviceController

fun getDeviceController(): ChipDeviceController {
Expand All @@ -30,4 +36,26 @@ object ChipClient {
}
return chipDeviceController
}

/**
* Wrapper around [ChipDeviceController.getConnectedDevicePointer] to return the value directly.
*/
suspend fun getConnectedDevicePointer(nodeId: Long): Long {
return suspendCoroutine { continuation ->
getDeviceController().getConnectedDevicePointer(
nodeId,
object : GetConnectedDeviceCallback {
override fun onDeviceConnected(devicePointer: Long) {
Log.d(TAG, "Got connected device pointer")
continuation.resume(devicePointer)
}

override fun onConnectionFailure(nodeId: Long, error: Exception) {
val errorMessage = "Unable to get connected device with nodeId $nodeId"
Log.e(TAG, errorMessage, error)
continuation.resumeWithException(IllegalStateException(errorMessage))
}
})
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ open class GenericChipDeviceListener : ChipDeviceController.CompletionListener {
// No op
}

override fun onCommissioningComplete(nodeId: Long, errorCode: Int) {
// No op
}

override fun onNetworkCommissioningComplete(code: Int) {
// No op
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,18 @@ import kotlinx.android.synthetic.main.on_off_client_fragment.view.onBtn
import kotlinx.android.synthetic.main.on_off_client_fragment.view.readBtn
import kotlinx.android.synthetic.main.on_off_client_fragment.view.toggleBtn
import kotlinx.android.synthetic.main.on_off_client_fragment.view.updateAddressBtn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch

class OnOffClientFragment : Fragment() {
private val deviceController: ChipDeviceController
get() = ChipClient.getDeviceController()

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

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -40,9 +47,10 @@ class OnOffClientFragment : Fragment() {
deviceController.setCompletionListener(ChipControllerCallback())

updateAddressBtn.setOnClickListener { updateAddressClick() }
onBtn.setOnClickListener { sendOnCommandClick() }
offBtn.setOnClickListener { sendOffCommandClick() }
toggleBtn.setOnClickListener { sendToggleCommandClick() }
onBtn.setOnClickListener { scope.launch { sendOnCommandClick() } }
offBtn.setOnClickListener { scope.launch { sendOffCommandClick() } }
toggleBtn.setOnClickListener { scope.launch { sendToggleCommandClick() } }
readBtn.setOnClickListener { scope.launch { sendReadOnOffClick() } }

levelBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
Expand All @@ -58,14 +66,13 @@ class OnOffClientFragment : Fragment() {
"Level is: " + levelBar.progress,
Toast.LENGTH_SHORT
).show()
sendLevelCommandClick()
scope.launch { sendLevelCommandClick() }
}
})
readBtn.setOnClickListener { sendReadOnOffClick() }
}
}

private fun sendReadOnOffClick() {
private suspend fun sendReadOnOffClick() {
getOnOffClusterForDevice().readOnOffAttribute(object : ChipClusters.BooleanAttributeCallback {
override fun onSuccess(on: Boolean) {
Log.v(TAG, "On/Off attribute value: $on")
Expand All @@ -89,6 +96,10 @@ class OnOffClientFragment : Fragment() {
inner class ChipControllerCallback : GenericChipDeviceListener() {
override fun onConnectDeviceComplete() {}

override fun onCommissioningComplete(nodeId: Long, errorCode: Int) {
Log.d(TAG, "onCommissioningComplete for nodeId $nodeId: $errorCode")
}

override fun onSendMessageComplete(message: String?) {
commandStatusTv.text = requireContext().getString(R.string.echo_status_response, message)
}
Expand All @@ -106,6 +117,11 @@ class OnOffClientFragment : Fragment() {
}
}

override fun onStop() {
super.onStop()
scope.cancel()
}

private fun updateAddressClick() {
try{
deviceController.updateDevice(
Expand All @@ -118,10 +134,9 @@ class OnOffClientFragment : Fragment() {
}
}

private fun sendLevelCommandClick() {
private suspend fun sendLevelCommandClick() {
val cluster = ChipClusters.LevelControlCluster(
ChipClient.getDeviceController()
.getDevicePointer(deviceIdEd.text.toString().toLong()), 1
ChipClient.getConnectedDevicePointer(deviceIdEd.text.toString().toLong()), 1
)
cluster.moveToLevel(object : ChipClusters.DefaultClusterCallback {
override fun onSuccess() {
Expand All @@ -136,7 +151,7 @@ class OnOffClientFragment : Fragment() {
}, levelBar.progress, 0, 0, 0)
}

private fun sendOnCommandClick() {
private suspend fun sendOnCommandClick() {
getOnOffClusterForDevice().on(object : ChipClusters.DefaultClusterCallback {
override fun onSuccess() {
showMessage("ON command success")
Expand All @@ -150,7 +165,7 @@ class OnOffClientFragment : Fragment() {
})
}

private fun sendOffCommandClick() {
private suspend fun sendOffCommandClick() {
getOnOffClusterForDevice().off(object : ChipClusters.DefaultClusterCallback {
override fun onSuccess() {
showMessage("OFF command success")
Expand All @@ -163,7 +178,7 @@ class OnOffClientFragment : Fragment() {
})
}

private fun sendToggleCommandClick() {
private suspend fun sendToggleCommandClick() {
getOnOffClusterForDevice().toggle(object : ChipClusters.DefaultClusterCallback {
override fun onSuccess() {
showMessage("TOGGLE command success")
Expand All @@ -176,10 +191,9 @@ class OnOffClientFragment : Fragment() {
})
}

private fun getOnOffClusterForDevice(): OnOffCluster {
private suspend fun getOnOffClusterForDevice(): OnOffCluster {
return OnOffCluster(
ChipClient.getDeviceController()
.getDevicePointer(deviceIdEd.text.toString().toLong()), 1
ChipClient.getConnectedDevicePointer(deviceIdEd.text.toString().toLong()), 1
)
}

Expand Down
39 changes: 39 additions & 0 deletions src/controller/java/AndroidCallbacks-JNI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* 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 "AndroidCallbacks.h"

#include <jni.h>
#include <support/CodeUtils.h>
#include <support/logging/CHIPLogging.h>

#define JNI_METHOD(RETURN, CLASS_NAME, METHOD_NAME) \
extern "C" JNIEXPORT RETURN JNICALL Java_chip_devicecontroller_##CLASS_NAME##_##METHOD_NAME

using namespace chip::Controller;

JNI_METHOD(jlong, GetConnectedDeviceCallbackJni, newCallback)(JNIEnv * env, jobject self, jobject callback)
{
GetConnectedDeviceCallback * connectedDeviceCallback = new GetConnectedDeviceCallback(callback);
return reinterpret_cast<jlong>(connectedDeviceCallback);
}

JNI_METHOD(void, GetConnectedDeviceCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle)
{
GetConnectedDeviceCallback * connectedDeviceCallback = reinterpret_cast<GetConnectedDeviceCallback *>(callbackHandle);
VerifyOrReturn(connectedDeviceCallback != nullptr, ChipLogError(Controller, "GetConnectedDeviceCallback handle is nullptr"));
delete connectedDeviceCallback;
}
96 changes: 96 additions & 0 deletions src/controller/java/AndroidCallbacks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* 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 "AndroidCallbacks.h"
#include "JniReferences.h"
#include "JniTypeWrappers.h"

#include <jni.h>
#include <support/CodeUtils.h>
#include <support/ErrorStr.h>
#include <support/logging/CHIPLogging.h>

using namespace chip::Controller;

GetConnectedDeviceCallback::GetConnectedDeviceCallback(jobject javaCallback) :
mOnSuccess(OnDeviceConnectedFn, this), mOnFailure(OnDeviceConnectionFailureFn, this)
{
JNIEnv * env = chip::Controller::JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread"));
mJavaCallbackRef = env->NewGlobalRef(javaCallback);
if (mJavaCallbackRef == nullptr)
{
ChipLogError(Controller, "Could not create global reference for Java callback");
}
}

GetConnectedDeviceCallback::~GetConnectedDeviceCallback()
{
JNIEnv * env = chip::Controller::JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread"));
env->DeleteGlobalRef(mJavaCallbackRef);
}

void GetConnectedDeviceCallback::OnDeviceConnectedFn(void * context, Device * device)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
auto * self = static_cast<GetConnectedDeviceCallback *>(context);
jobject javaCallback = self->mJavaCallbackRef;

jclass getConnectedDeviceCallbackCls = nullptr;
JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/GetConnectedDeviceCallbackJni$GetConnectedDeviceCallback",
getConnectedDeviceCallbackCls);
VerifyOrReturn(getConnectedDeviceCallbackCls != nullptr,
ChipLogError(Controller, "Could not find GetConnectedDeviceCallback class"));
JniClass getConnectedDeviceCallbackJniCls(getConnectedDeviceCallbackCls);

jmethodID successMethod;
JniReferences::GetInstance().FindMethod(env, javaCallback, "onDeviceConnected", "(J)V", &successMethod);
VerifyOrReturn(successMethod != nullptr, ChipLogError(Controller, "Could not find onDeviceConnected method"));

static_assert(sizeof(jlong) >= sizeof(void *), "Need to store a pointer in a Java handle");
env->CallVoidMethod(javaCallback, successMethod, reinterpret_cast<jlong>(device));
}

void GetConnectedDeviceCallback::OnDeviceConnectionFailureFn(void * context, NodeId nodeId, CHIP_ERROR error)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
auto * self = static_cast<GetConnectedDeviceCallback *>(context);
jobject javaCallback = self->mJavaCallbackRef;

jclass getConnectedDeviceCallbackCls = nullptr;
JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/GetConnectedDeviceCallbackJni$GetConnectedDeviceCallback",
getConnectedDeviceCallbackCls);
VerifyOrReturn(getConnectedDeviceCallbackCls != nullptr,
ChipLogError(Controller, "Could not find GetConnectedDeviceCallback class"));
JniClass getConnectedDeviceCallbackJniCls(getConnectedDeviceCallbackCls);

jmethodID failureMethod;
JniReferences::GetInstance().FindMethod(env, javaCallback, "onConnectionFailure", "(JLjava/lang/Exception;)V", &failureMethod);
VerifyOrReturn(failureMethod != nullptr, ChipLogError(Controller, "Could not find onConnectionFailure method"));

// Create the exception to return.
jclass controllerExceptionCls;
CHIP_ERROR err = JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/ChipDeviceControllerException",
controllerExceptionCls);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find exception type for onConnectionFailure"));
JniClass controllerExceptionJniCls(controllerExceptionCls);

jmethodID exceptionConstructor = env->GetMethodID(controllerExceptionCls, "<init>", "(ILjava/lang/String;)V");
jobject exception = env->NewObject(controllerExceptionCls, exceptionConstructor, error, env->NewStringUTF(ErrorStr(error)));

env->CallVoidMethod(javaCallback, failureMethod, nodeId, exception);
}
40 changes: 40 additions & 0 deletions src/controller/java/AndroidCallbacks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* 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/CHIPDeviceController.h>
#include <jni.h>

namespace chip {
namespace Controller {

// Callback for success and failure cases of GetConnectedDevice().
struct GetConnectedDeviceCallback
{
GetConnectedDeviceCallback(jobject javaCallback);
~GetConnectedDeviceCallback();

static void OnDeviceConnectedFn(void * context, Device * device);
static void OnDeviceConnectionFailureFn(void * context, NodeId nodeId, CHIP_ERROR error);

Callback::Callback<OnDeviceConnected> mOnSuccess;
Callback::Callback<OnDeviceConnectionFailure> mOnFailure;
jobject mJavaCallbackRef;
};

} // namespace Controller
} // namespace chip
11 changes: 11 additions & 0 deletions src/controller/java/AndroidDeviceControllerWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ void AndroidDeviceControllerWrapper::OnPairingDeleted(CHIP_ERROR error)
CallJavaMethod("onPairingDeleted", static_cast<jint>(error));
}

void AndroidDeviceControllerWrapper::OnCommissioningComplete(NodeId deviceId, CHIP_ERROR error)
{
StackUnlockGuard unlockGuard(mStackLock);
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
jmethodID onCommissioningCompleteMethod;
CHIP_ERROR err = JniReferences::GetInstance().FindMethod(env, mJavaObjectRef, "onCommissioningComplete", "(JI)V",
&onCommissioningCompleteMethod);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Error finding Java method: %d", err));
env->CallVoidMethod(mJavaObjectRef, onCommissioningCompleteMethod, static_cast<jlong>(deviceId), error);
}

// TODO Refactor this API to match latest spec, so that GenerateNodeOperationalCertificate receives the full CSR Elements data
// payload.
CHIP_ERROR
Expand Down
Loading

0 comments on commit 25df383

Please sign in to comment.