Skip to content

Commit

Permalink
tv-casting-app/android: Open commissioning window through JNI in a ne…
Browse files Browse the repository at this point in the history
…w fragment (#17655)
  • Loading branch information
sharadb-amazon authored and pull[bot] committed Sep 7, 2023
1 parent 3d19ea8 commit 5596357
Show file tree
Hide file tree
Showing 15 changed files with 618 additions and 52 deletions.
1 change: 1 addition & 0 deletions examples/tv-casting-app/android/App/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.+'
testImplementation 'org.mockito:mockito-core:3.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.chip.casting.app;

import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.chip.casting.dnssd.CommissionerDiscoveryListener;
import com.chip.casting.util.GlobalCastingConstants;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/** A {@link Fragment} to discover commissioners on the network */
public class CommissionerDiscoveryFragment extends Fragment {

public CommissionerDiscoveryFragment() {}

/**
* Use this factory method to create a new instance of this fragment using the provided
* parameters.
*
* @return A new instance of fragment CommissionerDiscoveryFragment.
*/
public static CommissionerDiscoveryFragment newInstance() {
return new CommissionerDiscoveryFragment();
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startCommissionerDiscovery();
}

@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_commissioner_discovery, container, false);
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Button manualCommissioningButton = getView().findViewById(R.id.manualCommissioningButton);
Callback callback = (Callback) this.getActivity();
manualCommissioningButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
callback.handleManualCommissioningButtonClicked();
}
});
}

private void startCommissionerDiscovery() {
WifiManager wifi = (WifiManager) this.getContext().getSystemService(Context.WIFI_SERVICE);
WifiManager.MulticastLock multicastLock = wifi.createMulticastLock("multicastLock");
multicastLock.setReferenceCounted(true);
multicastLock.acquire();

CastingContext castingContext = new CastingContext(this.getActivity());
NsdManager.DiscoveryListener commissionerDiscoveryListener =
new CommissionerDiscoveryListener(castingContext);

NsdManager nsdManager = castingContext.getNsdManager();
nsdManager.discoverServices(
GlobalCastingConstants.CommissionerServiceType,
NsdManager.PROTOCOL_DNS_SD,
commissionerDiscoveryListener);

// Stop discovery after specified timeout
Executors.newSingleThreadScheduledExecutor()
.schedule(
new Runnable() {
@Override
public void run() {
nsdManager.stopServiceDiscovery(commissionerDiscoveryListener);
multicastLock.release();
}
},
10,
TimeUnit.SECONDS);
}

