diff --git a/docs/examples/index.md b/docs/examples/index.md
index 0ecda80b9d6a6b..e7b6224bdda1a1 100644
--- a/docs/examples/index.md
+++ b/docs/examples/index.md
@@ -78,6 +78,15 @@ darwin-framework-tool/README
java-matter-controller/README
```
+## Virtual Device App example
+
+```{toctree}
+:glob:
+:maxdepth: 1
+
+virtual-device-app/**/README
+```
+
## Lighting example
```{toctree}
diff --git a/examples/virtual-device-app/android/.gn b/examples/virtual-device-app/android/.gn
new file mode 100644
index 00000000000000..747b125d35d3c2
--- /dev/null
+++ b/examples/virtual-device-app/android/.gn
@@ -0,0 +1,25 @@
+# Copyright (c) 2023 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.
+
+import("//build_overrides/build.gni")
+
+# The location of the build configuration file.
+buildconfig = "${build_root}/config/BUILDCONFIG.gn"
+
+# CHIP uses angle bracket includes.
+check_system_includes = true
+
+default_args = {
+ import("//args.gni")
+}
diff --git a/examples/virtual-device-app/android/App/.gitignore b/examples/virtual-device-app/android/App/.gitignore
new file mode 100644
index 00000000000000..c9ad62ce32cf40
--- /dev/null
+++ b/examples/virtual-device-app/android/App/.gitignore
@@ -0,0 +1,26 @@
+*.iml
+.gradle
+/local.properties
+/.idea/misc.xml
+/.idea/gradle.xml
+/.idea/compiler.xml
+/.idea/.name
+/.idea/jarRepositories.xml
+/.idea/vcs.xml
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.idea/**
+.DS_Store
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+
+# Shared libs & JAR libs (those libs are copied into source tree for easy Android build).
+*.so
+*.jar
+*.map
diff --git a/examples/virtual-device-app/android/App/app/.gitignore b/examples/virtual-device-app/android/App/app/.gitignore
new file mode 100644
index 00000000000000..757fee31c9045c
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/.gitignore
@@ -0,0 +1 @@
+/.idea
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/build.gradle.kts b/examples/virtual-device-app/android/App/app/build.gradle.kts
new file mode 100644
index 00000000000000..3b139f2b61b6ad
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/build.gradle.kts
@@ -0,0 +1,98 @@
+import com.matter.buildsrc.Deps
+import com.matter.buildsrc.Versions
+
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ id("dagger.hilt.android.plugin")
+ id("androidx.navigation.safeargs.kotlin")
+ kotlin("kapt")
+}
+
+android {
+ namespace = "com.matter.virtual.device.app"
+ compileSdk = Versions.compileSdkVersion
+ buildToolsVersion = Versions.buildToolsVersion
+
+ defaultConfig {
+ applicationId = "com.matter.virtual.device.app"
+ minSdk = Versions.minSdkVersion
+ targetSdk = Versions.targetSdkVersion
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ externalNativeBuild {
+ cmake {
+ targets += listOf("default")
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+
+ debug {
+ packagingOptions {
+ jniLibs.keepDebugSymbols.add("**/*.so")
+ }
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ buildFeatures {
+ viewBinding = true
+ dataBinding = true
+ }
+ kapt {
+ correctErrorTypes = true
+ }
+ sourceSets {
+ getByName("main") {
+ jniLibs.setSrcDirs(listOf("libs/jniLibs"))
+ }
+ }
+ packagingOptions {
+ jniLibs.pickFirsts.add("**/*.so")
+ jniLibs.useLegacyPackaging = true
+ }
+}
+
+dependencies {
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
+
+ implementation(project(":core:common"))
+ implementation(project(":core:data"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:model"))
+ implementation(project(":feature:main"))
+ implementation(project(":feature:qrcode"))
+ implementation(project(":feature:setup"))
+
+ implementation(Deps.AndroidX.core)
+ implementation(Deps.AndroidX.appcompat)
+ implementation(Deps.material)
+
+ implementation(Deps.Navigation.fragment)
+ implementation(Deps.Navigation.ui)
+
+ implementation(Deps.Dagger.hiltAndroid)
+ kapt(Deps.Dagger.hiltAndroidCompiler)
+
+ implementation(Deps.timber)
+
+ testImplementation(Deps.Test.junit)
+ androidTestImplementation(Deps.Test.junitExt)
+ androidTestImplementation(Deps.Test.espresso)
+}
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/proguard-rules.pro b/examples/virtual-device-app/android/App/app/proguard-rules.pro
new file mode 100644
index 00000000000000..481bb434814107
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/src/androidTest/java/com/matter/virtual/device/app/ExampleInstrumentedTest.kt b/examples/virtual-device-app/android/App/app/src/androidTest/java/com/matter/virtual/device/app/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000000000..09ae8b5b5a2b5b
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/androidTest/java/com/matter/virtual/device/app/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.matter.virtual.device.app
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.matter.virtual.device.app", appContext.packageName)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/app/src/main/AndroidManifest.xml b/examples/virtual-device-app/android/App/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000000..a57ddde21a56eb
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/src/main/ic_launcher-playstore.png b/examples/virtual-device-app/android/App/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 00000000000000..55c35b139dc27c
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/ic_launcher-playstore.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/java/com/matter/virtual/device/app/App.kt b/examples/virtual-device-app/android/App/app/src/main/java/com/matter/virtual/device/app/App.kt
new file mode 100644
index 00000000000000..16b1731fb941da
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/java/com/matter/virtual/device/app/App.kt
@@ -0,0 +1,14 @@
+package com.matter.virtual.device.app
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+import timber.log.Timber
+
+@HiltAndroidApp
+class App : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+ Timber.plant(TimberDebugTree())
+ }
+}
diff --git a/examples/virtual-device-app/android/App/app/src/main/java/com/matter/virtual/device/app/MainActivity.kt b/examples/virtual-device-app/android/App/app/src/main/java/com/matter/virtual/device/app/MainActivity.kt
new file mode 100644
index 00000000000000..94bfa6e97b8206
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/java/com/matter/virtual/device/app/MainActivity.kt
@@ -0,0 +1,67 @@
+package com.matter.virtual.device.app
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.view.MenuItem
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import com.matter.virtual.device.app.databinding.ActivityMainBinding
+import dagger.hilt.android.AndroidEntryPoint
+import timber.log.Timber
+
+@AndroidEntryPoint
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityMainBinding
+
+ private val permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
+
+ private val requestMultiplePermissions =
+ registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
+ permissions.entries.forEach { Timber.d("${it.key}:${it.value}") }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Timber.d("onCreate()")
+
+ val isPermissionsGranted =
+ permissions.all {
+ ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
+ }
+
+ if (!isPermissionsGranted) {
+ requestMultiplePermissions.launch(permissions)
+ }
+
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ Timber.d("onDestroy()")
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home -> {
+ onBackPressed()
+ return true
+ }
+ }
+
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ Timber.d("RequestCode:$requestCode")
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/app/src/main/java/com/matter/virtual/device/app/TimberDebugTree.kt b/examples/virtual-device-app/android/App/app/src/main/java/com/matter/virtual/device/app/TimberDebugTree.kt
new file mode 100644
index 00000000000000..a317af1372ed99
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/java/com/matter/virtual/device/app/TimberDebugTree.kt
@@ -0,0 +1,13 @@
+package com.matter.virtual.device.app
+
+import timber.log.Timber
+
+class TimberDebugTree : Timber.DebugTree() {
+ override fun createStackElementTag(element: StackTraceElement): String {
+ return if (BuildConfig.DEBUG) {
+ "[STD]:${element.fileName}:${element.lineNumber}#${element.methodName}"
+ } else {
+ "[STD]:${element.className}#${element.methodName}"
+ }
+ }
+}
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/drawable/ic_launcher_background.xml b/examples/virtual-device-app/android/App/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000000000..ca3826a46ce070
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/drawable/ic_launcher_foreground.xml b/examples/virtual-device-app/android/App/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 00000000000000..da1dcd94e9d5c2
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/layout/activity_main.xml b/examples/virtual-device-app/android/App/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000000000..fd08d47433063f
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000000000..bbd3e021239ce7
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000000000..bbd3e021239ce7
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000000000..06c2ae63795569
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000000000..0cf22d879af102
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000000000..3caf60918eaae5
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000000000..6295d899ef529d
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000000000..4b37dff4fe74a8
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000000..07249dac256563
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000000000..c1b77f5764199f
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000000..926924d8a97c79
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000000000..9261b54e843b16
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000000..1e239a77f6dba0
Binary files /dev/null and b/examples/virtual-device-app/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/navigation/nav_graph.xml b/examples/virtual-device-app/android/App/app/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 00000000000000..da2e60563663e2
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/values-night/colors.xml b/examples/virtual-device-app/android/App/app/src/main/res/values-night/colors.xml
new file mode 100644
index 00000000000000..088fde391464b9
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,5 @@
+
+
+ @android:color/white
+ #010101
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/app/src/main/res/values/strings.xml b/examples/virtual-device-app/android/App/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000000000..8194edb5a4edd8
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Matter Virtual Device
+
diff --git a/examples/virtual-device-app/android/App/app/src/test/java/com/matter/virtual/device/app/ExampleUnitTest.kt b/examples/virtual-device-app/android/App/app/src/test/java/com/matter/virtual/device/app/ExampleUnitTest.kt
new file mode 100644
index 00000000000000..8de371c92f9f9b
--- /dev/null
+++ b/examples/virtual-device-app/android/App/app/src/test/java/com/matter/virtual/device/app/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.matter.virtual.device.app
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/build.gradle.kts b/examples/virtual-device-app/android/App/build.gradle.kts
new file mode 100644
index 00000000000000..03ba9c9b33c1e3
--- /dev/null
+++ b/examples/virtual-device-app/android/App/build.gradle.kts
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ val kotlinVersion = "1.8.10"
+
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath("com.android.tools.build:gradle:7.3.1")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
+ classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion")
+ classpath("com.google.dagger:hilt-android-gradle-plugin:2.44.2")
+ classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3")
+ }
+}
+
+allprojects {
+ buildDir = File("${rootProject.buildDir}/${project.name}")
+}
+
+tasks.register("clean", Delete::class) {
+ delete(rootProject.buildDir)
+}
diff --git a/examples/virtual-device-app/android/App/buildSrc/.gitignore b/examples/virtual-device-app/android/App/buildSrc/.gitignore
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/examples/virtual-device-app/android/App/buildSrc/build.gradle.kts b/examples/virtual-device-app/android/App/buildSrc/build.gradle.kts
new file mode 100644
index 00000000000000..b22ed732fde2c5
--- /dev/null
+++ b/examples/virtual-device-app/android/App/buildSrc/build.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+ `kotlin-dsl`
+}
+
+repositories {
+ mavenCentral()
+}
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/buildSrc/src/main/java/com/matter/buildsrc/Deps.kt b/examples/virtual-device-app/android/App/buildSrc/src/main/java/com/matter/buildsrc/Deps.kt
new file mode 100644
index 00000000000000..2593dca0ee3227
--- /dev/null
+++ b/examples/virtual-device-app/android/App/buildSrc/src/main/java/com/matter/buildsrc/Deps.kt
@@ -0,0 +1,49 @@
+package com.matter.buildsrc
+
+object Deps {
+ object AndroidX {
+ const val core = "androidx.core:core-ktx:1.8.0"
+ const val appcompat = "androidx.appcompat:appcompat:1.5.1"
+ const val fragment = "androidx.fragment:fragment-ktx:1.5.7"
+
+ object Lifecycle {
+ private const val lifecycleVersion = "2.3.1"
+ const val viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
+ const val livedata = "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
+ const val compiler = "androidx.lifecycle:lifecycle-compiler:$lifecycleVersion"
+ }
+ }
+
+ const val material = "com.google.android.material:material:1.7.0"
+
+ object Kotlin {
+ const val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0"
+ const val coroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"
+ const val reflect = "org.jetbrains.kotlin:kotlin-reflect:1.5.31"
+ const val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2"
+ }
+
+ object Dagger {
+ private const val daggerVersion = "2.44.2"
+ const val hiltAndroid = "com.google.dagger:hilt-android:$daggerVersion"
+ const val hiltAndroidCompiler = "com.google.dagger:hilt-android-compiler:$daggerVersion"
+ }
+
+ object Test {
+ const val junit = "junit:junit:4.13.2"
+ const val junitExt = "androidx.test.ext:junit:1.1.3"
+ const val espresso = "androidx.test.espresso:espresso-core:3.4.0"
+ }
+
+ object Navigation {
+ private const val navigationVersion = "2.5.3"
+ const val fragment = "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
+ const val ui = "androidx.navigation:navigation-ui-ktx:$navigationVersion"
+ }
+
+ const val timber = "com.jakewharton.timber:timber:5.0.1"
+ const val inject = "javax.inject:javax.inject:1"
+ const val zxing = "com.google.zxing:core:3.4.0"
+ const val lottie = "com.airbnb.android:lottie:3.4.1"
+ const val gson = "com.google.code.gson:gson:2.8.5"
+}
diff --git a/examples/virtual-device-app/android/App/buildSrc/src/main/java/com/matter/buildsrc/Versions.kt b/examples/virtual-device-app/android/App/buildSrc/src/main/java/com/matter/buildsrc/Versions.kt
new file mode 100644
index 00000000000000..543ed231e3709c
--- /dev/null
+++ b/examples/virtual-device-app/android/App/buildSrc/src/main/java/com/matter/buildsrc/Versions.kt
@@ -0,0 +1,8 @@
+package com.matter.buildsrc
+
+object Versions {
+ const val compileSdkVersion = 33
+ const val buildToolsVersion = "31.0.0"
+ const val minSdkVersion = 24
+ const val targetSdkVersion = 33
+}
diff --git a/examples/virtual-device-app/android/App/core/common/.gitignore b/examples/virtual-device-app/android/App/core/common/.gitignore
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/examples/virtual-device-app/android/App/core/common/build.gradle.kts b/examples/virtual-device-app/android/App/core/common/build.gradle.kts
new file mode 100644
index 00000000000000..d424108ab919de
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/build.gradle.kts
@@ -0,0 +1,60 @@
+import com.matter.buildsrc.Deps
+import com.matter.buildsrc.Versions
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("org.jetbrains.kotlin.plugin.serialization")
+ id("com.google.dagger.hilt.android")
+ kotlin("kapt")
+}
+
+android {
+ namespace = "com.matter.virtual.device.app.core.common"
+ compileSdk = Versions.compileSdkVersion
+
+ defaultConfig {
+ minSdk = Versions.minSdkVersion
+ targetSdk = Versions.targetSdkVersion
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+
+ implementation(project(":core:model"))
+
+ implementation(Deps.Kotlin.serialization)
+ implementation(Deps.Kotlin.reflect)
+
+ implementation(Deps.Dagger.hiltAndroid)
+ kapt(Deps.Dagger.hiltAndroidCompiler)
+
+ implementation(Deps.Navigation.fragment)
+ implementation(Deps.Navigation.ui)
+
+ implementation(Deps.zxing)
+
+ testImplementation(Deps.Test.junit)
+ androidTestImplementation(Deps.Test.junitExt)
+ androidTestImplementation(Deps.Test.espresso)
+}
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/common/proguard-rules.pro b/examples/virtual-device-app/android/App/core/common/proguard-rules.pro
new file mode 100644
index 00000000000000..481bb434814107
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/common/src/androidTest/java/com/matter/virtual/device/app/core/common/ExampleInstrumentedTest.kt b/examples/virtual-device-app/android/App/core/common/src/androidTest/java/com/matter/virtual/device/app/core/common/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000000000..54876ab392e911
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/androidTest/java/com/matter/virtual/device/app/core/common/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.matter.virtual.device.app.core.common
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.matter.virtual.device.app.core.common.test", appContext.packageName)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/AndroidManifest.xml b/examples/virtual-device-app/android/App/core/common/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000000..a5918e68abcdde
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/DeepLink.kt b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/DeepLink.kt
new file mode 100644
index 00000000000000..1a84f4a6840cd2
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/DeepLink.kt
@@ -0,0 +1,21 @@
+package com.matter.virtual.device.app.core.common
+
+import androidx.core.net.toUri
+import androidx.navigation.NavDeepLinkRequest
+
+object DeepLink {
+ fun getDeepLinkRequestForQrcodeFragment(setting: String): NavDeepLinkRequest {
+ return NavDeepLinkRequest.Builder.fromUri(
+ "android-app://com.matter.virtual.device.app.feature.qrcode/qrcodeFragment/${setting}"
+ .toUri()
+ )
+ .build()
+ }
+
+ fun getDeepLinkRequestForSetupFragment(setting: String): NavDeepLinkRequest {
+ return NavDeepLinkRequest.Builder.fromUri(
+ "android-app://com.matter.virtual.device.app.feature.setup/setupFragment/${setting}".toUri()
+ )
+ .build()
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/Device.kt b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/Device.kt
new file mode 100644
index 00000000000000..067898bbd79d93
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/Device.kt
@@ -0,0 +1,38 @@
+package com.matter.virtual.device.app.core.common
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import kotlinx.serialization.Serializable
+
+@Serializable
+sealed class Device(
+ val title: String,
+ @StringRes val deviceNameResId: Int,
+ @DrawableRes val deviceIconResId: Int,
+ val deviceTypeId: Long,
+ val discriminator: Int
+) {
+ @Serializable
+ object OnOffSwitch :
+ Device(
+ "onoffswitch",
+ R.string.matter_on_off_switch,
+ R.drawable.round_toggle_on_24,
+ 0x0103,
+ 3841
+ )
+
+ @Serializable
+ object Unknown :
+ Device("unknown", R.string.matter_device, R.drawable.round_device_unknown_24, 65535, 3840)
+
+ companion object {
+ fun map(title: String): Device {
+ return Device::class
+ .sealedSubclasses
+ .firstOrNull { it.objectInstance?.title == title }
+ ?.objectInstance
+ ?: Unknown
+ }
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/MatterConstants.kt b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/MatterConstants.kt
new file mode 100644
index 00000000000000..84f26bfca81b68
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/MatterConstants.kt
@@ -0,0 +1,11 @@
+package com.matter.virtual.device.app.core.common
+
+object MatterConstants {
+ const val DEFAULT_VERSION = 0
+ const val DEFAULT_VENDOR_ID = 0xFFF1
+ const val DEFAULT_PRODUCT_ID = 0x8001
+ const val DEFAULT_COMMISSIONING_FLOW = 0
+ const val DEFAULT_SETUP_PINCODE = 20202021L
+ const val DEFAULT_DISCRIMINATOR = 3840
+ const val DEFAULT_DEVICE_NAME = "Matter Device"
+}
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/MatterSettings.kt b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/MatterSettings.kt
new file mode 100644
index 00000000000000..dfa2f9e7542a51
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/MatterSettings.kt
@@ -0,0 +1,11 @@
+package com.matter.virtual.device.app.core.common
+
+import com.matter.virtual.device.app.core.model.OnboardingType
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class MatterSettings(
+ var device: Device = Device.Unknown,
+ var onboardingType: OnboardingType = OnboardingType.UNKNOWN,
+ var discriminator: Int = MatterConstants.DEFAULT_DISCRIMINATOR
+)
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/QrcodeUtil.kt b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/QrcodeUtil.kt
new file mode 100644
index 00000000000000..35f82d94c5c9a8
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/QrcodeUtil.kt
@@ -0,0 +1,29 @@
+package com.matter.virtual.device.app.core.common
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.qrcode.QRCodeWriter
+
+object QrcodeUtil {
+
+ fun createQrCodeBitmap(contents: String, width: Int, height: Int): Bitmap? {
+ try {
+ val bitMatrix = QRCodeWriter().encode(contents, BarcodeFormat.QR_CODE, width, height)
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+
+ for (y in 0 until bitMatrix.height) {
+ for (x in 0 until bitMatrix.width) {
+ if (bitMatrix.get(x, y)) {
+ bitmap.setPixel(x, y, Color.BLACK)
+ }
+ }
+ }
+
+ return bitmap
+ } catch (e: Exception) {
+ e.printStackTrace()
+ return null
+ }
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/Result.kt b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/Result.kt
new file mode 100644
index 00000000000000..02330400bebc5b
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/Result.kt
@@ -0,0 +1,20 @@
+package com.matter.virtual.device.app.core.common
+
+sealed class Result {
+
+ data class Success(val data: T) : Result()
+
+ data class Error(val exception: Throwable) : Result()
+
+ object Loading : Result()
+}
+
+val Result<*>.succeeded
+ get() = this is Result.Success && data != null
+
+fun Result.successOr(fallback: T): T {
+ return (this as? Result.Success)?.data ?: fallback
+}
+
+val Result.data: T?
+ get() = (this as? Result.Success)?.data
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/di/CoroutinesModule.kt b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/di/CoroutinesModule.kt
new file mode 100644
index 00000000000000..ec4974e0c5646f
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/di/CoroutinesModule.kt
@@ -0,0 +1,30 @@
+package com.matter.virtual.device.app.core.common.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+
+@InstallIn(SingletonComponent::class)
+@Module
+internal object CoroutinesModule {
+ @DefaultDispatcher
+ @Provides
+ @Singleton
+ fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
+
+ @IoDispatcher @Provides @Singleton fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
+
+ @MainDispatcher
+ @Provides
+ @Singleton
+ fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
+
+ @MainImmediateDispatcher
+ @Provides
+ @Singleton
+ fun provideMainImmediateDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
+}
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/di/CoroutinesQualifiers.kt b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/di/CoroutinesQualifiers.kt
new file mode 100644
index 00000000000000..ac55fb68756e17
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/di/CoroutinesQualifiers.kt
@@ -0,0 +1,11 @@
+package com.matter.virtual.device.app.core.common.di
+
+import javax.inject.Qualifier
+
+@Retention(AnnotationRetention.BINARY) @Qualifier annotation class DefaultDispatcher
+
+@Retention(AnnotationRetention.BINARY) @Qualifier annotation class IoDispatcher
+
+@Retention(AnnotationRetention.BINARY) @Qualifier annotation class MainDispatcher
+
+@Retention(AnnotationRetention.BINARY) @Qualifier annotation class MainImmediateDispatcher
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/ui/DisableAppBarLayoutBehavior.kt b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/ui/DisableAppBarLayoutBehavior.kt
new file mode 100644
index 00000000000000..929306fbdc92f8
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/java/com/matter/virtual/device/app/core/common/ui/DisableAppBarLayoutBehavior.kt
@@ -0,0 +1,31 @@
+package com.matter.virtual.device.app.core.common.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import com.google.android.material.appbar.AppBarLayout
+
+class DisableAppBarLayoutBehavior(context: Context, attributeSet: AttributeSet) :
+ AppBarLayout.Behavior(context, attributeSet) {
+
+ override fun onStartNestedScroll(
+ parent: CoordinatorLayout,
+ child: AppBarLayout,
+ directTargetChild: View,
+ target: View,
+ nestedScrollAxes: Int,
+ type: Int
+ ): Boolean {
+ return false
+ }
+
+ override fun onTouchEvent(
+ parent: CoordinatorLayout,
+ child: AppBarLayout,
+ ev: MotionEvent
+ ): Boolean {
+ return false
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/res/drawable/menu_item_bg.xml b/examples/virtual-device-app/android/App/core/common/src/main/res/drawable/menu_item_bg.xml
new file mode 100644
index 00000000000000..12bfa05d1664d4
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/res/drawable/menu_item_bg.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/res/drawable/round_device_unknown_24.xml b/examples/virtual-device-app/android/App/core/common/src/main/res/drawable/round_device_unknown_24.xml
new file mode 100644
index 00000000000000..22d47670e5fc8d
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/res/drawable/round_device_unknown_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/res/drawable/round_toggle_on_24.xml b/examples/virtual-device-app/android/App/core/common/src/main/res/drawable/round_toggle_on_24.xml
new file mode 100644
index 00000000000000..d42714cc7696e0
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/res/drawable/round_toggle_on_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/res/values-night/colors.xml b/examples/virtual-device-app/android/App/core/common/src/main/res/values-night/colors.xml
new file mode 100644
index 00000000000000..cbc187543ea0e6
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/res/values-night/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #171717
+ @android:color/white
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/res/values/colors.xml b/examples/virtual-device-app/android/App/core/common/src/main/res/values/colors.xml
new file mode 100644
index 00000000000000..14778835968022
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/res/values/colors.xml
@@ -0,0 +1,12 @@
+
+
+ @android:color/white
+ @android:color/white
+ #4ca7ed
+ @android:color/black
+ #f6f6f6
+ @color/color_background_secondary
+ @color/textColor
+ #fcfcfc
+ @android:color/black
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/res/values/dimens.xml b/examples/virtual-device-app/android/App/core/common/src/main/res/values/dimens.xml
new file mode 100644
index 00000000000000..755c53609e9b3f
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/res/values/dimens.xml
@@ -0,0 +1,25 @@
+
+
+ 350dp
+ 25sp
+ 17sp
+
+ 10dp
+ 48dp
+ 48dp
+ 7dp
+ 7dp
+ 13dp
+ 14dp
+ 24dp
+ 17sp
+ 16dp
+ 16dp
+ 16dp
+
+ 11sp
+ 20sp
+ 15sp
+
+ 50dp
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/res/values/strings.xml b/examples/virtual-device-app/android/App/core/common/src/main/res/values/strings.xml
new file mode 100644
index 00000000000000..7afe5953bf2dfd
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+ Matter OnOff Switch
+ Matter Device
+ icon
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/common/src/main/res/values/themes.xml b/examples/virtual-device-app/android/App/core/common/src/main/res/values/themes.xml
new file mode 100644
index 00000000000000..fcbe6bc78d57d4
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/main/res/values/themes.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/common/src/test/java/com/matter/virtual/device/app/core/common/ExampleUnitTest.kt b/examples/virtual-device-app/android/App/core/common/src/test/java/com/matter/virtual/device/app/core/common/ExampleUnitTest.kt
new file mode 100644
index 00000000000000..6df49ef5a27e1a
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/common/src/test/java/com/matter/virtual/device/app/core/common/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.matter.virtual.device.app.core.common
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/data/.gitignore b/examples/virtual-device-app/android/App/core/data/.gitignore
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/examples/virtual-device-app/android/App/core/data/build.gradle.kts b/examples/virtual-device-app/android/App/core/data/build.gradle.kts
new file mode 100644
index 00000000000000..43027424737f65
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/build.gradle.kts
@@ -0,0 +1,55 @@
+import com.matter.buildsrc.Deps
+import com.matter.buildsrc.Versions
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("com.google.dagger.hilt.android")
+ kotlin("kapt")
+}
+
+android {
+ namespace = "com.matter.virtual.device.app.core.data"
+ compileSdk = Versions.compileSdkVersion
+
+ defaultConfig {
+ minSdk = Versions.minSdkVersion
+ targetSdk = Versions.targetSdkVersion
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+
+ implementation(project(":core:common"))
+ implementation(project(":core:matter"))
+ implementation(project(":core:model"))
+
+ implementation(Deps.Dagger.hiltAndroid)
+ kapt(Deps.Dagger.hiltAndroidCompiler)
+
+ implementation(Deps.timber)
+
+ testImplementation(Deps.Test.junit)
+ androidTestImplementation(Deps.Test.junitExt)
+ androidTestImplementation(Deps.Test.espresso)
+}
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/data/proguard-rules.pro b/examples/virtual-device-app/android/App/core/data/proguard-rules.pro
new file mode 100644
index 00000000000000..481bb434814107
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/data/src/androidTest/java/com/matter/virtual/device/app/core/data/ExampleInstrumentedTest.kt b/examples/virtual-device-app/android/App/core/data/src/androidTest/java/com/matter/virtual/device/app/core/data/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000000000..26be07910d95f1
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/src/androidTest/java/com/matter/virtual/device/app/core/data/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.matter.virtual.device.app.core.data
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.matter.virtual.device.app.core.data.test", appContext.packageName)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/data/src/main/AndroidManifest.xml b/examples/virtual-device-app/android/App/core/data/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000000..a5918e68abcdde
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/di/DataModule.kt b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/di/DataModule.kt
new file mode 100644
index 00000000000000..c0352d531da3b5
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/di/DataModule.kt
@@ -0,0 +1,19 @@
+package com.matter.virtual.device.app.core.data.di
+
+import com.matter.virtual.device.app.core.data.repository.MatterRepository
+import com.matter.virtual.device.app.core.data.repository.MatterRepositoryImpl
+import com.matter.virtual.device.app.core.data.repository.NetworkRepository
+import com.matter.virtual.device.app.core.data.repository.NetworkRepositoryImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@InstallIn(SingletonComponent::class)
+@Module
+internal abstract class DataModule {
+
+ @Binds abstract fun bindMatterRepository(repository: MatterRepositoryImpl): MatterRepository
+
+ @Binds abstract fun bindNetworkRepository(repository: NetworkRepositoryImpl): NetworkRepository
+}
diff --git a/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/MatterRepository.kt b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/MatterRepository.kt
new file mode 100644
index 00000000000000..7e436f8208276d
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/MatterRepository.kt
@@ -0,0 +1,9 @@
+package com.matter.virtual.device.app.core.data.repository
+
+import com.matter.virtual.device.app.core.model.Payload
+
+interface MatterRepository {
+ fun getQrcodeString(payload: Payload): String
+
+ fun getManualPairingCodeString(payload: Payload): String
+}
diff --git a/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/MatterRepositoryImpl.kt b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/MatterRepositoryImpl.kt
new file mode 100644
index 00000000000000..dc6d1cfd73174a
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/MatterRepositoryImpl.kt
@@ -0,0 +1,17 @@
+package com.matter.virtual.device.app.core.data.repository
+
+import com.matter.virtual.device.app.core.matter.MatterApi
+import com.matter.virtual.device.app.core.model.Payload
+import javax.inject.Inject
+
+internal class MatterRepositoryImpl @Inject constructor(private val matterApi: MatterApi) :
+ MatterRepository {
+
+ override fun getQrcodeString(payload: Payload): String {
+ return matterApi.getQrcodeString(payload)
+ }
+
+ override fun getManualPairingCodeString(payload: Payload): String {
+ return matterApi.getManualPairingCodeString(payload)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/NetworkRepository.kt b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/NetworkRepository.kt
new file mode 100644
index 00000000000000..4ff1cd8bf84a33
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/NetworkRepository.kt
@@ -0,0 +1,5 @@
+package com.matter.virtual.device.app.core.data.repository
+
+interface NetworkRepository {
+ suspend fun getSSID(): String
+}
diff --git a/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/NetworkRepositoryImpl.kt b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/NetworkRepositoryImpl.kt
new file mode 100644
index 00000000000000..20395ad2b7a637
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/src/main/java/com/matter/virtual/device/app/core/data/repository/NetworkRepositoryImpl.kt
@@ -0,0 +1,34 @@
+package com.matter.virtual.device.app.core.data.repository
+
+import android.content.Context
+import android.net.wifi.SupplicantState
+import android.net.wifi.WifiManager
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import timber.log.Timber
+
+internal class NetworkRepositoryImpl
+@Inject
+constructor(@ApplicationContext private val context: Context) : NetworkRepository {
+
+ override suspend fun getSSID(): String {
+ Timber.d("Hit")
+ return try {
+ val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager?
+ wifiManager?.let {
+ val wifiInfo = wifiManager.connectionInfo
+ var ssid = ""
+ if (wifiInfo.supplicantState == SupplicantState.COMPLETED) {
+ Timber.d("ssid:${wifiInfo.ssid}")
+ ssid = wifiInfo.ssid.replace("\"", "")
+ }
+
+ ssid
+ }
+ ?: "Unknown"
+ } catch (e: Exception) {
+ Timber.e(e, "Exception")
+ "Unknown"
+ }
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/data/src/test/java/com/matter/virtual/device/app/core/data/ExampleUnitTest.kt b/examples/virtual-device-app/android/App/core/data/src/test/java/com/matter/virtual/device/app/core/data/ExampleUnitTest.kt
new file mode 100644
index 00000000000000..ec5dd8e3957aa3
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/data/src/test/java/com/matter/virtual/device/app/core/data/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.matter.virtual.device.app.core.data
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/domain/.gitignore b/examples/virtual-device-app/android/App/core/domain/.gitignore
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/examples/virtual-device-app/android/App/core/domain/build.gradle.kts b/examples/virtual-device-app/android/App/core/domain/build.gradle.kts
new file mode 100644
index 00000000000000..6cc2a239897333
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/build.gradle.kts
@@ -0,0 +1,52 @@
+import com.matter.buildsrc.Deps
+import com.matter.buildsrc.Versions
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "com.matter.virtual.device.app.core.domain"
+ compileSdk = Versions.compileSdkVersion
+
+ defaultConfig {
+ minSdk = Versions.minSdkVersion
+ targetSdk = Versions.targetSdkVersion
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+
+ implementation(project(":core:common"))
+ implementation(project(":core:data"))
+ implementation(project(":core:model"))
+
+ implementation(Deps.Kotlin.coroutinesCore)
+
+ implementation(Deps.inject)
+
+ testImplementation(Deps.Test.junit)
+ androidTestImplementation(Deps.Test.junitExt)
+ androidTestImplementation(Deps.Test.espresso)
+}
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/domain/proguard-rules.pro b/examples/virtual-device-app/android/App/core/domain/proguard-rules.pro
new file mode 100644
index 00000000000000..481bb434814107
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/domain/src/androidTest/java/com/matter/virtual/device/app/core/domain/ExampleInstrumentedTest.kt b/examples/virtual-device-app/android/App/core/domain/src/androidTest/java/com/matter/virtual/device/app/core/domain/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000000000..ee74e1189e90bd
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/src/androidTest/java/com/matter/virtual/device/app/core/domain/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.matter.virtual.device.app.core.domain
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.matter.virtual.device.app.core.domain.test", appContext.packageName)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/domain/src/main/AndroidManifest.xml b/examples/virtual-device-app/android/App/core/domain/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000000..a5918e68abcdde
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/CoroutineUseCase.kt b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/CoroutineUseCase.kt
new file mode 100644
index 00000000000000..548bff027c5fe7
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/CoroutineUseCase.kt
@@ -0,0 +1,18 @@
+package com.matter.virtual.device.app.core.domain
+
+import com.matter.virtual.device.app.core.common.Result
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+abstract class CoroutineUseCase(private val coroutineDispatcher: CoroutineDispatcher) {
+
+ suspend operator fun invoke(parameters: P): Result {
+ return try {
+ withContext(coroutineDispatcher) { execute(parameters).let { Result.Success(it) } }
+ } catch (e: Exception) {
+ Result.Error(e)
+ }
+ }
+
+ @Throws(RuntimeException::class) protected abstract suspend fun execute(param: P): R
+}
diff --git a/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/NonParamCoroutineUseCase.kt b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/NonParamCoroutineUseCase.kt
new file mode 100644
index 00000000000000..869b865b00a52d
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/NonParamCoroutineUseCase.kt
@@ -0,0 +1,18 @@
+package com.matter.virtual.device.app.core.domain
+
+import com.matter.virtual.device.app.core.common.Result
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+abstract class NonParamCoroutineUseCase(private val coroutineDispatcher: CoroutineDispatcher) {
+
+ suspend operator fun invoke(): Result {
+ return try {
+ withContext(coroutineDispatcher) { execute().let { Result.Success(it) } }
+ } catch (e: Exception) {
+ Result.Error(e)
+ }
+ }
+
+ @Throws(RuntimeException::class) protected abstract suspend fun execute(): R
+}
diff --git a/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/usecase/matter/GetManualPairingCodeStringUseCase.kt b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/usecase/matter/GetManualPairingCodeStringUseCase.kt
new file mode 100644
index 00000000000000..4c75c7532696d7
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/usecase/matter/GetManualPairingCodeStringUseCase.kt
@@ -0,0 +1,20 @@
+package com.matter.virtual.device.app.core.domain.usecase.matter
+
+import com.matter.virtual.device.app.core.common.di.IoDispatcher
+import com.matter.virtual.device.app.core.data.repository.MatterRepository
+import com.matter.virtual.device.app.core.domain.CoroutineUseCase
+import com.matter.virtual.device.app.core.model.Payload
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+
+class GetManualPairingCodeStringUseCase
+@Inject
+constructor(
+ private val matterRepository: MatterRepository,
+ @IoDispatcher dispatcher: CoroutineDispatcher
+) : CoroutineUseCase(dispatcher) {
+
+ override suspend fun execute(param: Payload): String {
+ return matterRepository.getManualPairingCodeString(param)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/usecase/matter/GetQrCodeStringUseCase.kt b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/usecase/matter/GetQrCodeStringUseCase.kt
new file mode 100644
index 00000000000000..0e8a5e7f5772ce
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/usecase/matter/GetQrCodeStringUseCase.kt
@@ -0,0 +1,20 @@
+package com.matter.virtual.device.app.core.domain.usecase.matter
+
+import com.matter.virtual.device.app.core.common.di.IoDispatcher
+import com.matter.virtual.device.app.core.data.repository.MatterRepository
+import com.matter.virtual.device.app.core.domain.CoroutineUseCase
+import com.matter.virtual.device.app.core.model.Payload
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+
+class GetQrcodeStringUseCase
+@Inject
+constructor(
+ private val matterRepository: MatterRepository,
+ @IoDispatcher dispatcher: CoroutineDispatcher
+) : CoroutineUseCase(dispatcher) {
+
+ override suspend fun execute(param: Payload): String {
+ return matterRepository.getQrcodeString(param)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/usecase/network/GetSSIDUseCase.kt b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/usecase/network/GetSSIDUseCase.kt
new file mode 100644
index 00000000000000..43dfdabacb1363
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/src/main/java/com/matter/virtual/device/app/core/domain/usecase/network/GetSSIDUseCase.kt
@@ -0,0 +1,19 @@
+package com.matter.virtual.device.app.core.domain.usecase.network
+
+import com.matter.virtual.device.app.core.common.di.IoDispatcher
+import com.matter.virtual.device.app.core.data.repository.NetworkRepository
+import com.matter.virtual.device.app.core.domain.NonParamCoroutineUseCase
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+
+class GetSSIDUseCase
+@Inject
+constructor(
+ private val networkRepository: NetworkRepository,
+ @IoDispatcher dispatcher: CoroutineDispatcher
+) : NonParamCoroutineUseCase(dispatcher) {
+
+ override suspend fun execute(): String {
+ return networkRepository.getSSID()
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/domain/src/test/java/com/matter/virtual/device/app/core/domain/ExampleUnitTest.kt b/examples/virtual-device-app/android/App/core/domain/src/test/java/com/matter/virtual/device/app/core/domain/ExampleUnitTest.kt
new file mode 100644
index 00000000000000..97d7f5351868b1
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/domain/src/test/java/com/matter/virtual/device/app/core/domain/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.matter.virtual.device.app.core.domain
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/matter/.gitignore b/examples/virtual-device-app/android/App/core/matter/.gitignore
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/examples/virtual-device-app/android/App/core/matter/build.gradle.kts b/examples/virtual-device-app/android/App/core/matter/build.gradle.kts
new file mode 100644
index 00000000000000..9f94201ac02cf8
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/matter/build.gradle.kts
@@ -0,0 +1,60 @@
+import com.matter.buildsrc.Deps
+import com.matter.buildsrc.Versions
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("com.google.dagger.hilt.android")
+ kotlin("kapt")
+}
+
+android {
+ namespace = "com.matter.virtual.device.app.core.matter"
+ compileSdk = Versions.compileSdkVersion
+
+ defaultConfig {
+ minSdk = Versions.minSdkVersion
+ targetSdk = Versions.targetSdkVersion
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ sourceSets {
+ getByName("main") {
+ jniLibs.setSrcDirs(listOf("../../app/libs/jniLibs"))
+ }
+ }
+}
+
+dependencies {
+ implementation(fileTree(mapOf("dir" to "../../app/libs", "include" to listOf("*.jar"))))
+
+ implementation(project(":core:common"))
+ implementation(project(":core:model"))
+
+ implementation(Deps.Dagger.hiltAndroid)
+ kapt(Deps.Dagger.hiltAndroidCompiler)
+
+ implementation(Deps.timber)
+
+ testImplementation(Deps.Test.junit)
+ androidTestImplementation(Deps.Test.junitExt)
+ androidTestImplementation(Deps.Test.espresso)
+}
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/matter/proguard-rules.pro b/examples/virtual-device-app/android/App/core/matter/proguard-rules.pro
new file mode 100644
index 00000000000000..481bb434814107
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/matter/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/matter/src/androidTest/java/com/matter/virtual/device/app/core/matter/ExampleInstrumentedTest.kt b/examples/virtual-device-app/android/App/core/matter/src/androidTest/java/com/matter/virtual/device/app/core/matter/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000000000..1aa22f0498dde6
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/matter/src/androidTest/java/com/matter/virtual/device/app/core/matter/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.matter.virtual.device.app.core.matter
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.matter.virtual.device.app.core.matter.test", appContext.packageName)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/matter/src/main/AndroidManifest.xml b/examples/virtual-device-app/android/App/core/matter/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000000..a5918e68abcdde
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/matter/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/matter/src/main/java/com/matter/virtual/device/app/core/matter/MatterApi.kt b/examples/virtual-device-app/android/App/core/matter/src/main/java/com/matter/virtual/device/app/core/matter/MatterApi.kt
new file mode 100644
index 00000000000000..16974b4e307b49
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/matter/src/main/java/com/matter/virtual/device/app/core/matter/MatterApi.kt
@@ -0,0 +1,40 @@
+package com.matter.virtual.device.app.core.matter
+
+import chip.onboardingpayload.OnboardingPayloadException
+import chip.onboardingpayload.OnboardingPayloadParser
+import com.matter.virtual.device.app.core.matter.model.asSetupPayload
+import com.matter.virtual.device.app.core.model.Payload
+import javax.inject.Inject
+import javax.inject.Singleton
+import timber.log.Timber
+
+@Singleton
+class MatterApi @Inject constructor() {
+
+ fun getQrcodeString(payload: Payload): String {
+ val onboardingPayloadParser = OnboardingPayloadParser()
+ var qrcode = ""
+ try {
+ qrcode = onboardingPayloadParser.getQrCodeFromPayload(payload.asSetupPayload())
+ } catch (e: OnboardingPayloadException) {
+ e.printStackTrace()
+ }
+
+ Timber.d("qrcode:$qrcode")
+ return qrcode
+ }
+
+ fun getManualPairingCodeString(payload: Payload): String {
+ val onboardingPayloadParser = OnboardingPayloadParser()
+ var manualPairingCode = ""
+ try {
+ manualPairingCode =
+ onboardingPayloadParser.getManualPairingCodeFromPayload(payload.asSetupPayload())
+ } catch (e: OnboardingPayloadException) {
+ e.printStackTrace()
+ }
+
+ Timber.d("manualPairingCode:$manualPairingCode")
+ return manualPairingCode
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/matter/src/main/java/com/matter/virtual/device/app/core/matter/model/Payload.kt b/examples/virtual-device-app/android/App/core/matter/src/main/java/com/matter/virtual/device/app/core/matter/model/Payload.kt
new file mode 100644
index 00000000000000..076fd5c7e86815
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/matter/src/main/java/com/matter/virtual/device/app/core/matter/model/Payload.kt
@@ -0,0 +1,33 @@
+package com.matter.virtual.device.app.core.matter.model
+
+import chip.onboardingpayload.DiscoveryCapability
+import chip.onboardingpayload.OnboardingPayload
+import com.matter.virtual.device.app.core.common.MatterConstants
+import com.matter.virtual.device.app.core.model.OnboardingType
+import com.matter.virtual.device.app.core.model.Payload
+import timber.log.Timber
+
+fun Payload.asSetupPayload(): OnboardingPayload {
+ val discoveryCapabilities = HashSet()
+ when (this.onboardingType) {
+ OnboardingType.WIFI -> discoveryCapabilities.add(DiscoveryCapability.ON_NETWORK)
+ OnboardingType.BLE -> discoveryCapabilities.add(DiscoveryCapability.BLE)
+ OnboardingType.WIFI_BLE -> {
+ discoveryCapabilities.add(DiscoveryCapability.ON_NETWORK)
+ discoveryCapabilities.add(DiscoveryCapability.BLE)
+ }
+ else -> {
+ Timber.e("Unknown Type")
+ }
+ }
+
+ return OnboardingPayload(
+ MatterConstants.DEFAULT_VERSION,
+ MatterConstants.DEFAULT_VENDOR_ID,
+ MatterConstants.DEFAULT_PRODUCT_ID,
+ MatterConstants.DEFAULT_COMMISSIONING_FLOW,
+ discoveryCapabilities,
+ discriminator,
+ MatterConstants.DEFAULT_SETUP_PINCODE
+ )
+}
diff --git a/examples/virtual-device-app/android/App/core/matter/src/test/java/com/matter/virtual/device/app/core/matter/ExampleUnitTest.kt b/examples/virtual-device-app/android/App/core/matter/src/test/java/com/matter/virtual/device/app/core/matter/ExampleUnitTest.kt
new file mode 100644
index 00000000000000..c355211d3b59db
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/matter/src/test/java/com/matter/virtual/device/app/core/matter/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.matter.virtual.device.app.core.matter
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/model/.gitignore b/examples/virtual-device-app/android/App/core/model/.gitignore
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/examples/virtual-device-app/android/App/core/model/build.gradle.kts b/examples/virtual-device-app/android/App/core/model/build.gradle.kts
new file mode 100644
index 00000000000000..d51ec3d4154ea2
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/model/build.gradle.kts
@@ -0,0 +1,44 @@
+import com.matter.buildsrc.Deps
+import com.matter.buildsrc.Versions
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "com.matter.virtual.device.app.core.model"
+ compileSdk = Versions.compileSdkVersion
+
+ defaultConfig {
+ minSdk = Versions.minSdkVersion
+ targetSdk = Versions.targetSdkVersion
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+
+ testImplementation(Deps.Test.junit)
+ androidTestImplementation(Deps.Test.junitExt)
+ androidTestImplementation(Deps.Test.espresso)
+}
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/model/proguard-rules.pro b/examples/virtual-device-app/android/App/core/model/proguard-rules.pro
new file mode 100644
index 00000000000000..481bb434814107
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/model/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/model/src/androidTest/java/com/matter/virtual/device/app/core/model/ExampleInstrumentedTest.kt b/examples/virtual-device-app/android/App/core/model/src/androidTest/java/com/matter/virtual/device/app/core/model/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000000000..ce8f30dded727c
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/model/src/androidTest/java/com/matter/virtual/device/app/core/model/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.matter.virtual.device.app.core.model
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.matter.virtual.device.app.core.model.test", appContext.packageName)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/core/model/src/main/AndroidManifest.xml b/examples/virtual-device-app/android/App/core/model/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000000..a5918e68abcdde
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/model/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/core/model/src/main/java/com/matter/virtual/device/app/core/model/OnobardingType.kt b/examples/virtual-device-app/android/App/core/model/src/main/java/com/matter/virtual/device/app/core/model/OnobardingType.kt
new file mode 100644
index 00000000000000..46ce38113a5b5b
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/model/src/main/java/com/matter/virtual/device/app/core/model/OnobardingType.kt
@@ -0,0 +1,16 @@
+package com.matter.virtual.device.app.core.model
+
+enum class OnboardingType {
+ WIFI,
+ BLE,
+ WIFI_BLE,
+ UNKNOWN
+}
+
+fun Int.asOnboardingType() =
+ when (this) {
+ 0 -> OnboardingType.WIFI
+ 1 -> OnboardingType.BLE
+ 2 -> OnboardingType.WIFI_BLE
+ else -> OnboardingType.UNKNOWN
+ }
diff --git a/examples/virtual-device-app/android/App/core/model/src/main/java/com/matter/virtual/device/app/core/model/Payload.kt b/examples/virtual-device-app/android/App/core/model/src/main/java/com/matter/virtual/device/app/core/model/Payload.kt
new file mode 100644
index 00000000000000..4ffc70768e31d1
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/model/src/main/java/com/matter/virtual/device/app/core/model/Payload.kt
@@ -0,0 +1,3 @@
+package com.matter.virtual.device.app.core.model
+
+data class Payload(val onboardingType: OnboardingType, val discriminator: Int)
diff --git a/examples/virtual-device-app/android/App/core/model/src/test/java/com/matter/virtual/device/app/core/model/ExampleUnitTest.kt b/examples/virtual-device-app/android/App/core/model/src/test/java/com/matter/virtual/device/app/core/model/ExampleUnitTest.kt
new file mode 100644
index 00000000000000..59f3546d111ca4
--- /dev/null
+++ b/examples/virtual-device-app/android/App/core/model/src/test/java/com/matter/virtual/device/app/core/model/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.matter.virtual.device.app.core.model
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/feature/main/.gitignore b/examples/virtual-device-app/android/App/feature/main/.gitignore
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/examples/virtual-device-app/android/App/feature/main/build.gradle.kts b/examples/virtual-device-app/android/App/feature/main/build.gradle.kts
new file mode 100644
index 00000000000000..c9931cf58938e4
--- /dev/null
+++ b/examples/virtual-device-app/android/App/feature/main/build.gradle.kts
@@ -0,0 +1,69 @@
+import com.matter.buildsrc.Deps
+import com.matter.buildsrc.Versions
+
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("com.google.dagger.hilt.android")
+ id("androidx.navigation.safeargs.kotlin")
+ kotlin("kapt")
+}
+
+android {
+ namespace = "com.matter.virtual.device.app.feature.main"
+ compileSdk = Versions.compileSdkVersion
+
+ defaultConfig {
+ minSdk = Versions.minSdkVersion
+ targetSdk = Versions.targetSdkVersion
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ buildFeatures {
+ viewBinding = true
+ dataBinding = true
+ }
+}
+
+dependencies {
+
+ implementation(project(":core:common"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:model"))
+
+ implementation(Deps.AndroidX.core)
+ implementation(Deps.AndroidX.appcompat)
+ implementation(Deps.AndroidX.fragment)
+
+ implementation(Deps.Kotlin.serialization)
+
+ implementation(Deps.Navigation.fragment)
+ implementation(Deps.Navigation.ui)
+
+ implementation(Deps.Dagger.hiltAndroid)
+ kapt(Deps.Dagger.hiltAndroidCompiler)
+
+ implementation(Deps.timber)
+
+ testImplementation(Deps.Test.junit)
+ androidTestImplementation(Deps.Test.junitExt)
+ androidTestImplementation(Deps.Test.espresso)
+}
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/feature/main/proguard-rules.pro b/examples/virtual-device-app/android/App/feature/main/proguard-rules.pro
new file mode 100644
index 00000000000000..481bb434814107
--- /dev/null
+++ b/examples/virtual-device-app/android/App/feature/main/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/feature/main/src/androidTest/java/com/matter/virtual/device/app/feature/main/ExampleInstrumentedTest.kt b/examples/virtual-device-app/android/App/feature/main/src/androidTest/java/com/matter/virtual/device/app/feature/main/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000000000..b1b135167ba95d
--- /dev/null
+++ b/examples/virtual-device-app/android/App/feature/main/src/androidTest/java/com/matter/virtual/device/app/feature/main/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.matter.virtual.device.app.feature.main
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.matter.virtual.device.app.feature.main.test", appContext.packageName)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/feature/main/src/main/AndroidManifest.xml b/examples/virtual-device-app/android/App/feature/main/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000000..a5918e68abcdde
--- /dev/null
+++ b/examples/virtual-device-app/android/App/feature/main/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/virtual-device-app/android/App/feature/main/src/main/java/com/matter/virtual/device/app/feature/main/BindingAdapter.kt b/examples/virtual-device-app/android/App/feature/main/src/main/java/com/matter/virtual/device/app/feature/main/BindingAdapter.kt
new file mode 100644
index 00000000000000..771aa2ad937ad9
--- /dev/null
+++ b/examples/virtual-device-app/android/App/feature/main/src/main/java/com/matter/virtual/device/app/feature/main/BindingAdapter.kt
@@ -0,0 +1,13 @@
+package com.matter.virtual.device.app.feature.main
+
+import androidx.annotation.DrawableRes
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.databinding.BindingAdapter
+
+object BindingAdapter {
+ @BindingAdapter("imageSrc")
+ @JvmStatic
+ fun AppCompatImageView.bindImageSrc(@DrawableRes imgResId: Int) {
+ this.setImageResource(imgResId)
+ }
+}
diff --git a/examples/virtual-device-app/android/App/feature/main/src/main/java/com/matter/virtual/device/app/feature/main/MainFragment.kt b/examples/virtual-device-app/android/App/feature/main/src/main/java/com/matter/virtual/device/app/feature/main/MainFragment.kt
new file mode 100644
index 00000000000000..55ebdace76a00e
--- /dev/null
+++ b/examples/virtual-device-app/android/App/feature/main/src/main/java/com/matter/virtual/device/app/feature/main/MainFragment.kt
@@ -0,0 +1,122 @@
+package com.matter.virtual.device.app.feature.main
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedCallback
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.matter.virtual.device.app.core.common.DeepLink
+import com.matter.virtual.device.app.core.common.MatterSettings
+import com.matter.virtual.device.app.feature.main.databinding.FragmentMainBinding
+import com.matter.virtual.device.app.feature.main.model.Menu
+import dagger.hilt.android.AndroidEntryPoint
+import kotlin.math.abs
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import timber.log.Timber
+
+@AndroidEntryPoint
+class MainFragment : Fragment() {
+
+ private lateinit var binding: FragmentMainBinding
+ private lateinit var onBackPressedCallback: OnBackPressedCallback
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ Timber.d("Hit")
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)
+ binding.lifecycleOwner = viewLifecycleOwner
+
+ return binding.root
+ }
+
+ @OptIn(ExperimentalSerializationApi::class)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ Timber.d("Hit")
+ super.onViewCreated(view, savedInstanceState)
+
+ (activity as AppCompatActivity).setSupportActionBar(binding.toolbar)
+ (activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true)
+
+ binding.appBarLayout.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
+ var ratio = 0F
+ if (abs(verticalOffset) != 0) {
+ ratio = abs(verticalOffset).toFloat() / appBarLayout.totalScrollRange.toFloat()
+ }
+
+ binding.collapseTitle.alpha = 1f - ratio * 2f + 0.1f
+ binding.toolbarTitle.alpha = (ratio - 0.5f) * 2f + 0.1f
+ }
+
+ val itemList = arrayListOf(Menu.ON_OFF_SWITCH)
+
+ val menuAdapter =
+ MenuAdapter(
+ object : MenuAdapter.ItemHandler {
+ override fun onClick(item: Menu) {
+ val matterSettings = MatterSettings(device = item.device)
+ val jsonSettings = Json.encodeToString(matterSettings)
+ try {
+ findNavController()
+ .navigate(DeepLink.getDeepLinkRequestForSetupFragment(jsonSettings))
+ } catch (e: Exception) {
+ Timber.e(e, "navigate failure")
+ }
+ }
+ }
+ )
+ .apply { submitList(itemList) }
+
+ val sideSpace = resources.getDimension(R.dimen.menu_item_side_space).toInt()
+ val bottomSpace = resources.getDimension(R.dimen.menu_item_bottom_space).toInt()
+
+ binding.recyclerView.apply {
+ layoutManager = LinearLayoutManager(requireContext())
+ if (itemDecorationCount == 0) {
+ addItemDecoration(VerticalSpaceItemDecoration(sideSpace, bottomSpace))
+ }
+ adapter = menuAdapter
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ Timber.d("onResume()")
+ }
+
+ override fun onAttach(context: Context) {
+ Timber.d("Hit")
+ super.onAttach(context)
+
+ onBackPressedCallback =
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ Timber.d("handleOnBackPressed()")
+ requireActivity().finishAffinity()
+ }
+ }
+
+ requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ Timber.d("onDetach()")
+ onBackPressedCallback.remove()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ Timber.d("onDestroy()")
+ }
+}
diff --git a/examples/virtual-device-app/android/App/feature/main/src/main/java/com/matter/virtual/device/app/feature/main/MenuAdapter.kt b/examples/virtual-device-app/android/App/feature/main/src/main/java/com/matter/virtual/device/app/feature/main/MenuAdapter.kt
new file mode 100644
index 00000000000000..c3212333d7ec13
--- /dev/null
+++ b/examples/virtual-device-app/android/App/feature/main/src/main/java/com/matter/virtual/device/app/feature/main/MenuAdapter.kt
@@ -0,0 +1,46 @@
+package com.matter.virtual.device.app.feature.main
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.matter.virtual.device.app.feature.main.databinding.ItemMenuBinding
+import com.matter.virtual.device.app.feature.main.model.Menu
+
+internal class MenuAdapter(private val itemHandler: ItemHandler) :
+ ListAdapter