Skip to content

Commit

Permalink
[Android] virtual-device-app: Add On/Off Switch feature in App (#28550)
Browse files Browse the repository at this point in the history
* virtual-device-app: Update chip device config

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add java files about chip stack id information

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add initialize api for OnOffManager

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Update parameter types about chip stack id

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add setCallback api

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Use ExampleDACProvider

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add core:ui module

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add MatterModule for provide DeviceApp library

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add OnOffManager stub

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement chip stack initialize code

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add device event callback

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add permissions about stack related

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement service managing code

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add SharedPreferences feature for managing commissioning state

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add OnOffManager repository

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Update matter repository for device event callback

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement sharedpreferences usecase

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement matter usecase

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add ignore batter optimization feature

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add SharedViewModel for managing reset popup

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement main view scenario

- go to commissioned device view
- reset abnormal state
- start commissioning

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement qrcode view scenario

- start MatterAppService
- monitoring establishment started event

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement loading view scenario

- monitoring commissioning complete event

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement base fragment/viewmodel for device detail view

- fabric removed event
- common reset menu/popup
- abstract setup functions

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement common layout

- on/off button layout
- radio button layout
- common space

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Implement onoff switch scenario

- set onoff attribute
- show onoff status

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Apply feature:control navGraph

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* virtual-device-app: Add permissions about stack related

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>

* Restyled by google-java-format

Signed-off-by: Charles Kim <[email protected]>

---------

Signed-off-by: Jaehoon You <[email protected]>
Signed-off-by: Charles Kim <[email protected]>
Co-authored-by: Jaehoon You <[email protected]>
Co-authored-by: Restyled.io <[email protected]>
  • Loading branch information
3 people authored and pull[bot] committed Feb 13, 2024
1 parent 01bab0c commit 1182256
Show file tree
Hide file tree
Showing 94 changed files with 2,342 additions and 151 deletions.
2 changes: 2 additions & 0 deletions examples/virtual-device-app/android/App/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ dependencies {
implementation(project(":core:data"))
implementation(project(":core:domain"))
implementation(project(":core:model"))
implementation(project(":core:ui"))
implementation(project(":feature:control"))
implementation(project(":feature:main"))
implementation(project(":feature:qrcode"))
implementation(project(":feature:setup"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

<application
android:name=".App"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
package com.matter.virtual.device.app

import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import android.view.Gravity
import android.view.MenuItem
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.matter.virtual.device.app.core.common.EventObserver
import com.matter.virtual.device.app.core.ui.SharedViewModel
import com.matter.virtual.device.app.core.ui.UiState
import com.matter.virtual.device.app.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
Expand All @@ -15,14 +27,22 @@ import timber.log.Timber
class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding
private val viewModel by viewModels<SharedViewModel>()

private val permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
private val permissions =
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.ACCESS_FINE_LOCATION
)

private val requestMultiplePermissions =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach { Timber.d("${it.key}:${it.value}") }
}

@SuppressLint("BatteryLife")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.d("onCreate()")
Expand All @@ -36,8 +56,40 @@ class MainActivity : AppCompatActivity() {
requestMultiplePermissions.launch(permissions)
}

val powerManager = applicationContext.getSystemService(POWER_SERVICE) as PowerManager?
powerManager?.let { manager ->
if (!manager.isIgnoringBatteryOptimizations(packageName)) {
Timber.d("not in battery optimization whitelist")
val intent =
Intent().apply {
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
data = Uri.parse("package:$packageName")
}
startActivity(intent)
}
}

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

viewModel.uiState.observe(
this,
EventObserver { uiState ->
when (uiState) {
UiState.Waiting -> {
binding.progress.visibility = View.VISIBLE
}
UiState.Exit -> {
binding.progress.visibility = View.GONE
finishAffinity()
}
is UiState.Reset -> {
showFactoryResetPopup(getString(uiState.messageResId), uiState.isCancelable)
}
else -> {}
}
}
)
}

override fun onDestroy() {
Expand All @@ -64,4 +116,28 @@ class MainActivity : AppCompatActivity() {
Timber.d("RequestCode:$requestCode")
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}

private fun showFactoryResetPopup(message: String, isCancelable: Boolean) {
val builder =
AlertDialog.Builder(this)
.setTitle("Factory Reset")
.setMessage(message)
.setPositiveButton("Ok") { dialog, _ ->
Timber.d("Ok")
dialog.dismiss()
viewModel.resetMatterAppServer()
}
.setCancelable(false)

if (isCancelable) {
builder.setNegativeButton("Cancel") { dialog, _ ->
Timber.d("Cancel")
dialog.dismiss()
}
}

val dialog = builder.create()
dialog.window?.setGravity(Gravity.BOTTOM)
dialog.show()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
<include app:graph="@navigation/main_nav_graph" />
<include app:graph="@navigation/setup_nav_graph" />
<include app:graph="@navigation/qrcode_nav_graph" />
<include app:graph="@navigation/control_nav_graph" />
</navigation>
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ dependencies {
implementation(Deps.Navigation.fragment)
implementation(Deps.Navigation.ui)

implementation(Deps.timber)
implementation(Deps.zxing)

testImplementation(Deps.Test.junit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.matter.virtual.device.app.core.common

import androidx.core.net.toUri
import androidx.navigation.NavDeepLinkRequest
import timber.log.Timber

object DeepLink {
fun getDeepLinkRequestForQrcodeFragment(setting: String): NavDeepLinkRequest {
Expand All @@ -26,4 +27,17 @@ object DeepLink {
)
.build()
}

fun getDeepLinkRequestFromDevice(device: Device, setting: String): NavDeepLinkRequest {
Timber.d("setting:$setting")
val uri =
when (device) {
Device.OnOffSwitch ->
"android-app://com.matter.virtual.device.app.feature.control/onOffSwitchFragment/${setting}"
.toUri()
Device.Unknown -> throw UnsupportedOperationException("Unsupported device")
}

return NavDeepLinkRequest.Builder.fromUri(uri).build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.matter.virtual.device.app.core.common

import androidx.lifecycle.Observer

open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set

fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}

fun peekContent(): T = content
}

class EventObserver<T>(private val onEventUnHandledContent: (T) -> Unit) : Observer<Event<T>> {
override fun onChanged(event: Event<T>?) {
event?.getContentIfNotHandled()?.let { value -> onEventUnHandledContent(value) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ 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_PRODUCT_ID = 0x8003
const val DEFAULT_COMMISSIONING_FLOW = 0
const val DEFAULT_SETUP_PINCODE = 20202021L
const val DEFAULT_DISCRIMINATOR = 3840
const val DEFAULT_DEVICE_NAME = "Matter Device"
const val DEFAULT_ENDPOINT = 1

const val TEST_SPAKE2P_VERIFIER =
"uWFwqugDNGiEck/po7KHwwMwwqZgN10XuyBajPGuyzUEV/iree4lOrao5GuwnlQ65CJzbeUB49s31EH+NEkg0JVI5MGCQGMMT/SRPFNRODm3wH/MBiehuFc6FJ/NH6Rmzw=="
const val TEST_SPAKE2P_SALT = "U1BBS0UyUCBLZXkgU2FsdA=="
const val TEST_SPAKE2P_ITERATION_COUNT = 1000
const val TEST_SETUP_PASSCODE: Long = 20202021
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.matter.virtual.device.app.core.common.sharedpreferences

object SharedPreferencesKey {
const val COMMISSIONING_DEVICE_COMPLETED = "commissioningDeviceCompleted"
const val COMMISSIONED_DEVICE = "commissionedDevice"
const val COMMISSIONING_SEQUENCE = "commissioningSequence"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.matter.virtual.device.app.core.common.sharedpreferences

import android.content.Context
import android.content.SharedPreferences
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
import timber.log.Timber

@Singleton
class SharedPreferencesManager
@Inject
constructor(@ApplicationContext private val context: Context) {

private val sharedPreferences: SharedPreferences by lazy {
context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
}

fun getString(key: String): String {
val value = sharedPreferences.getString(key, DEFAULT_VALUE_STRING)
return value ?: ""
}

fun setString(key: String, value: String) {
val editor = sharedPreferences.edit()
editor.putString(key, value)
editor.apply()
}

fun getBoolean(key: String): Boolean {
return sharedPreferences.getBoolean(key, DEFAULT_VALUE_BOOLEAN)
}

fun setBoolean(key: String, value: Boolean) {
val editor = sharedPreferences.edit()
editor.putBoolean(key, value)
editor.apply()
}

fun deleteMatterSharedPreferences() {
Timber.d("deleteMatterSharedPreferences()")
if (!context.deleteSharedPreferences(PREFERENCES_NAME_MATTER_KEY_VALUE_STORE)) {
Timber.e("delete failure($PREFERENCES_NAME_MATTER_KEY_VALUE_STORE)")
}

if (!context.deleteSharedPreferences(PREFERENCES_NAME_MATTER_CONFIGURATION_MANAGER)) {
Timber.e("delete failure($PREFERENCES_NAME_MATTER_CONFIGURATION_MANAGER)")
}
}

companion object {
private const val PREFERENCES_NAME = "virtualdeviceapp"
private const val PREFERENCES_NAME_MATTER_KEY_VALUE_STORE = "chip.platform.KeyValueStore"
private const val PREFERENCES_NAME_MATTER_CONFIGURATION_MANAGER =
"chip.platform.ConfigurationManager"
private const val DEFAULT_VALUE_STRING = ""
private const val DEFAULT_VALUE_BOOLEAN = false
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="appbar_layout_height">350dp</dimen>
<dimen name="collapse_title_text_size">25sp</dimen>
<dimen name="toolbar_title_text_size">17sp</dimen>

<dimen name="menu_item_side_space">10dp</dimen>
<dimen name="menu_item_image_width">48dp</dimen>
<dimen name="menu_item_image_height">48dp</dimen>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,22 @@ android {
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:matter"))
implementation(project(":core:model"))

implementation(Deps.Kotlin.serialization)

implementation(Deps.Dagger.hiltAndroid)
kapt(Deps.Dagger.hiltAndroidCompiler)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
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 com.matter.virtual.device.app.core.data.repository.*
import com.matter.virtual.device.app.core.data.repository.cluster.OnOffManagerRepository
import com.matter.virtual.device.app.core.data.repository.cluster.OnOffManagerRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -13,7 +12,17 @@ import dagger.hilt.components.SingletonComponent
@Module
internal abstract class DataModule {

@Binds
abstract fun bindOnOffManagerRepository(
repository: OnOffManagerRepositoryImpl
): OnOffManagerRepository

@Binds abstract fun bindMatterRepository(repository: MatterRepositoryImpl): MatterRepository

@Binds abstract fun bindNetworkRepository(repository: NetworkRepositoryImpl): NetworkRepository

@Binds
abstract fun bindSharedPreferencesRepository(
repository: SharedPreferencesRepositoryImpl
): SharedPreferencesRepository
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
package com.matter.virtual.device.app.core.data.repository

import com.matter.virtual.device.app.core.common.MatterSettings
import com.matter.virtual.device.app.core.model.Payload

interface MatterRepository {
fun getQrcodeString(payload: Payload): String

fun getManualPairingCodeString(payload: Payload): String

suspend fun startMatterAppService(matterSettings: MatterSettings)

suspend fun stopMatterAppService()

fun reset()

suspend fun isCommissioningCompleted(): Boolean

suspend fun isCommissioningSessionEstablishmentStarted(): Boolean

suspend fun isFabricRemoved(): Boolean
}
Loading

0 comments on commit 1182256

Please sign in to comment.