/** Interface for notifying the host. */
interface Callback {
/** Notifies listener of Skip to manual Commissioning Button click. */
void handleManualCommissioningButtonClicked();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.chip.casting.app;

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.chip.casting.TvCastingApp;
import com.chip.casting.dnssd.CommissionerDiscoveryListener;
import com.chip.casting.util.GlobalCastingConstants;

/** A {@link Fragment} to get the TV Casting App commissioned. */
public class CommissioningFragment extends Fragment {
private static final String TAG = CommissionerDiscoveryListener.class.getSimpleName();

private final TvCastingApp tvCastingApp;

public CommissioningFragment(TvCastingApp tvCastingApp) {
this.tvCastingApp = tvCastingApp;
}

/**
* Use this factory method to create a new instance of this fragment using the provided
* parameters.
*
* @param tvCastingApp TV Casting App (JNI)
* @return A new instance of fragment CommissioningFragment.
*/
// TODO: Rename and change types and number of parameters
public static CommissioningFragment newInstance(TvCastingApp tvCastingApp) {
return new CommissioningFragment(tvCastingApp);
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_commissioning, container, false);
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
boolean status =
tvCastingApp.openBasicCommissioningWindow(
GlobalCastingConstants.CommissioningWindowDurationSecs);
Log.d(
TAG,
"CommissioningFragment.onViewCreated tvCastingApp.openBasicCommissioningWindow returned "
+ status);
TextView commissioningWindowStatusView = getView().findViewById(R.id.commissioningWindowStatus);
TextView onboardingPayloadView = getView().findViewById(R.id.onboardingPayload);
if (status == true) {
commissioningWindowStatusView.setText("Commissioning window opened!");
onboardingPayloadView.setText("Onboarding Pin: " + GlobalCastingConstants.SetupPasscode);
} else {
commissioningWindowStatusView.setText("Commissioning window could not be opened!");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,61 +1,84 @@
package com.chip.casting.app;

import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import chip.appserver.ChipAppServer;
import chip.platform.AndroidBleManager;
import chip.platform.AndroidChipPlatform;
import chip.platform.ChipMdnsCallbackImpl;
import chip.platform.DiagnosticDataProviderImpl;
import chip.platform.NsdManagerServiceResolver;
import chip.platform.PreferencesConfigurationManager;
import chip.platform.PreferencesKeyValueStoreManager;
import com.chip.casting.DACProviderStub;
import com.chip.casting.TvCastingApp;
import com.chip.casting.dnssd.CommissionerDiscoveryListener;
import com.chip.casting.util.GlobalCastingConstants;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {
public class MainActivity extends AppCompatActivity
implements CommissionerDiscoveryFragment.Callback {

private ChipAppServer chipAppServer;
private TvCastingApp tvCastingApp;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startCommissionerDiscovery();

testJni();
initJni();

Fragment fragment = CommissionerDiscoveryFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.main_fragment_container, fragment, fragment.getClass().getSimpleName())
.commit();
}

@Override
public void handleManualCommissioningButtonClicked() {
showFragment(CommissioningFragment.newInstance(tvCastingApp));
}

private void startCommissionerDiscovery() {
WifiManager wifi = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiManager.MulticastLock multicastLock = wifi.createMulticastLock("multicastLock");
multicastLock.setReferenceCounted(true);
multicastLock.acquire();

CastingContext castingContext = new CastingContext(this);
NsdManager.DiscoveryListener commissionerDiscoveryListener =
new CommissionerDiscoveryListener(castingContext);

NsdManager nsdManager = castingContext.getNsdManager();
nsdManager.discoverServices(
GlobalCastingConstants.CommissionerServiceType,
NsdManager.PROTOCOL_DNS_SD,
commissionerDiscoveryListener);

// Stop discovery after specified timeout
Executors.newSingleThreadScheduledExecutor()
.schedule(
new Runnable() {
@Override
public void run() {
nsdManager.stopServiceDiscovery(commissionerDiscoveryListener);
multicastLock.release();
}
},
10,
TimeUnit.SECONDS);
private void initJni() {
tvCastingApp =
new TvCastingApp((app, clusterId, duration) -> app.openBasicCommissioningWindow(duration));

tvCastingApp.setDACProvider(new DACProviderStub());
Context applicationContext = this.getApplicationContext();
AndroidChipPlatform chipPlatform =
new AndroidChipPlatform(
new AndroidBleManager(),
new PreferencesKeyValueStoreManager(applicationContext),
new PreferencesConfigurationManager(applicationContext),
new NsdManagerServiceResolver(applicationContext),
new ChipMdnsCallbackImpl(),
new DiagnosticDataProviderImpl(applicationContext));

chipPlatform.updateCommissionableDataProviderData(
null, null, 0, GlobalCastingConstants.SetupPasscode, GlobalCastingConstants.Discriminator);

chipAppServer = new ChipAppServer();
chipAppServer.startApp();
}

private void showFragment(Fragment fragment, boolean showOnBack) {
System.out.println(
"showFragment called with " + fragment.getClass().getSimpleName() + " and " + showOnBack);
FragmentTransaction fragmentTransaction =
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment_container, fragment, fragment.getClass().getSimpleName());
if (showOnBack) {
fragmentTransaction.addToBackStack(null);
}

fragmentTransaction.commit();
}

/** TBD: Temp dummy function for testing */
private void testJni() {
TvCastingApp tvCastingApp =
new TvCastingApp((app, clusterId, endpoint) -> app.doSomethingInCpp(endpoint));
tvCastingApp.doSomethingInCpp(0);
private void showFragment(Fragment fragment) {
showFragment(fragment, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@

public class GlobalCastingConstants {
public static final String CommissionerServiceType = "_matterd._udp.";
public static final int CommissioningWindowDurationSecs = 3 * 60;
public static int SetupPasscode = 20202021;
public static int Discriminator = 0xF00;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2021 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.chip.casting;

public interface DACProvider {
byte[] GetCertificationDeclaration();

byte[] GetFirmwareInformation();

byte[] GetDeviceAttestationCert();

byte[] GetProductAttestationIntermediateCert();

byte[] GetDeviceAttestationCertPrivateKey();

byte[] GetDeviceAttestationCertPublicKeyKey();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.chip.casting;

import android.util.Base64;

public class DACProviderStub implements DACProvider {

private String kDevelopmentDAC_Cert_FFF1_8001 =
"MIIB5zCCAY6gAwIBAgIIac3xDenlTtEwCgYIKoZIzj0EAwIwPTElMCMGA1UEAwwcTWF0dGVyIERldiBQQUkgMHhGRkYxIG5vIFBJRDEUMBIGCisGAQQBgqJ8AgEMBEZGRjEwIBcNMjIwMjA1MDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMFMxJTAjBgNVBAMMHE1hdHRlciBEZXYgREFDIDB4RkZGMS8weDgwMDExFDASBgorBgEEAYKifAIBDARGRkYxMRQwEgYKKwYBBAGConwCAgwEODAwMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEY6xpNCkQoOVYj8b/Vrtj5i7M7LFI99TrA+5VJgFBV2fRalxmP3k+SRIyYLgpenzX58/HsxaznZjpDSk3dzjoKjYDBeMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSI3eezADgpMs/3NMBGJIEPRBaKbzAfBgNVHSMEGDAWgBRjVA5H9kscONE4hKRi0WwZXY/7PDAKBggqhkjOPQQDAgNHADBEAiABJ6J7S0RhDuL83E0reIVWNmC8D3bxchntagjfsrPBzQIga1ngr0Xz6yqFuRnTVzFSjGAoxBUjlUXhCOTlTnCXE1M=";

private String kDevelopmentDAC_PrivateKey_FFF1_8001 =
"qrYAroroqrfXNifCF7fCBHCcppRq9fL3UwgzpStE+/8=";

private String kDevelopmentDAC_PublicKey_FFF1_8001 =
"BEY6xpNCkQoOVYj8b/Vrtj5i7M7LFI99TrA+5VJgFBV2fRalxmP3k+SRIyYLgpenzX58/HsxaznZjpDSk3dzjoI=";

private String KPAI_FFF1_8000_Cert_Array =
"MIIByzCCAXGgAwIBAgIIVq2CIq2UW2QwCgYIKoZIzj0EAwIwMDEYMBYGA1UEAwwPTWF0dGVyIFRlc3QgUEFBMRQwEgYKKwYBBAGConwCAQwERkZGMTAgFw0yMjAyMDUwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowPTElMCMGA1UEAwwcTWF0dGVyIERldiBQQUkgMHhGRkYxIG5vIFBJRDEUMBIGCisGAQQBgqJ8AgEMBEZGRjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARBmpMVwhc+DIyHbQPM/JRIUmR/f+xeUIL0BZko7KiUxZQVEwmsYx5MsDOSr2hLC6+35ls7gWLC9Sv5MbjneqqCo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUY1QOR/ZLHDjROISkYtFsGV2P+zwwHwYDVR0jBBgwFoAUav0idx9RH+y/FkGXZxDc3DGhcX4wCgYIKoZIzj0EAwIDSAAwRQIhALLvJ/Sa6bUPuR7qyUxNC9u415KcbLiPrOUpNo0SBUwMAiBlXckrhr2QmIKmxiF3uCXX0F7b58Ivn+pxIg5+pwP4kQ==";

/**
* format_version = 1 vendor_id = 0xFFF1 product_id_array = [ 0x8000,0x8001...0x8063]
* device_type_id = 0x1234 certificate_id = "ZIG20141ZB330001-24" security_level = 0
* security_information = 0 version_number = 0x2694 certification_type = 0 dac_origin_vendor_id is
* not present dac_origin_product_id is not present
*/
private String kCertificationDeclaration =
"MIICGQYJKoZIhvcNAQcCoIICCjCCAgYCAQMxDTALBglghkgBZQMEAgEwggFxBgkqhkiG9w0BBwGgggFiBIIBXhUkAAElAfH/NgIFAIAFAYAFAoAFA4AFBIAFBYAFBoAFB4AFCIAFCYAFCoAFC4AFDIAFDYAFDoAFD4AFEIAFEYAFEoAFE4AFFIAFFYAFFoAFF4AFGIAFGYAFGoAFG4AFHIAFHYAFHoAFH4AFIIAFIYAFIoAFI4AFJIAFJYAFJoAFJ4AFKIAFKYAFKoAFK4AFLIAFLYAFLoAFL4AFMIAFMYAFMoAFM4AFNIAFNYAFNoAFN4AFOIAFOYAFOoAFO4AFPIAFPYAFPoAFP4AFQIAFQYAFQoAFQ4AFRIAFRYAFRoAFR4AFSIAFSYAFSoAFS4AFTIAFTYAFToAFT4AFUIAFUYAFUoAFU4AFVIAFVYAFVoAFV4AFWIAFWYAFWoAFW4AFXIAFXYAFXoAFX4AFYIAFYYAFYoAFY4AYJAMWLAQTWklHMjAxNDJaQjMzMDAwMy0yNCQFACQGACUHlCYkCAAYMX0wewIBA4AUYvqCM1ms+qmWPhz6FArd9QTzcWAwCwYJYIZIAWUDBAIBMAoGCCqGSM49BAMCBEcwRQIgJOXR9Hp9ew0gaibvaZt8l1e3LUaQid4xkuZ4x0Xn9gwCIQD4qi+nEfy3m5fjl87aZnuuRk4r0//fw8zteqjKX0wafA==";

@Override
public byte[] GetCertificationDeclaration() {
return Base64.decode(kCertificationDeclaration, Base64.DEFAULT);
}

@Override
public byte[] GetFirmwareInformation() {
return new byte[0];
}

@Override
public byte[] GetDeviceAttestationCert() {
return Base64.decode(kDevelopmentDAC_Cert_FFF1_8001, Base64.DEFAULT);
}

@Override
public byte[] GetProductAttestationIntermediateCert() {
return Base64.decode(KPAI_FFF1_8000_Cert_Array, Base64.DEFAULT);
}

@Override
public byte[] GetDeviceAttestationCertPrivateKey() {
return Base64.decode(kDevelopmentDAC_PrivateKey_FFF1_8001, Base64.DEFAULT);
}

@Override
public byte[] GetDeviceAttestationCertPublicKeyKey() {
return Base64.decode(kDevelopmentDAC_PublicKey_FFF1_8001, Base64.DEFAULT);
}
}
Loading

0 comments on commit 5596357

Please sign in to comment.