Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] virtual-device-app: Add On/Off Switch feature in App #28550

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
98c5f39
virtual-device-app: Update chip device config
Jaehoon-You Aug 6, 2023
7cfaa6f
virtual-device-app: Add java files about chip stack id information
Jaehoon-You Aug 6, 2023
456e1a0
virtual-device-app: Add initialize api for OnOffManager
Jaehoon-You Aug 6, 2023
8cd07a0
virtual-device-app: Update parameter types about chip stack id
Jaehoon-You Aug 6, 2023
7c35f5d
virtual-device-app: Add setCallback api
Jaehoon-You Aug 6, 2023
b0a5e5f
virtual-device-app: Use ExampleDACProvider
Jaehoon-You Aug 6, 2023
cb459c4
virtual-device-app: Add core:ui module
Jaehoon-You Aug 6, 2023
1874ad6
virtual-device-app: Add MatterModule for provide DeviceApp library
Jaehoon-You Aug 6, 2023
5b38336
virtual-device-app: Add OnOffManager stub
Jaehoon-You Aug 6, 2023
1943fe2
virtual-device-app: Implement chip stack initialize code
Jaehoon-You Aug 6, 2023
9cb969d
virtual-device-app: Add device event callback
Jaehoon-You Aug 6, 2023
1136d54
virtual-device-app: Add permissions about stack related
Jaehoon-You Aug 6, 2023
592f654
virtual-device-app: Implement service managing code
Jaehoon-You Aug 6, 2023
c17657e
virtual-device-app: Add SharedPreferences feature for managing commis…
Jaehoon-You Aug 6, 2023
8107188
virtual-device-app: Add OnOffManager repository
Jaehoon-You Aug 6, 2023
c091d60
virtual-device-app: Update matter repository for device event callback
Jaehoon-You Aug 6, 2023
c510c45
virtual-device-app: Implement sharedpreferences usecase
Jaehoon-You Aug 6, 2023
da9f523
virtual-device-app: Implement matter usecase
Jaehoon-You Aug 6, 2023
fdc8f86
virtual-device-app: Add ignore batter optimization feature
Jaehoon-You Aug 6, 2023
3820c52
virtual-device-app: Add SharedViewModel for managing reset popup
Jaehoon-You Aug 6, 2023
b3cdfb6
virtual-device-app: Implement main view scenario
Jaehoon-You Aug 6, 2023
058bace
virtual-device-app: Implement qrcode view scenario
Jaehoon-You Aug 6, 2023
4100dee
virtual-device-app: Implement loading view scenario
Jaehoon-You Aug 6, 2023
82ce4f8
virtual-device-app: Implement base fragment/viewmodel for device deta…
Jaehoon-You Aug 6, 2023
98ec339
virtual-device-app: Implement common layout
Jaehoon-You Aug 6, 2023
b74c055
virtual-device-app: Implement onoff switch scenario
Jaehoon-You Aug 6, 2023
87b7c6b
virtual-device-app: Apply feature:control navGraph
Jaehoon-You Aug 6, 2023
34ebb69
virtual-device-app: Add permissions about stack related
Jaehoon-You Aug 6, 2023
366f42d
Restyled by google-java-format
restyled-commits Aug 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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