diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java index bbfabb68b93ed4..258f400dcde5e1 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java @@ -5,6 +5,7 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; +import com.chip.casting.TvCastingApp; import com.chip.casting.dnssd.CommissionerDiscoveryListener; import com.chip.casting.util.GlobalCastingConstants; import java.util.concurrent.Executors; @@ -17,6 +18,8 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startCommissionerDiscovery(); + + testJni(); } private void startCommissionerDiscovery() { @@ -48,4 +51,11 @@ public void run() { 10, TimeUnit.SECONDS); } + + /** TBD: Temp dummy function for testing */ + private void testJni() { + TvCastingApp tvCastingApp = + new TvCastingApp((app, clusterId, endpoint) -> app.doSomethingInCpp(endpoint)); + tvCastingApp.doSomethingInCpp(0); + } } diff --git a/examples/tv-casting-app/android/java/src/com/chip/casting/TvCastingApp.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java similarity index 52% rename from examples/tv-casting-app/android/java/src/com/chip/casting/TvCastingApp.java rename to examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java index c66868db6cf7e5..0257c0d95f122a 100644 --- a/examples/tv-casting-app/android/java/src/com/chip/casting/TvCastingApp.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2022 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,28 @@ */ package com.chip.casting; +import android.util.Log; + public class TvCastingApp { - public TvCastingApp() {} + private TvCastingAppCallback mCallback; + private static final String TAG = TvCastingApp.class.getSimpleName(); + + public TvCastingApp(TvCastingAppCallback callback) { + mCallback = callback; + nativeInit(); + } + + private void postClusterInit(int clusterId, int endpoint) { + Log.d(TAG, "postClusterInit for " + clusterId + " at " + endpoint); + if (mCallback != null) { + mCallback.onClusterInit(this, clusterId, endpoint); + } + } + + public native void nativeInit(); + + /** TBD: Temp dummy function for testing */ + public native void doSomethingInCpp(int endpoint); static { System.loadLibrary("TvCastingApp"); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingAppCallback.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingAppCallback.java new file mode 100644 index 00000000000000..2ff10fc08a550a --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingAppCallback.java @@ -0,0 +1,21 @@ +/* + * + * Copyright (c) 2022 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. + */ +package com.chip.casting; + +public interface TvCastingAppCallback { + void onClusterInit(TvCastingApp app, int clusterId, int endpoint); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp new file mode 100644 index 00000000000000..c53e644f2f18a2 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp @@ -0,0 +1,86 @@ +/* + * 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 "TvCastingApp-JNI.h" +#include +#include +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_com_chip_casting_TvCastingApp_##METHOD_NAME + +TvCastingAppJNI TvCastingAppJNI::sInstance; + +void TvCastingAppJNI::InitializeWithObjects(jobject app) +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(AppServer, "Failed to GetEnvForCurrentThread for TvCastingAppJNI")); + + mTvCastingAppObject = env->NewGlobalRef(app); + VerifyOrReturn(mTvCastingAppObject != nullptr, ChipLogError(AppServer, "Failed to NewGlobalRef TvCastingAppJNI")); + + jclass managerClass = env->GetObjectClass(mTvCastingAppObject); + VerifyOrReturn(managerClass != nullptr, ChipLogError(AppServer, "Failed to get TvCastingAppJNI Java class")); + + mPostClusterInitMethod = env->GetMethodID(managerClass, "postClusterInit", "(II)V"); + if (mPostClusterInitMethod == nullptr) + { + ChipLogError(AppServer, "Failed to access ChannelManager 'postClusterInit' method"); + env->ExceptionClear(); + } +} + +void TvCastingAppJNI::PostClusterInit(chip::ClusterId clusterId, chip::EndpointId endpoint) +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, + ChipLogError(AppServer, "Failed to GetEnvForCurrentThread for TvCastingAppJNI::PostClusterInit")); + VerifyOrReturn(mTvCastingAppObject != nullptr, ChipLogError(AppServer, "TvCastingAppJNI::mTvCastingAppObject null")); + VerifyOrReturn(mPostClusterInitMethod != nullptr, ChipLogError(AppServer, "TvCastingAppJNI::mPostClusterInitMethod null")); + + env->CallVoidMethod(mTvCastingAppObject, mPostClusterInitMethod, static_cast(clusterId), static_cast(endpoint)); + if (env->ExceptionCheck()) + { + ChipLogError(AppServer, "Failed to call TvCastingAppJNI 'postClusterInit' method"); + env->ExceptionClear(); + } +} + +jint JNI_OnLoad(JavaVM * jvm, void * reserved) +{ + return AndroidAppServerJNI_OnLoad(jvm, reserved); +} + +void JNI_OnUnload(JavaVM * jvm, void * reserved) +{ + return AndroidAppServerJNI_OnUnload(jvm, reserved); +} + +JNI_METHOD(void, nativeInit)(JNIEnv *, jobject app) +{ + TvCastingAppJNIMgr().InitializeWithObjects(app); +} + +// TBD: Temp dummy function for testing +JNI_METHOD(void, doSomethingInCpp)(JNIEnv *, jobject, jint endpoint) +{ + ChipLogProgress(AppServer, "JNI_METHOD doSomethingInCpp called with endpoint %d", endpoint); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.h new file mode 100644 index 00000000000000..d1783efca1102c --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.h @@ -0,0 +1,41 @@ +/* + * + * 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 +#include + +class TvCastingAppJNI +{ +public: + void InitializeWithObjects(jobject app); + void PostClusterInit(chip::ClusterId clusterId, chip::EndpointId endpoint); + +private: + friend TvCastingAppJNI & TvCastingAppJNIMgr(); + + static TvCastingAppJNI sInstance; + jobject mTvCastingAppObject = nullptr; + jmethodID mPostClusterInitMethod = nullptr; +}; + +inline class TvCastingAppJNI & TvCastingAppJNIMgr() +{ + return TvCastingAppJNI::sInstance; +} diff --git a/examples/tv-casting-app/android/BUILD.gn b/examples/tv-casting-app/android/BUILD.gn index 5cfaccc7577b48..b3159bc3337420 100644 --- a/examples/tv-casting-app/android/BUILD.gn +++ b/examples/tv-casting-app/android/BUILD.gn @@ -22,7 +22,10 @@ import("${chip_root}/build/chip/tools.gni") shared_library("jni") { output_name = "libTvCastingApp" - sources = [ "${chip_root}/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h" ] + sources = [ + "${chip_root}/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h", + "App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp", + ] deps = [ "${chip_root}/examples/tv-casting-app/tv-casting-common", @@ -51,7 +54,10 @@ android_library("java") { "${chip_root}/build/chip/java:shared_cpplib", ] - sources = [ "java/src/com/chip/casting/TvCastingApp.java" ] + sources = [ + "App/app/src/main/jni/com/chip/casting/TvCastingApp.java", + "App/app/src/main/jni/com/chip/casting/TvCastingAppCallback.java", + ] javac_flags = [ "-Xlint:deprecation" ] diff --git a/examples/tv-casting-app/android/README.md b/examples/tv-casting-app/android/README.md new file mode 100644 index 00000000000000..5ac7c61af036ab --- /dev/null +++ b/examples/tv-casting-app/android/README.md @@ -0,0 +1,97 @@ +# Matter TV Casting Android App Example + +This is a Matter TV Casting Android app that can be used to cast content to a +TV. This app discovers TVs on the local network that act as commissioners, lets +the user select one, sends the TV a User Directed Commissioning request, enters +commissioning mode, advertises itself as a Commissionable Node and gets +commissioned. Then it allows the user to send Matter ContentLauncher commands to +the TV. + +
+ +- [Requirements for building](#requirements) + - [ABIs and TARGET_CPU](#abi) + - [Gradle & JDK Version](#jdk) +- [Preparing for build](#preparing) +- [Building & Installing the app](#building-installing) +- [Running the app on Android](#running-the-app-on-android) + +
+ + + +## Requirements for building + +You need Android SDK 21 & NDK downloaded to your machine. Set the +`$ANDROID_HOME` environment variable to where the SDK is downloaded and the +`$ANDROID_NDK_HOME` environment variable to point to where the NDK package is +downloaded. + + + +### ABIs and TARGET_CPU + +`TARGET_CPU` can have the following values, depending on your smartphone CPU +architecture: + +| ABI | TARGET_CPU | +| ----------- | ---------- | +| armeabi-v7a | arm | +| arm64-v8a | arm64 | +| x86 | x86 | +| x86_64 | x64 | + + + +### Gradle & JDK Version + +We are using Gradle 7.1.1 for all android project which does not support Java 17 +(https://docs.gradle.org/current/userguide/compatibility.html) while the default +JDK version on MacOS for Apple Silicon is 'openjdk 17.0.1' or above. + +Using JDK bundled with Android Studio will help with that. + +```shell +export JAVA_HOME=/Applications/Android\ Studio.app/Contents/jre/Contents/Home/ +``` + +
+ + + +## Preparing for build + +Complete the following steps to prepare the Matter build: + +1. Check out the Matter repository. + +2. Run bootstrap (**only required first time**) + + ```shell + source scripts/bootstrap.sh + ``` + + + +## Building & Installing the app + +This is the simplest option. In the command line, run the following command from +the top Matter directory: + +```shell +./scripts/build/build_examples.py --target android-arm64-chip-tv-casting-app build +``` + +See the table above for other values of `TARGET_CPU`. + +The debug Android package `app-debug.apk` will be generated at +`out/android-$TARGET_CPU-chip-tv-casting-app/outputs/apk/debug/`, and can be +installed with + +```shell +adb install out/android-$TARGET_CPU-chip-tv-casting-app/outputs/apk/debug/app-debug.apk +``` + +You can use Android Studio to edit the Android app itself and run it after +build_examples.py, but you will not be able to edit Matter Android code from +`src/controller/java`, or other Matter C++ code within Android Studio. diff --git a/scripts/build/builders/android.py b/scripts/build/builders/android.py index 2e2dbd29dcc027..d9cc9f156c53a4 100644 --- a/scripts/build/builders/android.py +++ b/scripts/build/builders/android.py @@ -183,7 +183,7 @@ def copyToExampleAndroid(self): title='Prepare Native libs ' + self.identifier) if self.app.ExampleName() == 'tv-casting-app': - libs = ['libc++_shared.so'] + libs = ['libc++_shared.so', 'libTvCastingApp.so'] else: libs = ['libSetupPayloadParser.so', 'libc++_shared.so', 'libTvApp.so'] @@ -194,6 +194,7 @@ def copyToExampleAndroid(self): if self.app.ExampleName() == 'tv-casting-app': jars = { 'AndroidPlatform.jar': 'third_party/connectedhomeip/src/platform/android/AndroidPlatform.jar', + 'CHIPAppServer.jar': 'third_party/connectedhomeip/src/app/server/java/CHIPAppServer.jar', 'TvCastingApp.jar': 'TvCastingApp.jar', } else: diff --git a/scripts/build/testdata/build_all_except_host.txt b/scripts/build/testdata/build_all_except_host.txt index 13c0648b3cb5da..18a9aaed8ef4e4 100644 --- a/scripts/build/testdata/build_all_except_host.txt +++ b/scripts/build/testdata/build_all_except_host.txt @@ -725,8 +725,12 @@ mkdir -p {root}/examples/tv-casting-app/android/App/app/libs/jniLibs/armeabi-v7a cp {out}/android-arm-chip-tv-casting-app/lib/jni/armeabi-v7a/libc++_shared.so {root}/examples/tv-casting-app/android/App/app/libs/jniLibs/armeabi-v7a/libc++_shared.so +cp {out}/android-arm-chip-tv-casting-app/lib/jni/armeabi-v7a/libTvCastingApp.so {root}/examples/tv-casting-app/android/App/app/libs/jniLibs/armeabi-v7a/libTvCastingApp.so + cp {out}/android-arm-chip-tv-casting-app/lib/third_party/connectedhomeip/src/platform/android/AndroidPlatform.jar {root}/examples/tv-casting-app/android/App/app/libs/AndroidPlatform.jar +cp {out}/android-arm-chip-tv-casting-app/lib/third_party/connectedhomeip/src/app/server/java/CHIPAppServer.jar {root}/examples/tv-casting-app/android/App/app/libs/CHIPAppServer.jar + cp {out}/android-arm-chip-tv-casting-app/lib/TvCastingApp.jar {root}/examples/tv-casting-app/android/App/app/libs/TvCastingApp.jar # Building Example android-arm-chip-tv-casting-app @@ -805,8 +809,12 @@ mkdir -p {root}/examples/tv-casting-app/android/App/app/libs/jniLibs/arm64-v8a cp {out}/android-arm64-chip-tv-casting-app/lib/jni/arm64-v8a/libc++_shared.so {root}/examples/tv-casting-app/android/App/app/libs/jniLibs/arm64-v8a/libc++_shared.so +cp {out}/android-arm64-chip-tv-casting-app/lib/jni/arm64-v8a/libTvCastingApp.so {root}/examples/tv-casting-app/android/App/app/libs/jniLibs/arm64-v8a/libTvCastingApp.so + cp {out}/android-arm64-chip-tv-casting-app/lib/third_party/connectedhomeip/src/platform/android/AndroidPlatform.jar {root}/examples/tv-casting-app/android/App/app/libs/AndroidPlatform.jar +cp {out}/android-arm64-chip-tv-casting-app/lib/third_party/connectedhomeip/src/app/server/java/CHIPAppServer.jar {root}/examples/tv-casting-app/android/App/app/libs/CHIPAppServer.jar + cp {out}/android-arm64-chip-tv-casting-app/lib/TvCastingApp.jar {root}/examples/tv-casting-app/android/App/app/libs/TvCastingApp.jar # Building Example android-arm64-chip-tv-casting-app