diff --git a/field-smartphone/src/main/ic_launcher-playstore.png b/field-smartphone/src/main/ic_launcher-playstore.png index 91cd705..5ba317c 100644 Binary files a/field-smartphone/src/main/ic_launcher-playstore.png and b/field-smartphone/src/main/ic_launcher-playstore.png differ diff --git a/field-smartphone/src/main/java/kaist/iclab/field_tracker/DeviceInfo.kt b/field-smartphone/src/main/java/kaist/iclab/field_tracker/DeviceInfo.kt deleted file mode 100644 index 52ea8e9..0000000 --- a/field-smartphone/src/main/java/kaist/iclab/field_tracker/DeviceInfo.kt +++ /dev/null @@ -1,9 +0,0 @@ -package kaist.iclab.field_tracker - -interface Util { - fun getDeviceUUID(): String - fun getOSVersion(): String - fun getDeviceModel(): String - fun getDeviceName(): String - fun getAppVersion(): String -} \ No newline at end of file diff --git a/field-smartphone/src/main/java/kaist/iclab/field_tracker/KoinModule.kt b/field-smartphone/src/main/java/kaist/iclab/field_tracker/KoinModule.kt index ca9e671..37dbff7 100644 --- a/field-smartphone/src/main/java/kaist/iclab/field_tracker/KoinModule.kt +++ b/field-smartphone/src/main/java/kaist/iclab/field_tracker/KoinModule.kt @@ -3,66 +3,63 @@ package kaist.iclab.field_tracker import kaist.iclab.field_tracker.ui.AbstractMainViewModel import kaist.iclab.field_tracker.ui.MainViewModelImpl import kaist.iclab.tracker.Tracker -import kaist.iclab.tracker.collectors.* +import kaist.iclab.tracker.TrackerUtil +import kaist.iclab.tracker.collectors.AmbientLightCollector import kaist.iclab.tracker.controller.CollectorControllerInterface -import kaist.iclab.tracker.database.DatabaseInterface -import kaist.iclab.tracker.database.TempDBImpl +import kaist.iclab.tracker.controller.CollectorInterface import kaist.iclab.tracker.notf.NotfManagerInterface import kaist.iclab.tracker.permission.PermissionManagerInterface import org.koin.android.ext.koin.androidContext -import org.koin.core.module.dsl.viewModel import org.koin.core.module.dsl.singleOf import org.koin.dsl.module - val appModule = module { + // singleOf(::ActivityTransitionCollector) + singleOf(::AmbientLightCollector) +// singleOf(::AppUsageLogCollector) +// singleOf(::BatteryCollector) +// singleOf(::CallLogCollector) +// singleOf(::DataTrafficStatCollector) +// singleOf(::LocationCollector) +// singleOf(::MessageLogCollector) +// singleOf(::NotificationCollector) +// singleOf(::ScreenCollector) +// singleOf(::UserInteractionCollector) +// singleOf(::WiFiScanCollector) + + single> { + mapOf( +// "ActivityTransitionCollector" to get(), + "AmbientLight" to get(), +// "AppUsageLogCollector" to get(), +// "BatteryCollector" to get(), +// "CallLogCollector" to get(), +// "DataTrafficStatCollector" to get(), +// "LocationCollector" to get(), +// "MessageLogCollector" to get(), +// "NotificationCollector" to get(), +// "ScreenCollector" to get(), +// "UserInteractionCollector" to get(), +// "WiFiScanCollector" to get() + ) + } + single { Tracker.getCollectorController() } - single { - Tracker.getPermissionManager() - } single { Tracker.getNotfManager() } - single { - TempDBImpl(androidContext()) + single { + Tracker.getPermissionManager() } - singleOf(::ActivityTransitionCollector) - singleOf(::AmbientLightCollector) - singleOf(::AppUsageLogCollector) - singleOf(::BatteryCollector) - singleOf(::CallLogCollector) - singleOf(::DataTrafficStatCollector) - singleOf(::LocationCollector) - singleOf(::MessageLogCollector) - singleOf(::NotificationCollector) - singleOf(::ScreenCollector) - singleOf(::UserInteractionCollector) - singleOf(::WiFiScanCollector) + single { + TrackerUtil(androidContext()) + } - viewModel { - val collectors = mapOf( - "ActivityTransitionCollector" to get(), - "AmbientLightCollector" to get(), - "AppUsageLogCollector" to get(), - "BatteryCollector" to get(), - "CallLogCollector" to get(), - "DataTrafficStatCollector" to get(), - "LocationCollector" to get(), - "MessageLogCollector" to get(), - "NotificationCollector" to get(), - "ScreenCollector" to get(), - "UserInteractionCollector" to get(), - "WiFiScanCollector" to get() - ) - MainViewModelImpl( - get(), - get(), - get(), - collectors - ) + single{ + MainViewModelImpl(get(), get(), get>().keys.toTypedArray()) } } diff --git a/field-smartphone/src/main/java/kaist/iclab/field_tracker/MainApplication.kt b/field-smartphone/src/main/java/kaist/iclab/field_tracker/MainApplication.kt index 63bd201..89b1d76 100644 --- a/field-smartphone/src/main/java/kaist/iclab/field_tracker/MainApplication.kt +++ b/field-smartphone/src/main/java/kaist/iclab/field_tracker/MainApplication.kt @@ -3,7 +3,8 @@ package kaist.iclab.field_tracker import android.app.Application import android.util.Log import kaist.iclab.tracker.Tracker -import kaist.iclab.tracker.controller.CollectorControllerInterface +import kaist.iclab.tracker.controller.AbstractCollector +import kaist.iclab.tracker.controller.CollectorInterface import kaist.iclab.tracker.notf.NotfManagerInterface import org.koin.android.ext.android.get import org.koin.android.ext.koin.androidContext @@ -13,27 +14,27 @@ import org.koin.core.context.startKoin class MainApplication: Application(){ override fun onCreate() { super.onCreate() - Tracker.initialize(this@MainApplication) startKoin { androidLogger() androidContext(this@MainApplication) modules(appModule) } - initConfiguration() +// Tracker.initialize(this@MainApplication, get(), get(), get()) +// initConfiguration() } - fun initConfiguration() { - val collectorController = get() - collectorController.collectors.forEach { collector -> - collector.listener = { data -> - Log.d(collector.NAME, "Data: $data") - } - } - - val notfManager = get() - notfManager.setServiceNotfDescription( - icon = R.drawable.ic_notf - ) - } +// fun initConfiguration() { +// val collectorMap = get>() +// collectorMap.forEach { (name, collector) -> +// collector.listener = { data -> +// Log.d(collector.NAME, "Data: $data") +// } +// } +// +// val notfManager = get() +// notfManager.setServiceNotfDescription( +// icon = R.drawable.ic_notf +// ) +// } } \ No newline at end of file diff --git a/field-smartphone/src/main/java/kaist/iclab/field_tracker/database/DatabaseInterface.kt b/field-smartphone/src/main/java/kaist/iclab/field_tracker/database/DatabaseInterface.kt index 3c1c69b..1bcae69 100644 --- a/field-smartphone/src/main/java/kaist/iclab/field_tracker/database/DatabaseInterface.kt +++ b/field-smartphone/src/main/java/kaist/iclab/field_tracker/database/DatabaseInterface.kt @@ -1,7 +1,5 @@ package kaist.iclab.tracker.database -import android.net.Uri -import kaist.iclab.tracker.collectors.AbstractCollector import kotlinx.coroutines.flow.Flow interface DatabaseInterface { @@ -21,9 +19,10 @@ interface DatabaseInterface { // /*TODO: maybe UI required*/ // fun export() // -// /*Update config including their enabled/disabled*/ + /*Update config including their enabled/disabled*/ // fun updateConfig(name: String, config: AbstractCollector.Config) - + fun updateConfig(name: String, value: Boolean) + fun getConfigFlow(): Flow> } \ No newline at end of file diff --git a/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/AbstractMainViewModel.kt b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/AbstractMainViewModel.kt index 31e8baf..0fbb837 100644 --- a/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/AbstractMainViewModel.kt +++ b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/AbstractMainViewModel.kt @@ -1,26 +1,9 @@ package kaist.iclab.field_tracker.ui import androidx.lifecycle.ViewModel -import kaist.iclab.tracker.collectors.AbstractCollector -import kotlinx.coroutines.flow.MutableStateFlow +import kaist.iclab.tracker.controller.CollectorConfig +import kaist.iclab.tracker.controller.CollectorInterface +import kaist.iclab.tracker.controller.CollectorState import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -abstract class AbstractMainViewModel: ViewModel() { - abstract val _isRunningState: MutableStateFlow - val isRunningState: StateFlow - get() = _isRunningState.asStateFlow() - - abstract val collectorMap: Map - abstract val _enabledCollectors: MutableStateFlow> - val enabledCollectors: StateFlow> - get() = _enabledCollectors.asStateFlow() - - abstract fun start() - abstract fun stop() - abstract fun enable(name: String) - abstract fun disable(name: String) - - abstract fun sync() - abstract fun delete() -} \ No newline at end of file +abstract class AbstractMainViewModel: ViewModel(), MainViewModelInterface diff --git a/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainScreen.kt b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainScreen.kt index 12471cd..7168dca 100644 --- a/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainScreen.kt +++ b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainScreen.kt @@ -1,6 +1,5 @@ package kaist.iclab.field_tracker.ui -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -8,205 +7,333 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.DirectionsRun -import androidx.compose.material.icons.filled.Pause -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.filled.Sync -import androidx.compose.material.icons.rounded.Delete -import androidx.compose.material.icons.rounded.ExpandLess -import androidx.compose.material.icons.rounded.ExpandMore -import androidx.compose.material.icons.rounded.PlayArrow -import androidx.compose.material.icons.rounded.Stop -import androidx.compose.material.icons.rounded.Upload -import androidx.compose.material3.Button +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import kaist.iclab.field_tracker.ui.theme.TrackerTheme +import kaist.iclab.tracker.controller.CollectorConfig +import kaist.iclab.tracker.controller.CollectorState import org.koin.androidx.compose.koinViewModel @Composable fun MainScreen(viewModel: AbstractMainViewModel = koinViewModel()) { - val isRunning = viewModel.isRunningState.collectAsStateWithLifecycle() + val isRunning = viewModel.controllerStateFlow.collectAsStateWithLifecycle() + val collectorStates = viewModel.collectorStateFlow.collectAsStateWithLifecycle( + viewModel.collectors.map{ it to CollectorState(CollectorState.FLAG.UNAVAILABLE, "Not initialized") }.toMap() + ) + val collectorConfigs = viewModel.configFlow.collectAsStateWithLifecycle( + viewModel.collectors.map{ it to CollectorConfig() }.toMap() + ) - Column { - CollectorControllerUI(isRunning.value, viewModel) - LazyColumn( - modifier = Modifier.fillMaxWidth() - ) { - items( - viewModel.collectorMap.keys.toList() - ) { item -> - Collector(item, viewModel) - } - } - } -} + Column( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + Header() + Section( + contents = listOf( + { + SettingRow( + title = "Run Tracker", + displayStatus = false, + displayToggle = true, + toggleStatus = isRunning.value, + onClick = {if(isRunning.value) viewModel.stop() else viewModel.start()} + ) + } + ) + ) +// Section( +// contents = listOf( +// { +// SettingRow( +// title = "Users", +// status = "testing@ic.kaist.ac.kr", +// displayStatus = true, +// displayToggle = false, +// onClick = {} +// ) +// } +// ) +// ) -@Composable -fun Collector(name: String, viewModel: AbstractMainViewModel) { - val enabledCollectors = viewModel.enabledCollectors.collectAsStateWithLifecycle() - val (expanded, setExpand) = remember { mutableStateOf(true) } - Column { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier - .padding(16.dp) - .fillMaxWidth() - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - IconButton(onClick = { setExpand(!expanded) }) { - Icon( - - imageVector = if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore, - contentDescription = "expand", - tint = Color.Gray, - modifier = Modifier - .width(32.dp) - .height(32.dp) + Section( + contents = viewModel.collectors.map{ name -> + { + SettingRow( + title = name, + displayStatus = false, + displayToggle = true, + toggleStatus = collectorStates.value[name]?.flag == CollectorState.FLAG.ENABLED, + onClick = {if(collectorStates.value[name]?.flag == CollectorState.FLAG.ENABLED) + viewModel.disableCollector(name) else viewModel.enableCollector(name)} ) } - Icon( - imageVector = Icons.Default.DirectionsRun, - contentDescription = name, - tint = Color.Black, - modifier = Modifier.size(24.dp) - ) - Text(text = name, fontSize = 16.sp) } - Switch( - checked = enabledCollectors.value[name] ?: false, - onCheckedChange = { - if (it) { - viewModel.enable(name) - } else { - viewModel.disable(name) - } + ) + +// Section( +//// viewModel.collectorMap.keys.toList(). +// contents = listOf( +// { +// SettingRow( +// title = "External Devices", +// status = "Galaxy Watch, Polar H10", +// displayStatus = true, +// displayToggle = true, +// onClick = {} +// ) +// } +// ) +// ) + +// Section( +// contents = listOf( +// { +// SettingRow( +// title = "Conduct Experiment", +// status = "beta-testing", +// displayStatus = true, +// displayToggle = true, +// onClick = {} +// ) +// }, +// { +// SettingRow( +// title = "Server URL", +// status= "abc.kaist.ac.kr", +// displayStatus = true, +// displayToggle = false, +// onClick = {} +// ) +// }, +// { +// SettingRow( +// title = "Sync", +// status= "3 hours", +// displayStatus = true, +// displayToggle = true, +// onClick = {} +// ) +// }, +// { +// SettingRow( +// title = "Delete", +// displayStatus = false, +// displayToggle = false, +// onClick = {} +// ) +// } +// ) +// ) + + Section( + contents = listOf( + { + SettingRow( + title = "Version", + status = viewModel.getAppVersion(), + displayStatus = true, + displayToggle = false, + onClick = {} + ) }, - colors = SwitchDefaults.colors( - checkedThumbColor = Color(0xFF0B57D0), // Blue thumb when checked - checkedTrackColor = Color(0xFFD3E3FD), // Blue track when checked - uncheckedThumbColor = Color.White, // White thumb when unchecked - uncheckedTrackColor = Color(0xFFC0C0C0), // Light blue track when unchecked - uncheckedBorderColor = Color.Transparent, // No border for unchecked state - checkedBorderColor = Color.Transparent // No border for checked state - ), - thumbContent = { - Box( - modifier = Modifier - .size(16.dp) // Increase thumb size - .background(Color.Transparent, CircleShape) // Ensure thumb is circular + { + SettingRow( + title = "Device", + status= viewModel.getDeviceInfo(), + displayStatus = true, + displayToggle = false, + onClick = {} ) - } + }, + { + SettingRow( + title = "License", + displayStatus = false, + displayToggle = false, + onClick = {} + ) + }, ) - } - - if (expanded) { - Row( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), - ) { - Text(text = "Hello") - } + ) - } } +} +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Header() { + TopAppBar( + title = { + Text(text = "Tracker Configuration", + fontWeight = FontWeight.SemiBold) + }, + actions = { + IconButton(onClick = { /* Handle menu click */ }) { + Icon( + imageVector = Icons.Default.Menu, // This is the standard menu icon + contentDescription = "Menu" + ) + } + }, + colors = TopAppBarColors( + containerColor = Color.White, + titleContentColor = Color.Black, + actionIconContentColor = Color.Black, + scrolledContainerColor = Color.White, + navigationIconContentColor = Color.Black + ) + ) +} +@Composable +fun CustomDivider( + modifier: Modifier, + isHorizontal: Boolean = true +) { + if (isHorizontal) { + HorizontalDivider( + modifier = modifier, + thickness = 1.dp, + color = Color(0xFFEAEAEA) + ) + } else { + VerticalDivider( + modifier = modifier, + thickness = 1.dp, + color = Color(0xFFEAEAEA) + ) + } } @Composable -fun CollectorControllerUI(isRunning: Boolean, viewModel: AbstractMainViewModel) { - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, +fun Section( + contents: List<@Composable () -> Unit> +) { + Column( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp) + .clip(RoundedCornerShape(18.dp)) + .background(Color.White) + .padding(horizontal = 18.dp) + ) { - // Sync Button (left of Play/Pause) - IconButton( - onClick = {}, - modifier = Modifier.size(40.dp) - ) { - Icon( - imageVector = Icons.Default.Sync, - contentDescription = "Sync", - tint = Color.Gray, - modifier = Modifier.size(24.dp) - ) + contents.forEachIndexed { index, item -> + if (index > 0) { + CustomDivider(modifier = Modifier + .fillMaxWidth()) +// .padding(horizontal = 18.dp)) + } + item() } + } +} - // Play/Pause Button (centered as main action) - IconButton( - onClick = { - if (isRunning) { - viewModel.stop() - } else { - viewModel.start() - } - }, - modifier = Modifier - .size(40.dp) - .background(Color(0xFF0B57D0), CircleShape) - ) { - Icon( - imageVector = if (isRunning) Icons.Default.Pause else Icons.Default.PlayArrow, - contentDescription = if (isRunning) "Pause" else "Start", - tint = Color.White, - modifier = Modifier.size(28.dp) - ) - } - // Flush Button - IconButton( - onClick = {}, - modifier = Modifier.size(40.dp) - ) { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Flush", - tint = Color.Gray, - modifier = Modifier.size(24.dp) +@Composable +fun CustomToggle( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, +) { + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + colors = SwitchDefaults.colors( + checkedThumbColor = Color.White, // Blue thumb when checked + checkedTrackColor = Color(0xFF3579FF), // Blue track when checked + uncheckedThumbColor = Color.White, // White thumb when unchecked + uncheckedTrackColor = Color(0xFF9A999E), // Light blue track when unchecked + uncheckedBorderColor = Color.Transparent, // No border for unchecked state + checkedBorderColor = Color.Transparent // No border for checked state + ), + thumbContent = { + Box( + modifier = Modifier + .size(16.dp) // Increase thumb size + .background(Color.Transparent, CircleShape) // Ensure thumb is circular ) } - } + ) } -@Preview(showBackground = true) @Composable -fun Preview() { - MainScreen(MainViewModelFakeImpl()) +fun SettingRow( + title: String, + status: String = "", + onClick: () -> Unit, + toggleStatus: Boolean = false, + displayStatus: Boolean, + displayToggle: Boolean, +) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 14.dp) + .clickable { onClick() }, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + text = title, + fontSize = 18.sp, + ) + if (displayStatus) { + Text( + text = status, + fontSize = 12.sp, + color = Color(0xFF8E8D92) + ) + } + } + if (displayToggle) { + Row(verticalAlignment = Alignment.CenterVertically) { + CustomDivider(modifier = Modifier.height(36.dp), isHorizontal = false) + Spacer(modifier = Modifier.width(8.dp)) + CustomToggle(toggleStatus) { onClick() } + } + } + } + } } + +//@Preview(showBackground = true) +//@Composable +//fun Preview() { +// MainScreen(MainViewModelFakeImpl()) +//} diff --git a/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelFakeImpl.kt b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelFakeImpl.kt index 4de74de..a471c6d 100644 --- a/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelFakeImpl.kt +++ b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelFakeImpl.kt @@ -1,52 +1,58 @@ -package kaist.iclab.field_tracker.ui - -import android.util.Log -import kaist.iclab.tracker.collectors.AbstractCollector -import kaist.iclab.tracker.collectors.BatteryCollector -import kotlinx.coroutines.flow.MutableStateFlow - -class MainViewModelFakeImpl( - -) : AbstractMainViewModel() { - companion object{ - const val TAG = "MainViewModelFakeImpl" - } - - override val collectorMap: Map = mapOf() - override val _enabledCollectors: MutableStateFlow> - = MutableStateFlow( - mapOf("Battery" to false) - ) - - override val _isRunningState = MutableStateFlow(false) - - - override fun start() { - _isRunningState.value = true - } - - override fun stop() { - _isRunningState.value = false - } - - override fun enable(name: String) { - _enabledCollectors.value = _enabledCollectors.value.toMutableMap().apply { - this[name] = true - } - - } - - override fun disable(name: String) { - _enabledCollectors.value = _enabledCollectors.value.toMutableMap().apply { - this[name] = false - } - } - - override fun sync() { - Log.d(TAG, "SYNC") - } - - override fun delete() { - Log.d(TAG, "delete") - } -} \ No newline at end of file +//package kaist.iclab.field_tracker.ui +// +//import kaist.iclab.tracker.controller.AbstractCollector +//import kotlinx.coroutines.flow.MutableStateFlow +// +//class MainViewModelFakeImpl( +// +//) : AbstractMainViewModel() { +// companion object{ +// const val TAG = "MainViewModelFakeImpl" +// } +// +// override val collectorMap: Map = mapOf() +// override val _enabledCollectors: MutableStateFlow> +// = MutableStateFlow( +// mapOf("Battery" to false) +// ) +// +// override val _isRunningState = MutableStateFlow(false) +// +// +// override fun start() { +// _isRunningState.value = true +// } +// +// override fun stop() { +// _isRunningState.value = false +// } +// +// override fun enable(name: String) { +// _enabledCollectors.value = _enabledCollectors.value.toMutableMap().apply { +// this[name] = true +// } +// +// } +// +// override fun disable(name: String) { +// _enabledCollectors.value = _enabledCollectors.value.toMutableMap().apply { +// this[name] = false +// } +// } +// +//// override fun sync() { +//// Log.d(TAG, "SYNC") +//// } +//// +//// override fun delete() { +//// Log.d(TAG, "delete") +//// } +// +// override fun getDeviceInfo(): String { +// return "Device Info" +// } +// +// override fun getAppVersion(): String { +// return "App Version" +// } +//} \ No newline at end of file diff --git a/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelImpl.kt b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelImpl.kt index 575c7c8..026313f 100644 --- a/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelImpl.kt +++ b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelImpl.kt @@ -1,48 +1,22 @@ package kaist.iclab.field_tracker.ui -import android.util.Log -import androidx.lifecycle.viewModelScope -import kaist.iclab.tracker.collectors.AbstractCollector +import kaist.iclab.tracker.TrackerUtil import kaist.iclab.tracker.controller.CollectorControllerInterface -import kaist.iclab.tracker.database.DatabaseInterface -import kaist.iclab.tracker.permission.PermissionManagerInterface -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch +import kaist.iclab.tracker.controller.CollectorInterface class MainViewModelImpl( private val collectorController: CollectorControllerInterface, - private val permissionManager: PermissionManagerInterface, - private val database: DatabaseInterface, - override val collectorMap: Map -) : AbstractMainViewModel() { - + private val trackerUtil: TrackerUtil, + override val collectors: Array +): AbstractMainViewModel() { companion object { const val TAG = "MainViewModelImpl" } - override val _isRunningState: MutableStateFlow = MutableStateFlow(false) - override val _enabledCollectors: MutableStateFlow> = MutableStateFlow( - collectorMap.map { it.key to false }.toMap() - ) - - - init { - Log.d(TAG, "isRunning: ${_isRunningState.value}") - viewModelScope.launch { - Log.d(TAG, "INITIALIZED") - collectorController.isRunningFlow().collect { - Log.d(TAG, "isRunningState: $it") - _isRunningState.value = it - } - } - viewModelScope.launch { - database.getConfigFlow().collect { - Log.d(TAG, "enabledCollectors: $it") - _enabledCollectors.value = it - } - } - } + override val controllerStateFlow = collectorController.stateFlow + override val collectorStateFlow = collectorController.collectorStateFlow + override val configFlow = collectorController.configFlow override fun start() { collectorController.start() @@ -52,31 +26,27 @@ class MainViewModelImpl( collectorController.stop() } - override fun enable(name: String) { - collectorMap[name]?.let { collector-> - collectorController.add(collector) - collector.enable(permissionManager){ - if(it){ - _enabledCollectors.value = _enabledCollectors.value.toMutableMap().apply { - this[name] = true - } - } - } - - } + override fun enableCollector(name: String) { + collectorController.enableCollector(name) } - override fun disable(name: String) { - collectorMap[name]?.let { collector-> - collectorController.remove(collector) - } + override fun disableCollector(name: String) { + collectorController.disableCollector(name) } - override fun sync() { - throw NotImplementedError("Not implemented") + override fun getDeviceInfo(): String { + return trackerUtil.getDeviceModel() } - override fun delete() { - throw NotImplementedError("Not implemented") + override fun getAppVersion(): String { + return trackerUtil.getAppVersion() } + + // override fun sync() { +// throw NotImplementedError("Not implemented") +// } +// +// override fun delete() { +// throw NotImplementedError("Not implemented") +// } } \ No newline at end of file diff --git a/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelInterface.kt b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelInterface.kt new file mode 100644 index 0000000..98f7a36 --- /dev/null +++ b/field-smartphone/src/main/java/kaist/iclab/field_tracker/ui/MainViewModelInterface.kt @@ -0,0 +1,27 @@ +package kaist.iclab.field_tracker.ui + +import kaist.iclab.tracker.controller.CollectorConfig +import kaist.iclab.tracker.controller.CollectorInterface +import kaist.iclab.tracker.controller.CollectorState +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface MainViewModelInterface { + val collectors: Array + val controllerStateFlow: StateFlow + + val collectorStateFlow: Flow> + val configFlow: Flow> + + fun start() + fun stop() + fun enableCollector(name: String) + fun disableCollector(name: String) + + fun getDeviceInfo(): String + fun getAppVersion(): String + + +// fun sync() +// fun delete() +} \ No newline at end of file diff --git a/field-smartphone/src/main/res/drawable-hdpi/ic_notf.png b/field-smartphone/src/main/res/drawable-hdpi/ic_notf.png index 61350f5..986d076 100644 Binary files a/field-smartphone/src/main/res/drawable-hdpi/ic_notf.png and b/field-smartphone/src/main/res/drawable-hdpi/ic_notf.png differ diff --git a/field-smartphone/src/main/res/drawable-mdpi/ic_notf.png b/field-smartphone/src/main/res/drawable-mdpi/ic_notf.png index 680bfce..91a5b1d 100644 Binary files a/field-smartphone/src/main/res/drawable-mdpi/ic_notf.png and b/field-smartphone/src/main/res/drawable-mdpi/ic_notf.png differ diff --git a/field-smartphone/src/main/res/drawable-xhdpi/ic_notf.png b/field-smartphone/src/main/res/drawable-xhdpi/ic_notf.png index 0b37350..044af02 100644 Binary files a/field-smartphone/src/main/res/drawable-xhdpi/ic_notf.png and b/field-smartphone/src/main/res/drawable-xhdpi/ic_notf.png differ diff --git a/field-smartphone/src/main/res/drawable-xxhdpi/ic_notf.png b/field-smartphone/src/main/res/drawable-xxhdpi/ic_notf.png index 17fb4a5..0f67649 100644 Binary files a/field-smartphone/src/main/res/drawable-xxhdpi/ic_notf.png and b/field-smartphone/src/main/res/drawable-xxhdpi/ic_notf.png differ diff --git a/field-smartphone/src/main/res/drawable-xxxhdpi/ic_notf.png b/field-smartphone/src/main/res/drawable-xxxhdpi/ic_notf.png index 968b211..77e1b96 100644 Binary files a/field-smartphone/src/main/res/drawable-xxxhdpi/ic_notf.png and b/field-smartphone/src/main/res/drawable-xxxhdpi/ic_notf.png differ diff --git a/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher.webp b/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher.webp index c566880..5505cdf 100644 Binary files a/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher.webp and b/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp index 8506333..936fd28 100644 Binary files a/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and b/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher_round.webp index 12b9c67..3d9614d 100644 Binary files a/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/field-smartphone/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher.webp b/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher.webp index 0d234e3..b9ce0be 100644 Binary files a/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher.webp and b/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp index 41e8450..d78ec03 100644 Binary files a/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and b/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 1ed677c..af29288 100644 Binary files a/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/field-smartphone/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher.webp b/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher.webp index 262515a..3cd1159 100644 Binary files a/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp index ec55e52..b177dfd 100644 Binary files a/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and b/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index fe94217..a65e049 100644 Binary files a/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/field-smartphone/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher.webp index 388c3cd..0b34540 100644 Binary files a/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp index b5bb8ad..5274a98 100644 Binary files a/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and b/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 10bdd1c..781e2bd 100644 Binary files a/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/field-smartphone/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index 915b389..05cc549 100644 Binary files a/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp index e780d11..57e6fa2 100644 Binary files a/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and b/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index c6cafcd..952ab81 100644 Binary files a/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/field-smartphone/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/lab-smartphone/src/main/java/kaist/iclab/lab_galaxywatch_tracker/data/WearableDataCollector.kt b/lab-smartphone/src/main/java/kaist/iclab/lab_galaxywatch_tracker/data/WearableDataCollector.kt index 3c866a9..98f0b61 100644 --- a/lab-smartphone/src/main/java/kaist/iclab/lab_galaxywatch_tracker/data/WearableDataCollector.kt +++ b/lab-smartphone/src/main/java/kaist/iclab/lab_galaxywatch_tracker/data/WearableDataCollector.kt @@ -5,7 +5,7 @@ import com.google.android.gms.wearable.DataClient import com.google.android.gms.wearable.DataEventBuffer import com.google.android.gms.wearable.DataMapItem import com.google.android.gms.wearable.Wearable -import kaist.iclab.tracker.collectors.AbstractCollector +import kaist.iclab.tracker.controller.AbstractCollector import kaist.iclab.tracker.database.DatabaseInterface class WearableDataCollector( diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/Tracker.kt b/tracker-library/src/main/java/kaist/iclab/tracker/Tracker.kt index ce8ecdf..a80f2e0 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/Tracker.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/Tracker.kt @@ -23,21 +23,39 @@ object Tracker { private var notfManagerInterface: NotfManagerInterface? = null @Synchronized - fun initialize(context: Context, permissionManager_: PermissionManagerInterface) { - if (collectorController?.get() == null) { + fun initialize(context: Context, + permissionManager_: PermissionManagerInterface, + notfManager_: NotfManagerInterface, + collectorController_: CollectorControllerInterface){ + if (permissionManager?.get() == null) { permissionManager = WeakReference(permissionManager_) - val collectorController_ = CollectorControllerImpl(context.applicationContext) + } + if (collectorController?.get() == null) { collectorController = WeakReference(collectorController_) } if(notfManagerInterface == null){ - notfManagerInterface = NotfManagerImpl() + notfManagerInterface = notfManager_ notfManagerInterface?.createServiceNotfChannel(context) } + } +// if (collectorController?.get() == null) { +// permissionManager = WeakReference(permissionManager_) +// val collectorController_ = CollectorControllerImpl(context.applicationContext) +// collectorController = WeakReference(collectorController_) +// } +// if(notfManagerInterface == null){ +// notfManagerInterface = NotfManagerImpl() +// notfManagerInterface?.createServiceNotfChannel(context) +// } +// } @Synchronized - fun initialize(context: Context, ){ - initialize(context, PermissionManagerImpl(context)) + fun initialize(context: Context){ + val notfManager_ = NotfManagerImpl() + notfManager_.createServiceNotfChannel(context) + initialize(context,PermissionManagerImpl(context),notfManager_, CollectorControllerImpl(context, emptyMap())) + } fun getCollectorController(): CollectorControllerInterface { diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/TrackerUtil.kt b/tracker-library/src/main/java/kaist/iclab/tracker/TrackerUtil.kt new file mode 100644 index 0000000..d7205cf --- /dev/null +++ b/tracker-library/src/main/java/kaist/iclab/tracker/TrackerUtil.kt @@ -0,0 +1,35 @@ +package kaist.iclab.tracker + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.os.Build +import android.provider.Settings + +class TrackerUtil( + private val context: Context) { + fun getDeviceModel() = Build.MODEL +// Device Name (e.g., Galaxy S10) is hard to retrieve + fun getAppVersion(): String { + return try { + val pInfo = context.packageManager.getPackageInfo(context.packageName,0) + return pInfo.versionName + } catch (e: Exception) { + "Unknown" + } + } + fun getOSVersion() = Build.VERSION.RELEASE + fun getDeviceUUID(): String = Settings.Secure.ANDROID_ID + fun isPreinstalledApp(): String { + return try { + val pInfo = context.packageManager.getPackageInfo(context.packageName, 0) + if(pInfo.applicationInfo.flags and (ApplicationInfo.FLAG_SYSTEM or + ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0){ + return "PREINSTALLED" + }else{ + return "USER_INSTALLED" + } + } catch (e: Exception) { + return "UNKNOWN" + } + } +} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AbstractCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AbstractCollector.kt deleted file mode 100644 index 8f021fc..0000000 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AbstractCollector.kt +++ /dev/null @@ -1,56 +0,0 @@ -package kaist.iclab.tracker.collectors - -import android.content.Context -import android.util.Log -import kaist.iclab.tracker.permission.PermissionManagerInterface - -abstract class AbstractCollector( - open val context: Context -) { - abstract val permissions: Array - abstract val foregroundServiceTypes: Array - open val TAG: String = this::class.simpleName ?: "UnnamedClass" - open val NAME: String = extractName(this::class.simpleName ?: "UnknownCollector") - - var listener: ((DataEntity)-> Unit)? = null - - abstract class Config - abstract class DataEntity - - /* Check whether the system allow to collect data - * In case of sensor malfunction or broken, it would not be available.*/ - abstract fun isAvailable(): Boolean - - /* Enable the collector by checking and requesting permissions - * Different with `isAvailable`, `enable` is used to request permissions when - * the collector is available, but does not have permission - * */ - fun enable(permissionManager: PermissionManagerInterface, onResult: (granted: Boolean)-> Unit) { - permissionManager.request(permissions){ - Log.d(TAG, "Permission granted: ${permissions.all { permission -> it[permission] == true }}") - onResult(permissions.all { permission -> it[permission] == true }) - } - } - - - /* Start collector to collect data - * */ - abstract fun start() - - /* Stop collector to stop collecting data - * */ - abstract fun stop() - - - private fun extractName(className: String): String { - // Replace "Collector" with an empty string - val nameWithoutCollector = className.replace("Collector", "") - - // Split the name into parts based on camel case and underscores - val parts = nameWithoutCollector.split("(?=\\p{Upper})|_|(?<=\\p{Lower})(?=\\p{Upper})".toRegex()) - - // Join the parts and convert to uppercase - return parts.joinToString("_").uppercase() - } - -} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/ActivityTransitionCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/ActivityTransitionCollector.kt index 0f5be4b..8d4f2cc 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/ActivityTransitionCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/ActivityTransitionCollector.kt @@ -1,106 +1,106 @@ -package kaist.iclab.tracker.collectors - -import android.Manifest -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager -import android.net.wifi.WifiManager -import android.os.Build -import com.google.android.gms.location.ActivityRecognition -import com.google.android.gms.location.ActivityRecognitionClient -import com.google.android.gms.location.ActivityTransition -import com.google.android.gms.location.ActivityTransitionRequest -import com.google.android.gms.location.ActivityTransitionResult -import com.google.android.gms.location.DetectedActivity -import kaist.iclab.tracker.triggers.SystemBroadcastTrigger -import java.util.concurrent.TimeUnit - -class ActivityTransitionCollector( - override val context: Context -) : AbstractCollector(context) { - - val ACTION = "kaist.iclab.tracker.${NAME}_REQUEST" - val CODE = 0xF1 - - data class DataEntity( - val timestamp: Long, - val activityType: Int, - val transitionType: Int - ) : AbstractCollector.DataEntity() - - override val permissions = listOfNotNull( - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACTIVITY_RECOGNITION else null, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else null - ).toTypedArray() - - override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() - - - override fun isAvailable(): Boolean = true - - private val client: ActivityRecognitionClient by lazy { - ActivityRecognition.getClient(context) - } - - private val activityTransitionIntent by lazy { - PendingIntent.getBroadcast( - context, CODE, - Intent(ACTION), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - } - - private val broadcastTrigger = SystemBroadcastTrigger( - context, - arrayOf( - ACTION - ) - ) { - val result = ActivityTransitionResult.extractResult(it) - result?.transitionEvents?.forEach { event -> - listener?.invoke( - DataEntity( - System.currentTimeMillis(), - event.activityType, - event.transitionType - ) - ) - } - } - - - override fun start() { - val request = listOf( - DetectedActivity.IN_VEHICLE, - DetectedActivity.ON_BICYCLE, - DetectedActivity.RUNNING, - DetectedActivity.ON_FOOT, - DetectedActivity.STILL, - DetectedActivity.WALKING - ).map { activity -> - listOf( - ActivityTransition.Builder() - .setActivityType(activity) - .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER) - .build(), - ActivityTransition.Builder() - .setActivityType(activity) - .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT) - .build() - ) - }.flatten().let { ActivityTransitionRequest(it) } - client.requestActivityTransitionUpdates(request, activityTransitionIntent) - broadcastTrigger.register() - } - - override fun stop() { - client.removeActivityTransitionUpdates(activityTransitionIntent) - broadcastTrigger.unregister() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.Manifest +//import android.app.PendingIntent +//import android.content.Context +//import android.content.Intent +//import android.hardware.Sensor +//import android.hardware.SensorEvent +//import android.hardware.SensorEventListener +//import android.hardware.SensorManager +//import android.net.wifi.WifiManager +//import android.os.Build +//import com.google.android.gms.location.ActivityRecognition +//import com.google.android.gms.location.ActivityRecognitionClient +//import com.google.android.gms.location.ActivityTransition +//import com.google.android.gms.location.ActivityTransitionRequest +//import com.google.android.gms.location.ActivityTransitionResult +//import com.google.android.gms.location.DetectedActivity +//import kaist.iclab.tracker.triggers.SystemBroadcastTrigger +//import java.util.concurrent.TimeUnit +// +//class ActivityTransitionCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// val ACTION = "kaist.iclab.tracker.${NAME}_REQUEST" +// val CODE = 0xF1 +// +// data class DataEntity( +// val timestamp: Long, +// val activityType: Int, +// val transitionType: Int +// ) : AbstractCollector.DataEntity() +// +// override val permissions = listOfNotNull( +// Manifest.permission.ACCESS_COARSE_LOCATION, +// Manifest.permission.ACCESS_FINE_LOCATION, +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACTIVITY_RECOGNITION else null, +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else null +// ).toTypedArray() +// +// override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() +// +// +// override fun isAvailable(): Boolean = true +// +// private val client: ActivityRecognitionClient by lazy { +// ActivityRecognition.getClient(context) +// } +// +// private val activityTransitionIntent by lazy { +// PendingIntent.getBroadcast( +// context, CODE, +// Intent(ACTION), +// PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE +// ) +// } +// +// private val broadcastTrigger = SystemBroadcastTrigger( +// context, +// arrayOf( +// ACTION +// ) +// ) { +// val result = ActivityTransitionResult.extractResult(it) +// result?.transitionEvents?.forEach { event -> +// listener?.invoke( +// DataEntity( +// System.currentTimeMillis(), +// event.activityType, +// event.transitionType +// ) +// ) +// } +// } +// +// +// override fun start() { +// val request = listOf( +// DetectedActivity.IN_VEHICLE, +// DetectedActivity.ON_BICYCLE, +// DetectedActivity.RUNNING, +// DetectedActivity.ON_FOOT, +// DetectedActivity.STILL, +// DetectedActivity.WALKING +// ).map { activity -> +// listOf( +// ActivityTransition.Builder() +// .setActivityType(activity) +// .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER) +// .build(), +// ActivityTransition.Builder() +// .setActivityType(activity) +// .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT) +// .build() +// ) +// }.flatten().let { ActivityTransitionRequest(it) } +// client.requestActivityTransitionUpdates(request, activityTransitionIntent) +// broadcastTrigger.register() +// } +// +// override fun stop() { +// client.removeActivityTransitionUpdates(activityTransitionIntent) +// broadcastTrigger.unregister() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AmbientLightCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AmbientLightCollector.kt index 0292a43..74060cc 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AmbientLightCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AmbientLightCollector.kt @@ -5,48 +5,84 @@ import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager +import kaist.iclab.tracker.controller.AbstractCollector +import kaist.iclab.tracker.controller.Availability +import kaist.iclab.tracker.controller.CollectorConfig +import kaist.iclab.tracker.controller.CollectorState +import kaist.iclab.tracker.controller.DataEntity +import kaist.iclab.tracker.permission.PermissionManagerInterface import java.util.concurrent.TimeUnit class AmbientLightCollector( - override val context: Context -) : AbstractCollector(context) { - var config: Config = Config( - TimeUnit.MINUTES.toMillis(3), - ) - - data class DataEntity( - val timestamp: Long, - val accuracy: Int, - val value: Float - ) : AbstractCollector.DataEntity() - + val context: Context, + permissionManager: PermissionManagerInterface, +) : AbstractCollector(permissionManager) { + override val permissions = listOfNotNull().toTypedArray() + override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() data class Config( val interval: Long - ) : AbstractCollector.Config() + ) : CollectorConfig() + override val defaultConfig = Config( + TimeUnit.SECONDS.toMillis(3) + ) - override val permissions = listOfNotNull().toTypedArray() + // Check whether there is a light sensor + override fun isAvailable(): Availability { + val status = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) != null + return Availability(status, if (status) null else "AmbientLight Sensor is not available") + } - override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() + override fun enable() { + if (_stateFlow.value.flag == CollectorState.FLAG.DISABLED) { + _stateFlow.tryEmit(CollectorState(CollectorState.FLAG.ENABLED)) + } + } - // Check whether there is a light sensor - override fun isAvailable(): Boolean { - return sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) != null + override fun disable() { + if (_stateFlow.value.flag == CollectorState.FLAG.ENABLED) { + _stateFlow.tryEmit(CollectorState(CollectorState.FLAG.DISABLED)) + } } - private val sensorManager: SensorManager by lazy { + override fun start() { + _stateFlow.tryEmit(CollectorState(CollectorState.FLAG.RUNNING)) + sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)?.let { sensor -> + sensorManager.registerListener( + sensorEventListener, + sensor, + TimeUnit.MILLISECONDS.toMicros(configFlow.value.interval).toInt() + ) + } + } + + override fun stop() { + sensorManager.unregisterListener(sensorEventListener) + _stateFlow.tryEmit(CollectorState(CollectorState.FLAG.ENABLED)) + } + + data class Entity( + override val received: Long, + val timestamp: Long, + val accuracy: Int, + val value: Float + ) : DataEntity(received) + + private val sensorManager: SensorManager by lazy{ context.getSystemService(Context.SENSOR_SERVICE) as SensorManager } + private val sensorEventListener = object:SensorEventListener { override fun onAccuracyChanged(sensor: Sensor?, p1: Int) {} - override fun onSensorChanged(event: SensorEvent?) { + val timestamp = System.currentTimeMillis() event?.let { listener?.invoke( - DataEntity( - System.currentTimeMillis(), + Entity( + timestamp, + it.timestamp, it.accuracy, it.values[0] ) @@ -55,17 +91,5 @@ class AmbientLightCollector( } } - override fun start() { - sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)?.let { sensor -> - sensorManager.registerListener( - sensorEventListener, - sensor, - TimeUnit.MILLISECONDS.toMicros(config.interval).toInt() - ) - } - } - override fun stop() { - sensorManager.unregisterListener(sensorEventListener) - } } \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AppUsageLogCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AppUsageLogCollector.kt index 4f434c9..7523f54 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AppUsageLogCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/AppUsageLogCollector.kt @@ -1,79 +1,80 @@ -package kaist.iclab.tracker.collectors - -import android.Manifest -import android.app.PendingIntent -import android.app.usage.UsageEvents -import android.app.usage.UsageStatsManager -import android.content.Context -import android.content.Intent -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager -import android.net.wifi.WifiManager -import android.os.Build -import com.google.android.gms.location.ActivityRecognition -import com.google.android.gms.location.ActivityRecognitionClient -import com.google.android.gms.location.ActivityTransition -import com.google.android.gms.location.ActivityTransitionRequest -import com.google.android.gms.location.ActivityTransitionResult -import com.google.android.gms.location.DetectedActivity -import kaist.iclab.tracker.triggers.AlarmTrigger -import kaist.iclab.tracker.triggers.SystemBroadcastTrigger -import java.util.concurrent.TimeUnit - -class AppUsageLogCollector( - override val context: Context -) : AbstractCollector(context) { - - val ACTION = "kaist.iclab.tracker.${NAME}_REQUEST" - val CODE = 0xEE - - var config = Config( - TimeUnit.MINUTES.toMillis(30) - ) - - data class Config( - val interval: Long, - ) : AbstractCollector.Config() - - data class DataEntity( - val timestamp: Long, - val packageName: String, - val eventType: Int - ) : AbstractCollector.DataEntity() - - override val permissions = listOfNotNull( - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) Manifest.permission.PACKAGE_USAGE_STATS else null, - ).toTypedArray() - - override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() - - override fun isAvailable(): Boolean = true - - private val alarmTrigger = AlarmTrigger( - context, - ACTION, - CODE, - config.interval - ){ - val usageStatManager =context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager - val timestamp = System.currentTimeMillis() - /*Give margin for alarm amy not correctly given*/ - val events = usageStatManager.queryEvents(timestamp - config.interval - TimeUnit.MINUTES.toMillis(5), timestamp) - val event = UsageEvents.Event() - while(events.hasNextEvent()){ - events.getNextEvent(event) - listener?.invoke(DataEntity(event.timeStamp, event.packageName, event.eventType)) - } - } - - - override fun start() { - alarmTrigger.register() - } - - override fun stop() { - alarmTrigger.unregister() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.Manifest +//import android.app.PendingIntent +//import android.app.usage.UsageEvents +//import android.app.usage.UsageStatsManager +//import android.content.Context +//import android.content.Intent +//import android.hardware.Sensor +//import android.hardware.SensorEvent +//import android.hardware.SensorEventListener +//import android.hardware.SensorManager +//import android.net.wifi.WifiManager +//import android.os.Build +//import com.google.android.gms.location.ActivityRecognition +//import com.google.android.gms.location.ActivityRecognitionClient +//import com.google.android.gms.location.ActivityTransition +//import com.google.android.gms.location.ActivityTransitionRequest +//import com.google.android.gms.location.ActivityTransitionResult +//import com.google.android.gms.location.DetectedActivity +//import kaist.iclab.tracker.triggers.AlarmTrigger +//import kaist.iclab.tracker.triggers.SystemBroadcastTrigger +//import java.util.concurrent.TimeUnit +// +//class AppUsageLogCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// val ACTION = "kaist.iclab.tracker.${NAME}_REQUEST" +// val CODE = 0xEE +// +// var config = Config( +// TimeUnit.MINUTES.toMillis(30) +// ) +// +// data class Config( +// val interval: Long, +// ) : AbstractCollector.Config() +// +// data class DataEntity( +// val timestamp: Long, +// val packageName: String, +//// val installed: String, +// val eventType: Int +// ) : AbstractCollector.DataEntity() +// +// override val permissions = listOfNotNull( +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) Manifest.permission.PACKAGE_USAGE_STATS else null, +// ).toTypedArray() +// +// override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() +// +// override fun isAvailable(): Boolean = true +// +// private val alarmTrigger = AlarmTrigger( +// context, +// ACTION, +// CODE, +// config.interval +// ){ +// val usageStatManager =context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager +// val timestamp = System.currentTimeMillis() +// /*Give margin for alarm amy not correctly given*/ +// val events = usageStatManager.queryEvents(timestamp - config.interval - TimeUnit.MINUTES.toMillis(5), timestamp) +// val event = UsageEvents.Event() +// while(events.hasNextEvent()){ +// events.getNextEvent(event) +// listener?.invoke(DataEntity(event.timeStamp, event.packageName, event.eventType)) +// } +// } +// +// +// override fun start() { +// alarmTrigger.register() +// } +// +// override fun stop() { +// alarmTrigger.unregister() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/BatteryCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/BatteryCollector.kt index 3f6ea46..3e1d148 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/BatteryCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/BatteryCollector.kt @@ -1,50 +1,50 @@ -package kaist.iclab.tracker.collectors - -import android.content.Context -import android.content.Intent -import android.os.BatteryManager -import kaist.iclab.tracker.triggers.SystemBroadcastTrigger - -class BatteryCollector( - override val context: Context -) : AbstractCollector(context) { - - data class DataEntity( - val timestamp: Long, - val connectedType: Int, - val status: Int, - val level: Int, - val temperature: Int - ): AbstractCollector.DataEntity() - - // No permission required for it - override val permissions: Array = emptyArray() - override val foregroundServiceTypes: Array = emptyArray() - - val trigger: SystemBroadcastTrigger = SystemBroadcastTrigger( - context, - arrayOf( - Intent.ACTION_BATTERY_CHANGED - ) - ){ intent -> - listener?.invoke(DataEntity( - System.currentTimeMillis(), - intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1), - intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1), - intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1), - intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1) - )) - - } - - // Access to Battery Status might be supported for all android systems - override fun isAvailable(): Boolean = true - - override fun start() { - trigger.register() - } - - override fun stop() { - trigger.unregister() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.content.Context +//import android.content.Intent +//import android.os.BatteryManager +//import kaist.iclab.tracker.triggers.SystemBroadcastTrigger +// +//class BatteryCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// data class DataEntity( +// val timestamp: Long, +// val connectedType: Int, +// val status: Int, +// val level: Int, +// val temperature: Int +// ): AbstractCollector.DataEntity() +// +// // No permission required for it +// override val permissions: Array = emptyArray() +// override val foregroundServiceTypes: Array = emptyArray() +// +// val trigger: SystemBroadcastTrigger = SystemBroadcastTrigger( +// context, +// arrayOf( +// Intent.ACTION_BATTERY_CHANGED +// ) +// ){ intent -> +// listener?.invoke(DataEntity( +// System.currentTimeMillis(), +// intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1), +// intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1), +// intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1), +// intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1) +// )) +// +// } +// +// // Access to Battery Status might be supported for all android systems +// override fun isAvailable(): Boolean = true +// +// override fun start() { +// trigger.register() +// } +// +// override fun stop() { +// trigger.unregister() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/CallLogCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/CallLogCollector.kt index 6040037..178d1f0 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/CallLogCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/CallLogCollector.kt @@ -1,86 +1,86 @@ -package kaist.iclab.tracker.collectors - -import android.Manifest -import android.content.Context -import android.provider.CallLog -import kaist.iclab.tracker.triggers.AlarmTrigger -import java.util.concurrent.TimeUnit - -class CallLogCollector( - override val context: Context -) : AbstractCollector(context) { - - var config: Config = Config( - TimeUnit.MINUTES.toMillis(30) - ) - data class DataEntity( - val timestamp: Long, - val duration: Long, - val number: String, - val type: Int - ) : AbstractCollector.DataEntity() - - data class Config( - val interval: Long - ) : AbstractCollector.Config() - - - // No permission required for it - override val permissions: Array = arrayOf( - Manifest.permission.READ_CONTACTS, - Manifest.permission.READ_CALL_LOG - ) - override val foregroundServiceTypes: Array = emptyArray() - - - - // Access to Battery Status might be supported for all android systems - override fun isAvailable(): Boolean = true - - val trigger = AlarmTrigger( - context, - "kaist.iclab.tracker.${NAME}_REQUEST", - 0x11, - config.interval - ){ - val current = System.currentTimeMillis() - val from = current - config.interval - TimeUnit.MINUTES.toMillis(5) - val cursor = context.contentResolver.query( - CallLog.Calls.CONTENT_URI, - arrayOf( - CallLog.Calls.DATE, - CallLog.Calls.NUMBER, - CallLog.Calls.DURATION, - CallLog.Calls.TYPE - ), - "${CallLog.Calls.DATE} BETWEEN ? AND ?", - arrayOf(from.toString(), current.toString()), - CallLog.Calls.DATE + " DESC" - ) - cursor?.use { - while (it.moveToNext()) { - val timestamp = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DATE)) - val number = it.getString(it.getColumnIndexOrThrow(CallLog.Calls.NUMBER)) - val duration = it.getString(it.getColumnIndexOrThrow(CallLog.Calls.DURATION)) - val type = it.getInt(it.getColumnIndexOrThrow(CallLog.Calls.TYPE)) - listener?.invoke( - DataEntity( - timestamp, - duration.toLong(), - number, - type - ) - ) - } - } - cursor?.close() - } - - override fun start() { - trigger.register() - } - - override fun stop() { - trigger.unregister() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.Manifest +//import android.content.Context +//import android.provider.CallLog +//import kaist.iclab.tracker.triggers.AlarmTrigger +//import java.util.concurrent.TimeUnit +// +//class CallLogCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// var config: Config = Config( +// TimeUnit.MINUTES.toMillis(30) +// ) +// data class DataEntity( +// val timestamp: Long, +// val duration: Long, +// val number: String, +// val type: Int +// ) : AbstractCollector.DataEntity() +// +// data class Config( +// val interval: Long +// ) : AbstractCollector.Config() +// +// +// // No permission required for it +// override val permissions: Array = arrayOf( +// Manifest.permission.READ_CONTACTS, +// Manifest.permission.READ_CALL_LOG +// ) +// override val foregroundServiceTypes: Array = emptyArray() +// +// +// +// // Access to Battery Status might be supported for all android systems +// override fun isAvailable(): Boolean = true +// +// val trigger = AlarmTrigger( +// context, +// "kaist.iclab.tracker.${NAME}_REQUEST", +// 0x11, +// config.interval +// ){ +// val current = System.currentTimeMillis() +// val from = current - config.interval - TimeUnit.MINUTES.toMillis(5) +// val cursor = context.contentResolver.query( +// CallLog.Calls.CONTENT_URI, +// arrayOf( +// CallLog.Calls.DATE, +// CallLog.Calls.NUMBER, +// CallLog.Calls.DURATION, +// CallLog.Calls.TYPE +// ), +// "${CallLog.Calls.DATE} BETWEEN ? AND ?", +// arrayOf(from.toString(), current.toString()), +// CallLog.Calls.DATE + " DESC" +// ) +// cursor?.use { +// while (it.moveToNext()) { +// val timestamp = it.getLong(it.getColumnIndexOrThrow(CallLog.Calls.DATE)) +// val number = it.getString(it.getColumnIndexOrThrow(CallLog.Calls.NUMBER)) +// val duration = it.getString(it.getColumnIndexOrThrow(CallLog.Calls.DURATION)) +// val type = it.getInt(it.getColumnIndexOrThrow(CallLog.Calls.TYPE)) +// listener?.invoke( +// DataEntity( +// timestamp, +// duration.toLong(), +// number, +// type +// ) +// ) +// } +// } +// cursor?.close() +// } +// +// override fun start() { +// trigger.register() +// } +// +// override fun stop() { +// trigger.unregister() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/DataTrafficStatCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/DataTrafficStatCollector.kt index 6d4f3ea..5a4ab32 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/DataTrafficStatCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/DataTrafficStatCollector.kt @@ -1,60 +1,60 @@ -package kaist.iclab.tracker.collectors - -import android.content.Context -import android.net.TrafficStats -import kaist.iclab.tracker.collectors.LocationCollector.Config -import kaist.iclab.tracker.triggers.AlarmTrigger -import java.util.concurrent.TimeUnit - -class DataTrafficStatCollector( - override val context: Context -) : AbstractCollector(context) { - - val ACTION = "kaist.iclab.tracker.ACTION_DATA_TRAFFIC_STAT" - val CODE = 0x1 - - var config: Config = Config( - TimeUnit.MINUTES.toMillis(3) - ) - - data class DataEntity( - val timestamp: Long, - val totalRx: Long, - val totalTx: Long, - val mobileRx: Long, - val mobileTx: Long, - ) : AbstractCollector.DataEntity() - - data class Config( - val interval: Long, - ) : AbstractCollector.Config() - - override val permissions = listOfNotNull().toTypedArray() - - override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() - - - override fun isAvailable(): Boolean = true - - private val alarmTrigger = AlarmTrigger(context, ACTION, CODE, - config.interval) { - val timestamp = System.currentTimeMillis() - listener?.invoke( - DataEntity( - timestamp, - TrafficStats.getTotalRxBytes(), - TrafficStats.getTotalTxBytes(), - TrafficStats.getMobileRxBytes(), - TrafficStats.getMobileTxBytes(), - ) - ) - } - - override fun start() { - alarmTrigger.register() - } - - override fun stop() { - alarmTrigger.unregister() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.content.Context +//import android.net.TrafficStats +//import kaist.iclab.tracker.collectors.LocationCollector.Config +//import kaist.iclab.tracker.triggers.AlarmTrigger +//import java.util.concurrent.TimeUnit +// +//class DataTrafficStatCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// val ACTION = "kaist.iclab.tracker.ACTION_DATA_TRAFFIC_STAT" +// val CODE = 0x1 +// +// var config: Config = Config( +// TimeUnit.MINUTES.toMillis(3) +// ) +// +// data class DataEntity( +// val timestamp: Long, +// val totalRx: Long, +// val totalTx: Long, +// val mobileRx: Long, +// val mobileTx: Long, +// ) : AbstractCollector.DataEntity() +// +// data class Config( +// val interval: Long, +// ) : AbstractCollector.Config() +// +// override val permissions = listOfNotNull().toTypedArray() +// +// override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() +// +// +// override fun isAvailable(): Boolean = true +// +// private val alarmTrigger = AlarmTrigger(context, ACTION, CODE, +// config.interval) { +// val timestamp = System.currentTimeMillis() +// listener?.invoke( +// DataEntity( +// timestamp, +// TrafficStats.getTotalRxBytes(), +// TrafficStats.getTotalTxBytes(), +// TrafficStats.getMobileRxBytes(), +// TrafficStats.getMobileTxBytes(), +// ) +// ) +// } +// +// override fun start() { +// alarmTrigger.register() +// } +// +// override fun stop() { +// alarmTrigger.unregister() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/LocationCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/LocationCollector.kt index c219d91..021db50 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/LocationCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/LocationCollector.kt @@ -1,133 +1,133 @@ -package kaist.iclab.tracker.collectors - -import android.Manifest -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.content.pm.ServiceInfo -import android.location.LocationManager -import android.os.Build -import android.util.Log -import com.google.android.gms.location.FusedLocationProviderClient -import com.google.android.gms.location.LocationRequest -import com.google.android.gms.location.LocationResult -import com.google.android.gms.location.LocationServices -import com.google.android.gms.location.Priority -import kaist.iclab.tracker.triggers.SystemBroadcastTrigger -import java.util.concurrent.TimeUnit - -class LocationCollector( - override val context: Context -) : AbstractCollector(context) { - - val ACTION = "android.intent.action.LOCATION_CHANGED" - - var config: Config = Config( - TimeUnit.MINUTES.toMillis(3), - 0, - TimeUnit.MINUTES.toMillis(10), - 0.0f, - 0, - Priority.PRIORITY_HIGH_ACCURACY - ) - - data class DataEntity( - val timestamp: Long, - val latitude: Double, - val longitude: Double, - val altitude: Double, - val speed: Float, - val accuracy: Float - ) : AbstractCollector.DataEntity() - - - data class Config( - val interval: Long, - val maxUpdateAge: Long, - val maxUpdateDelay: Long, - val minUpdateDistance: Float, - val minUpdateInterval: Long, - val priority: Int - ) : AbstractCollector.Config() - - - override val permissions = listOfNotNull( - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else null - ).toTypedArray() - - override val foregroundServiceTypes: Array = listOfNotNull( - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION else null - ).toTypedArray() - - lateinit var trigger: SystemBroadcastTrigger - - // Check whether there is at least one location provider - override fun isAvailable(): Boolean { - val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager - val pm = context.packageManager - - // Check if the device has GPS hardware - val hasGpsHardware = pm.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS) - - // Check if any location provider is enabled (GPS or Network) - val locationEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || - locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) - - // Return true if the device has GPS hardware and location providers are enabled - return hasGpsHardware && locationEnabled - } - - private val client: FusedLocationProviderClient by lazy { - LocationServices.getFusedLocationProviderClient(context) - } - - private val intent: PendingIntent by lazy { - PendingIntent.getBroadcast( - context, - 0xFF, - Intent(ACTION), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - } - - override fun start() { - trigger = SystemBroadcastTrigger( - context, - arrayOf(ACTION) - ) { intent -> - if (ACTION != intent.action) { - Log.e(TAG, "Invalid action: ${intent.action}") - } - val location = LocationResult.extractResult(intent)?.lastLocation - ?: return@SystemBroadcastTrigger - listener?.invoke( - DataEntity( - location.time, - location.longitude, - location.latitude, - location.altitude, - location.speed, - location.accuracy, - ) - ) - } - trigger.register() - - val request = LocationRequest.Builder(config.interval) - .setMaxUpdateDelayMillis(config.maxUpdateDelay) - .setMinUpdateDistanceMeters(config.minUpdateDistance) - .setMaxUpdateAgeMillis(config.maxUpdateAge) - .setMaxUpdateDelayMillis(config.maxUpdateDelay) - .setPriority(Priority.PRIORITY_HIGH_ACCURACY) - .build() - client.requestLocationUpdates(request, intent) - } - - override fun stop() { - trigger.unregister() - client.removeLocationUpdates(intent) - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.Manifest +//import android.app.PendingIntent +//import android.content.Context +//import android.content.Intent +//import android.content.pm.PackageManager +//import android.content.pm.ServiceInfo +//import android.location.LocationManager +//import android.os.Build +//import android.util.Log +//import com.google.android.gms.location.FusedLocationProviderClient +//import com.google.android.gms.location.LocationRequest +//import com.google.android.gms.location.LocationResult +//import com.google.android.gms.location.LocationServices +//import com.google.android.gms.location.Priority +//import kaist.iclab.tracker.triggers.SystemBroadcastTrigger +//import java.util.concurrent.TimeUnit +// +//class LocationCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// val ACTION = "android.intent.action.LOCATION_CHANGED" +// +// var config: Config = Config( +// TimeUnit.MINUTES.toMillis(3), +// 0, +// TimeUnit.MINUTES.toMillis(10), +// 0.0f, +// 0, +// Priority.PRIORITY_HIGH_ACCURACY +// ) +// +// data class DataEntity( +// val timestamp: Long, +// val latitude: Double, +// val longitude: Double, +// val altitude: Double, +// val speed: Float, +// val accuracy: Float +// ) : AbstractCollector.DataEntity() +// +// +// data class Config( +// val interval: Long, +// val maxUpdateAge: Long, +// val maxUpdateDelay: Long, +// val minUpdateDistance: Float, +// val minUpdateInterval: Long, +// val priority: Int +// ) : AbstractCollector.Config() +// +// +// override val permissions = listOfNotNull( +// Manifest.permission.ACCESS_COARSE_LOCATION, +// Manifest.permission.ACCESS_FINE_LOCATION, +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else null +// ).toTypedArray() +// +// override val foregroundServiceTypes: Array = listOfNotNull( +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION else null +// ).toTypedArray() +// +// lateinit var trigger: SystemBroadcastTrigger +// +// // Check whether there is at least one location provider +// override fun isAvailable(): Boolean { +// val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager +// val pm = context.packageManager +// +// // Check if the device has GPS hardware +// val hasGpsHardware = pm.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS) +// +// // Check if any location provider is enabled (GPS or Network) +// val locationEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || +// locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) +// +// // Return true if the device has GPS hardware and location providers are enabled +// return hasGpsHardware && locationEnabled +// } +// +// private val client: FusedLocationProviderClient by lazy { +// LocationServices.getFusedLocationProviderClient(context) +// } +// +// private val intent: PendingIntent by lazy { +// PendingIntent.getBroadcast( +// context, +// 0xFF, +// Intent(ACTION), +// PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE +// ) +// } +// +// override fun start() { +// trigger = SystemBroadcastTrigger( +// context, +// arrayOf(ACTION) +// ) { intent -> +// if (ACTION != intent.action) { +// Log.e(TAG, "Invalid action: ${intent.action}") +// } +// val location = LocationResult.extractResult(intent)?.lastLocation +// ?: return@SystemBroadcastTrigger +// listener?.invoke( +// DataEntity( +// location.time, +// location.longitude, +// location.latitude, +// location.altitude, +// location.speed, +// location.accuracy, +// ) +// ) +// } +// trigger.register() +// +// val request = LocationRequest.Builder(config.interval) +// .setMaxUpdateDelayMillis(config.maxUpdateDelay) +// .setMinUpdateDistanceMeters(config.minUpdateDistance) +// .setMaxUpdateAgeMillis(config.maxUpdateAge) +// .setMaxUpdateDelayMillis(config.maxUpdateDelay) +// .setPriority(Priority.PRIORITY_HIGH_ACCURACY) +// .build() +// client.requestLocationUpdates(request, intent) +// } +// +// override fun stop() { +// trigger.unregister() +// client.removeLocationUpdates(intent) +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/MessageLogCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/MessageLogCollector.kt index 2a212db..eea1f53 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/MessageLogCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/MessageLogCollector.kt @@ -1,133 +1,133 @@ -package kaist.iclab.tracker.collectors - -import android.Manifest -import android.content.ContentResolver -import android.content.Context -import android.database.Cursor -import android.net.Uri -import android.provider.CallLog -import android.provider.Telephony -import kaist.iclab.tracker.triggers.AlarmTrigger -import java.util.concurrent.TimeUnit - -class MessageLogCollector( - override val context: Context -) : AbstractCollector(context) { - - var config: Config = Config( - TimeUnit.MINUTES.toMillis(30) - ) - data class DataEntity( - val timestamp: Long, - val number: String, - val messageType: String, - val contactType: Int - ) : AbstractCollector.DataEntity() - - data class Config( - val interval: Long - ) : AbstractCollector.Config() - - - // No permission required for it - override val permissions: Array = arrayOf( - Manifest.permission.READ_CONTACTS, - Manifest.permission.READ_SMS - ) - override val foregroundServiceTypes: Array = emptyArray() - - - - // Access to Battery Status might be supported for all android systems - override fun isAvailable(): Boolean = true - - val trigger = AlarmTrigger( - context, - "kaist.iclab.tracker.${NAME}_REQUEST", - 0x13, - config.interval - ){ - val current = System.currentTimeMillis() - val from = current - config.interval - TimeUnit.MINUTES.toMillis(5) - var cursor = context.contentResolver.query( - Telephony.Sms.CONTENT_URI, - arrayOf( - Telephony.Sms.ADDRESS, // Sender/receiver number - Telephony.Sms.DATE, // Timestamp - Telephony.Sms.TYPE, // Type (Incoming/Outgoing) - ), - "${Telephony.Sms.DATE} BETWEEN ? AND ?", - arrayOf(from.toString(), current.toString()), - Telephony.Sms.DATE + " DESC" - ) - cursor?.use { - while (it.moveToNext()) { - val timestamp = it.getLong(it.getColumnIndexOrThrow(Telephony.Sms.DATE)) - val number = it.getString(it.getColumnIndexOrThrow(Telephony.Sms.ADDRESS)) - val type = it.getInt(it.getColumnIndexOrThrow(Telephony.Sms.TYPE)) - - listener?.invoke( - DataEntity( - timestamp, - number, - "SMS", - type - ) - ) - } - } - cursor?.close() - cursor = context.contentResolver.query( - Telephony.Mms.CONTENT_URI, - arrayOf( - Telephony.Mms._ID, // MMS ID - Telephony.Mms.DATE, // Timestamp - Telephony.Mms.MESSAGE_BOX // Type (Incoming/Outgoing) - ), - "${Telephony.Mms.DATE} BETWEEN ? AND ?", - arrayOf(from.toString(), current.toString()), - Telephony.Mms.DATE + " DESC" - ) - cursor?.use { - while (it.moveToNext()) { - val number = it.getLong(it.getColumnIndexOrThrow(Telephony.Mms._ID)) - val timestamp = it.getLong(it.getColumnIndexOrThrow(Telephony.Mms.DATE)) * 1000 // MMS date is in seconds - val type = it.getInt(it.getColumnIndexOrThrow(Telephony.Mms.MESSAGE_BOX)) - - - listener?.invoke( - DataEntity( - timestamp, - getMmsAddress(context.contentResolver, number) ?: "UNKNOWN", - "MMS", - type - ) - ) - } - } - cursor?.close() - } - - fun getMmsAddress(contentResolver: ContentResolver, mmsId: Long): String? { - // Build the URI for the MMS address table using the MMS ID - val uri = Uri.parse("content://mms/$mmsId/addr") - val projection = arrayOf(Telephony.Mms.Addr.ADDRESS) - - val cursor: Cursor? = contentResolver.query(uri, projection, null,null, null) - - cursor?.use { - if (it.moveToFirst()) { - return it.getString(it.getColumnIndexOrThrow(Telephony.Mms.Addr.ADDRESS)) - } - } - return null - } - - override fun start() { - trigger.register() - } - - override fun stop() { - trigger.unregister() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.Manifest +//import android.content.ContentResolver +//import android.content.Context +//import android.database.Cursor +//import android.net.Uri +//import android.provider.CallLog +//import android.provider.Telephony +//import kaist.iclab.tracker.triggers.AlarmTrigger +//import java.util.concurrent.TimeUnit +// +//class MessageLogCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// var config: Config = Config( +// TimeUnit.MINUTES.toMillis(30) +// ) +// data class DataEntity( +// val timestamp: Long, +// val number: String, +// val messageType: String, +// val contactType: Int +// ) : AbstractCollector.DataEntity() +// +// data class Config( +// val interval: Long +// ) : AbstractCollector.Config() +// +// +// // No permission required for it +// override val permissions: Array = arrayOf( +// Manifest.permission.READ_CONTACTS, +// Manifest.permission.READ_SMS +// ) +// override val foregroundServiceTypes: Array = emptyArray() +// +// +// +// // Access to Battery Status might be supported for all android systems +// override fun isAvailable(): Boolean = true +// +// val trigger = AlarmTrigger( +// context, +// "kaist.iclab.tracker.${NAME}_REQUEST", +// 0x13, +// config.interval +// ){ +// val current = System.currentTimeMillis() +// val from = current - config.interval - TimeUnit.MINUTES.toMillis(5) +// var cursor = context.contentResolver.query( +// Telephony.Sms.CONTENT_URI, +// arrayOf( +// Telephony.Sms.ADDRESS, // Sender/receiver number +// Telephony.Sms.DATE, // Timestamp +// Telephony.Sms.TYPE, // Type (Incoming/Outgoing) +// ), +// "${Telephony.Sms.DATE} BETWEEN ? AND ?", +// arrayOf(from.toString(), current.toString()), +// Telephony.Sms.DATE + " DESC" +// ) +// cursor?.use { +// while (it.moveToNext()) { +// val timestamp = it.getLong(it.getColumnIndexOrThrow(Telephony.Sms.DATE)) +// val number = it.getString(it.getColumnIndexOrThrow(Telephony.Sms.ADDRESS)) +// val type = it.getInt(it.getColumnIndexOrThrow(Telephony.Sms.TYPE)) +// +// listener?.invoke( +// DataEntity( +// timestamp, +// number, +// "SMS", +// type +// ) +// ) +// } +// } +// cursor?.close() +// cursor = context.contentResolver.query( +// Telephony.Mms.CONTENT_URI, +// arrayOf( +// Telephony.Mms._ID, // MMS ID +// Telephony.Mms.DATE, // Timestamp +// Telephony.Mms.MESSAGE_BOX // Type (Incoming/Outgoing) +// ), +// "${Telephony.Mms.DATE} BETWEEN ? AND ?", +// arrayOf(from.toString(), current.toString()), +// Telephony.Mms.DATE + " DESC" +// ) +// cursor?.use { +// while (it.moveToNext()) { +// val number = it.getLong(it.getColumnIndexOrThrow(Telephony.Mms._ID)) +// val timestamp = it.getLong(it.getColumnIndexOrThrow(Telephony.Mms.DATE)) * 1000 // MMS date is in seconds +// val type = it.getInt(it.getColumnIndexOrThrow(Telephony.Mms.MESSAGE_BOX)) +// +// +// listener?.invoke( +// DataEntity( +// timestamp, +// getMmsAddress(context.contentResolver, number) ?: "UNKNOWN", +// "MMS", +// type +// ) +// ) +// } +// } +// cursor?.close() +// } +// +// fun getMmsAddress(contentResolver: ContentResolver, mmsId: Long): String? { +// // Build the URI for the MMS address table using the MMS ID +// val uri = Uri.parse("content://mms/$mmsId/addr") +// val projection = arrayOf(Telephony.Mms.Addr.ADDRESS) +// +// val cursor: Cursor? = contentResolver.query(uri, projection, null,null, null) +// +// cursor?.use { +// if (it.moveToFirst()) { +// return it.getString(it.getColumnIndexOrThrow(Telephony.Mms.Addr.ADDRESS)) +// } +// } +// return null +// } +// +// override fun start() { +// trigger.register() +// } +// +// override fun stop() { +// trigger.unregister() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/NotificationCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/NotificationCollector.kt index 435a39a..404a00f 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/NotificationCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/NotificationCollector.kt @@ -1,112 +1,112 @@ -package kaist.iclab.tracker.collectors - -import android.Manifest -import android.app.PendingIntent -import android.app.usage.UsageEvents -import android.app.usage.UsageStatsManager -import android.content.Context -import android.content.Intent -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager -import android.net.wifi.WifiManager -import android.os.Build -import android.service.notification.NotificationListenerService -import android.service.notification.StatusBarNotification -import android.util.Log -import androidx.core.app.NotificationManagerCompat -import androidx.transition.Visibility -import com.google.android.gms.location.ActivityRecognition -import com.google.android.gms.location.ActivityRecognitionClient -import com.google.android.gms.location.ActivityTransition -import com.google.android.gms.location.ActivityTransitionRequest -import com.google.android.gms.location.ActivityTransitionResult -import com.google.android.gms.location.DetectedActivity -import kaist.iclab.tracker.triggers.AlarmTrigger -import kaist.iclab.tracker.triggers.SystemBroadcastTrigger -import java.util.concurrent.TimeUnit - -class NotificationCollector( - override val context: Context -) : AbstractCollector(context) { - - data class DataEntity( - val timestamp: Long, - val packageName: String, - val eventType: String, - val title: String, - val text: String, - val visibility: Int, - val category: String - ) : AbstractCollector.DataEntity() - - override val permissions = listOfNotNull( - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE else null - ).toTypedArray() - - override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() - - override fun isAvailable(): Boolean = - context.packageName in NotificationManagerCompat.getEnabledListenerPackages(context) - - object NotificationTrigger: NotificationListenerService() { - - var instance: NotificationTrigger? = null - var isListening = false - var listener: ((DataEntity) -> Unit)? = null - fun startListening() { - isListening = true - } - - fun stopListening() { - isListening = false - } - - override fun onCreate() { - super.onCreate() - instance = this - } - - override fun onDestroy() { - super.onDestroy() - instance = null - } - - override fun onNotificationPosted(sbn: StatusBarNotification) { - handleNotf(sbn, "POSTED") - } - - override fun onNotificationRemoved(sbn: StatusBarNotification) { - handleNotf(sbn, "REMOVED") - } - - private fun handleNotf(sbn: StatusBarNotification, eventType: String) { - if (isListening) { - listener?.invoke( - DataEntity( - System.currentTimeMillis(), - sbn.packageName, - eventType, - sbn.notification.extras.getString("android.title") ?: "", - sbn.notification.extras.getString("android.text") ?: "", - sbn.notification.visibility, - sbn.notification.category - ) - ) - } - } - } - - - override fun start() { - NotificationTrigger.instance?.let { - it.listener = listener - it.startListening() - } - } - - override fun stop() { - NotificationTrigger.instance?.stopListening() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.Manifest +//import android.app.PendingIntent +//import android.app.usage.UsageEvents +//import android.app.usage.UsageStatsManager +//import android.content.Context +//import android.content.Intent +//import android.hardware.Sensor +//import android.hardware.SensorEvent +//import android.hardware.SensorEventListener +//import android.hardware.SensorManager +//import android.net.wifi.WifiManager +//import android.os.Build +//import android.service.notification.NotificationListenerService +//import android.service.notification.StatusBarNotification +//import android.util.Log +//import androidx.core.app.NotificationManagerCompat +//import androidx.transition.Visibility +//import com.google.android.gms.location.ActivityRecognition +//import com.google.android.gms.location.ActivityRecognitionClient +//import com.google.android.gms.location.ActivityTransition +//import com.google.android.gms.location.ActivityTransitionRequest +//import com.google.android.gms.location.ActivityTransitionResult +//import com.google.android.gms.location.DetectedActivity +//import kaist.iclab.tracker.triggers.AlarmTrigger +//import kaist.iclab.tracker.triggers.SystemBroadcastTrigger +//import java.util.concurrent.TimeUnit +// +//class NotificationCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// data class DataEntity( +// val timestamp: Long, +// val packageName: String, +// val eventType: String, +// val title: String, +// val text: String, +// val visibility: Int, +// val category: String +// ) : AbstractCollector.DataEntity() +// +// override val permissions = listOfNotNull( +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE else null +// ).toTypedArray() +// +// override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() +// +// override fun isAvailable(): Boolean = +// context.packageName in NotificationManagerCompat.getEnabledListenerPackages(context) +// +// object NotificationTrigger: NotificationListenerService() { +// +// var instance: NotificationTrigger? = null +// var isListening = false +// var listener: ((DataEntity) -> Unit)? = null +// fun startListening() { +// isListening = true +// } +// +// fun stopListening() { +// isListening = false +// } +// +// override fun onCreate() { +// super.onCreate() +// instance = this +// } +// +// override fun onDestroy() { +// super.onDestroy() +// instance = null +// } +// +// override fun onNotificationPosted(sbn: StatusBarNotification) { +// handleNotf(sbn, "POSTED") +// } +// +// override fun onNotificationRemoved(sbn: StatusBarNotification) { +// handleNotf(sbn, "REMOVED") +// } +// +// private fun handleNotf(sbn: StatusBarNotification, eventType: String) { +// if (isListening) { +// listener?.invoke( +// DataEntity( +// System.currentTimeMillis(), +// sbn.packageName, +// eventType, +// sbn.notification.extras.getString("android.title") ?: "", +// sbn.notification.extras.getString("android.text") ?: "", +// sbn.notification.visibility, +// sbn.notification.category +// ) +// ) +// } +// } +// } +// +// +// override fun start() { +// NotificationTrigger.instance?.let { +// it.listener = listener +// it.startListening() +// } +// } +// +// override fun stop() { +// NotificationTrigger.instance?.stopListening() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/ScreenCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/ScreenCollector.kt index 6f323d6..016aaa0 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/ScreenCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/ScreenCollector.kt @@ -1,46 +1,46 @@ -package kaist.iclab.tracker.collectors - -import android.content.Context -import android.content.Intent -import kaist.iclab.tracker.triggers.SystemBroadcastTrigger - -class ScreenCollector( - override val context: Context -) : AbstractCollector(context) { - - data class DataEntity( - val timestamp: Long, - val type: String, - ) : AbstractCollector.DataEntity() - - override val permissions = listOfNotNull().toTypedArray() - override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() - - override fun isAvailable(): Boolean = true - - - private val broadcastTrigger = SystemBroadcastTrigger( - context, - arrayOf( - Intent.ACTION_SCREEN_ON, - Intent.ACTION_SCREEN_OFF, - Intent.ACTION_USER_PRESENT - ) - ) { intent -> - listener?.invoke( - DataEntity( - System.currentTimeMillis(), - intent.action ?: "UNKNOWN" - ) - ) - } - - - override fun start() { - broadcastTrigger.register() - } - - override fun stop() { - broadcastTrigger.unregister() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.content.Context +//import android.content.Intent +//import kaist.iclab.tracker.triggers.SystemBroadcastTrigger +// +//class ScreenCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// data class DataEntity( +// val timestamp: Long, +// val type: String, +// ) : AbstractCollector.DataEntity() +// +// override val permissions = listOfNotNull().toTypedArray() +// override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() +// +// override fun isAvailable(): Boolean = true +// +// +// private val broadcastTrigger = SystemBroadcastTrigger( +// context, +// arrayOf( +// Intent.ACTION_SCREEN_ON, +// Intent.ACTION_SCREEN_OFF, +// Intent.ACTION_USER_PRESENT +// ) +// ) { intent -> +// listener?.invoke( +// DataEntity( +// System.currentTimeMillis(), +// intent.action ?: "UNKNOWN" +// ) +// ) +// } +// +// +// override fun start() { +// broadcastTrigger.register() +// } +// +// override fun stop() { +// broadcastTrigger.unregister() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/UserInteractionCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/UserInteractionCollector.kt index d11e08f..e9a9776 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/UserInteractionCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/UserInteractionCollector.kt @@ -1,73 +1,73 @@ -package kaist.iclab.tracker.collectors - -import android.accessibilityservice.AccessibilityService -import android.content.Context -import android.net.TrafficStats -import android.view.KeyEvent -import android.view.accessibility.AccessibilityEvent -import kaist.iclab.tracker.collectors.LocationCollector.Config -import kaist.iclab.tracker.triggers.AlarmTrigger -import kotlinx.coroutines.sync.Mutex -import java.util.concurrent.TimeUnit - -class UserInteractionCollector( - override val context: Context -) : AbstractCollector(context) { - - - data class DataEntity( - val timestamp: Long, - val packageName: String, - val className: String, - val eventType: Int, - val text: String - ) : AbstractCollector.DataEntity() - - - override val permissions = listOfNotNull().toTypedArray() - - override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() - - - override fun isAvailable(): Boolean = true - - object MyAccessibilityService : AccessibilityService() { - var listener: ((DataEntity) -> Unit)? = null - var isListening = false - override fun onAccessibilityEvent(event: AccessibilityEvent?) { - if (isListening) { - event?.let { - listener?.invoke( - DataEntity( - System.currentTimeMillis(), - event.packageName.toString(), - event.className.toString(), - event.eventType, - event.text.toString() - ) - ) - } - } - } - - override fun onInterrupt() {} - - fun startListening() { - isListening = true - } - - fun stopListening() { - isListening = false - } - - } - - override fun start() { - MyAccessibilityService.listener = listener - MyAccessibilityService.startListening() - } - - override fun stop() { - MyAccessibilityService.stopListening() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.accessibilityservice.AccessibilityService +//import android.content.Context +//import android.net.TrafficStats +//import android.view.KeyEvent +//import android.view.accessibility.AccessibilityEvent +//import kaist.iclab.tracker.collectors.LocationCollector.Config +//import kaist.iclab.tracker.triggers.AlarmTrigger +//import kotlinx.coroutines.sync.Mutex +//import java.util.concurrent.TimeUnit +// +//class UserInteractionCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +// +// data class DataEntity( +// val timestamp: Long, +// val packageName: String, +// val className: String, +// val eventType: Int, +// val text: String +// ) : AbstractCollector.DataEntity() +// +// +// override val permissions = listOfNotNull().toTypedArray() +// +// override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() +// +// +// override fun isAvailable(): Boolean = true +// +// object MyAccessibilityService : AccessibilityService() { +// var listener: ((DataEntity) -> Unit)? = null +// var isListening = false +// override fun onAccessibilityEvent(event: AccessibilityEvent?) { +// if (isListening) { +// event?.let { +// listener?.invoke( +// DataEntity( +// System.currentTimeMillis(), +// event.packageName.toString(), +// event.className.toString(), +// event.eventType, +// event.text.toString() +// ) +// ) +// } +// } +// } +// +// override fun onInterrupt() {} +// +// fun startListening() { +// isListening = true +// } +// +// fun stopListening() { +// isListening = false +// } +// +// } +// +// override fun start() { +// MyAccessibilityService.listener = listener +// MyAccessibilityService.startListening() +// } +// +// override fun stop() { +// MyAccessibilityService.stopListening() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/WiFiScanCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/WiFiScanCollector.kt index 3183c3c..defbd2c 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/collectors/WiFiScanCollector.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/collectors/WiFiScanCollector.kt @@ -1,67 +1,67 @@ -package kaist.iclab.tracker.collectors - -import android.Manifest -import android.content.Context -import android.net.wifi.WifiManager -import android.os.Build -import kaist.iclab.tracker.triggers.SystemBroadcastTrigger - -class WiFiScanCollector( - override val context: Context -) : AbstractCollector(context) { - -// No Configuration! - - data class DataEntity( - val timestamp: Long, - val bssid: String, - val frequency: Int, - val level: Int - ) : AbstractCollector.DataEntity() - - override val permissions = listOfNotNull( - Manifest.permission.ACCESS_WIFI_STATE, - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else null - ).toTypedArray() - - override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() - - - override fun isAvailable(): Boolean { - return wifiManager.isWifiEnabled - } - - private val wifiManager: WifiManager by lazy { - context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager - } - - private val broadcastTrigger = SystemBroadcastTrigger( - context, - arrayOf( - WifiManager.SCAN_RESULTS_AVAILABLE_ACTION - ) - ) { - val results = wifiManager.scanResults - val timestamp = System.currentTimeMillis() - results.forEach { - listener?.invoke( - DataEntity( - timestamp, - it.BSSID, - it.frequency, - it.level, - ) - ) - } - } - - override fun start() { - broadcastTrigger.register() - } - - override fun stop() { - broadcastTrigger.unregister() - } -} \ No newline at end of file +//package kaist.iclab.tracker.collectors +// +//import android.Manifest +//import android.content.Context +//import android.net.wifi.WifiManager +//import android.os.Build +//import kaist.iclab.tracker.triggers.SystemBroadcastTrigger +// +//class WiFiScanCollector( +// override val context: Context +//) : AbstractCollector(context) { +// +//// No Configuration! +// +// data class DataEntity( +// val timestamp: Long, +// val bssid: String, +// val frequency: Int, +// val level: Int +// ) : AbstractCollector.DataEntity() +// +// override val permissions = listOfNotNull( +// Manifest.permission.ACCESS_WIFI_STATE, +// Manifest.permission.ACCESS_COARSE_LOCATION, +// Manifest.permission.ACCESS_FINE_LOCATION, +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else null +// ).toTypedArray() +// +// override val foregroundServiceTypes: Array = listOfNotNull().toTypedArray() +// +// +// override fun isAvailable(): Boolean { +// return wifiManager.isWifiEnabled +// } +// +// private val wifiManager: WifiManager by lazy { +// context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager +// } +// +// private val broadcastTrigger = SystemBroadcastTrigger( +// context, +// arrayOf( +// WifiManager.SCAN_RESULTS_AVAILABLE_ACTION +// ) +// ) { +// val results = wifiManager.scanResults +// val timestamp = System.currentTimeMillis() +// results.forEach { +// listener?.invoke( +// DataEntity( +// timestamp, +// it.BSSID, +// it.frequency, +// it.level, +// ) +// ) +// } +// } +// +// override fun start() { +// broadcastTrigger.register() +// } +// +// override fun stop() { +// broadcastTrigger.unregister() +// } +//} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/controller/AbstractCollector.kt b/tracker-library/src/main/java/kaist/iclab/tracker/controller/AbstractCollector.kt new file mode 100644 index 0000000..ba3710d --- /dev/null +++ b/tracker-library/src/main/java/kaist/iclab/tracker/controller/AbstractCollector.kt @@ -0,0 +1,87 @@ +package kaist.iclab.tracker.controller + +import android.util.Log +import kaist.iclab.tracker.permission.PermissionManagerInterface +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +abstract class AbstractCollector< + T: CollectorConfig, + K: DataEntity>( + val permissionManager: PermissionManagerInterface +): CollectorInterface { + init { + initState() + initConfig() + } + + protected val TAG: String = this::class.simpleName ?: "UnnamedClass" + override val NAME: String = extractName(this::class.simpleName ?: "UnknownCollector") + + abstract val defaultConfig: T + private val _configFlow + get() = MutableStateFlow(defaultConfig) + override val configFlow: StateFlow + get() = _configFlow.asStateFlow() + + protected fun initConfig(){ + _configFlow.tryEmit(defaultConfig) + } + + @Suppress("UNCHECKED_CAST") + override fun updateConfig(config: CollectorConfig) { + if(_stateFlow.value.flag == CollectorState.FLAG.RUNNING) throw IllegalStateException("Cannot update config while running.") + try{ + _configFlow.tryEmit(config as T) + } catch (e: ClassCastException) { + Log.e(TAG, "Invalid config type: ${config::class.simpleName}") + } + + } + override fun resetConfig() { + if(_stateFlow.value.flag == CollectorState.FLAG.RUNNING) throw IllegalStateException("Cannot update config while running.") + _configFlow.tryEmit(defaultConfig) + } + + + + protected val _stateFlow = MutableStateFlow(CollectorState(CollectorState.FLAG.UNAVAILABLE, "Not initialized")) + override val stateFlow: StateFlow + get() = _stateFlow.asStateFlow() + protected fun initState() { + val availability = isAvailable() + if(!availability.status) _stateFlow.tryEmit(CollectorState(CollectorState.FLAG.UNAVAILABLE, availability.reason)) + else if(!permissionManager.isPermissionsGranted(permissions)) _stateFlow.tryEmit( + CollectorState(CollectorState.FLAG.PERMISSION_REQUIRED)) + else _stateFlow.tryEmit(CollectorState(CollectorState.FLAG.ENABLED)) + } + + /* Check whether the system allow to collect data + * In case of sensor malfunction or broken, it would not be available.*/ + abstract fun isAvailable(): Availability + + /* Request required permissions to collect data */ + override fun requestPermissions(onResult: ((Boolean) -> Unit)) { + permissionManager.request(permissions) { + val granted = permissions.all { permission -> it[permission] == true } + Log.d(TAG, "Permission granted: $granted") + if(granted) _stateFlow.tryEmit(CollectorState(CollectorState.FLAG.ENABLED)) + else _stateFlow.tryEmit(CollectorState(CollectorState.FLAG.PERMISSION_REQUIRED, "Permission denied")) + onResult(granted) + } + } + + override var listener: ((DataEntity) -> Unit)? = null + private fun extractName(className: String): String { + // Replace "Collector" with an empty string + val tmp = className.replace("Collector", "") + + // Split the name into parts based on camel case and underscores + val parts = + tmp.split("(?=\\p{Upper})|_|(?<=\\p{Lower})(?=\\p{Upper})".toRegex()) + + // Join the parts and convert to uppercase + return parts.joinToString("_").uppercase() + } +} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/controller/Availability.kt b/tracker-library/src/main/java/kaist/iclab/tracker/controller/Availability.kt new file mode 100644 index 0000000..835ed79 --- /dev/null +++ b/tracker-library/src/main/java/kaist/iclab/tracker/controller/Availability.kt @@ -0,0 +1,6 @@ +package kaist.iclab.tracker.controller + +data class Availability( + val status: Boolean, + val reason: String? +) \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorConfig.kt b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorConfig.kt new file mode 100644 index 0000000..f5834b1 --- /dev/null +++ b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorConfig.kt @@ -0,0 +1,3 @@ +package kaist.iclab.tracker.controller + +open class CollectorConfig \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorControllerImpl.kt b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorControllerImpl.kt index 78408b6..0ef9a49 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorControllerImpl.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorControllerImpl.kt @@ -8,31 +8,59 @@ import android.os.Build import android.os.IBinder import android.util.Log import kaist.iclab.tracker.Tracker -import kaist.iclab.tracker.collectors.AbstractCollector import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge class CollectorControllerImpl( - private val context: Context + private val context: Context, + override val collectorMap: Map ) : CollectorControllerInterface { - private val serviceIntent = Intent(context, CollectorService::class.java) - override val collectors = mutableListOf() - private val _stateFlow = MutableSharedFlow(replay = 1) + private val _stateFlow = MutableStateFlow(false) + override val stateFlow: StateFlow + get() = _stateFlow.asStateFlow() + + override val collectorStateFlow = combine(collectorMap.map { (key, collector) -> + collector.stateFlow.map { state -> + key to state + } + }) { pairs -> pairs.toMap() } - override fun add(collector: AbstractCollector) { - collectors.add(collector) + override fun enableCollector(name: String) { + collectorMap[name]?.let { collector-> + collector.requestPermissions() { + if (it) { collector.enable() } + } + } } - override fun remove(collector: AbstractCollector) { - collectors.remove(collector) + override fun disableCollector(name: String) { + collectorMap[name]?.disable() } - override fun isRunningFlow(): Flow = _stateFlow.asSharedFlow() - override fun updateState(isRunning: Boolean) { - _stateFlow.tryEmit(isRunning) + override val configFlow = combine(collectorMap.map { (key, collector) -> + collector.configFlow.map { config -> + key to config + } + }) { pairs -> pairs.toMap() } + + override fun updateConfig(config: Map) { + collectorMap.forEach { (name, collector) -> + config[name]?.let { + collector.updateConfig(it) + } + } } + private val serviceIntent = Intent(context, CollectorService::class.java) + override fun start() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(serviceIntent) @@ -45,10 +73,10 @@ class CollectorControllerImpl( context.stopService(serviceIntent) } - class CollectorService: Service() { - val controller = Tracker.getCollectorController() + class CollectorService : Service() { + val controller = Tracker.getCollectorController() as CollectorControllerImpl val notfManager = Tracker.getNotfManager() - val collectors = controller.collectors + val collectorMap = controller.collectorMap override fun onBind(intent: Intent?): IBinder? = null override fun onDestroy() { stop() @@ -59,15 +87,15 @@ class CollectorControllerImpl( this, requiredForegroundServiceType() ) - controller.updateState(true) - collectors.forEach { collector -> + controller._stateFlow.tryEmit(true) + collectorMap.forEach { (_, collector) -> collector.start() } } fun stop() { - controller.updateState(false) - collectors.forEach { collector -> + controller._stateFlow.tryEmit(false) + collectorMap.forEach { (_, collector) -> collector.stop() } stopSelf() @@ -90,8 +118,8 @@ class CollectorControllerImpl( fun requiredForegroundServiceType(): Int { val serviceTypes = mutableSetOf() - collectors.forEach { - serviceTypes.addAll(it.foregroundServiceTypes) + collectorMap.forEach { (_, collector) -> + serviceTypes.addAll(collector.foregroundServiceTypes) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { serviceTypes.add(ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) @@ -103,39 +131,4 @@ class CollectorControllerImpl( } } } - -// object NotificationHandler { -// val SERVICE_CHANNEL_ID = "TRACKER_SERVICE" -// val SERVICE_CHANNEL_NUMBER = 1 -// -// val NOTF_TITLE = "Tracker Service" -// val NOTF_DESCRIPTION = "Tracker is running now" -// -// fun createServiceNotfChannel(context: Context) { -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { -// val channel = NotificationChannel( -// SERVICE_CHANNEL_ID, -// NOTF_TITLE, -// NotificationManager.IMPORTANCE_DEFAULT -// ).apply { -// description = NOTF_DESCRIPTION -// } -// // Register the channel with the system -// val notificationManager: NotificationManager = -// context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager -// notificationManager.createNotificationChannel(channel) -// } -// } -// -// fun createServiceNotf(service: Service): Notification { -// Log.d("NOTIFICATION_SERVICE", "Creating Notification") -// val builder = NotificationCompat.Builder(service, SERVICE_CHANNEL_ID) -// return builder -// .setSmallIcon(android.R.drawable.ic_dialog_info) -// .setContentTitle(NOTF_TITLE) -// .setContentText(NOTF_DESCRIPTION) -// .setOngoing(true) -// .build() -// } -// } } \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorControllerInterface.kt b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorControllerInterface.kt index 6097c92..5c0cace 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorControllerInterface.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorControllerInterface.kt @@ -1,19 +1,21 @@ package kaist.iclab.tracker.controller -import kaist.iclab.tracker.collectors.AbstractCollector -import kaist.iclab.tracker.permission.PermissionManagerInterface import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow interface CollectorControllerInterface { - val collectors: MutableList - - fun add(collector: AbstractCollector) - fun remove(collector: AbstractCollector) + val collectorMap: Map + val stateFlow: StateFlow fun start() fun stop() - fun isRunningFlow(): Flow - fun updateState(isRunning: Boolean) + + val collectorStateFlow: Flow> + + fun enableCollector(name: String) + fun disableCollector(name: String) + + + val configFlow : Flow> + fun updateConfig(config: Map) } \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorInterface.kt b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorInterface.kt new file mode 100644 index 0000000..e0c21cc --- /dev/null +++ b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorInterface.kt @@ -0,0 +1,30 @@ +package kaist.iclab.tracker.controller + +import kotlinx.coroutines.flow.StateFlow + +interface CollectorInterface { + val NAME: String + + val permissions: Array + val foregroundServiceTypes: Array + + val configFlow: StateFlow + fun updateConfig(config: CollectorConfig) + fun resetConfig() + + val stateFlow: StateFlow + + /* DISABLED => READY */ + fun enable() + /* READY => DISABLED */ + fun disable() + /* Request permission to collect data: PERMISSION_REQUIRED => READY */ + fun requestPermissions(onResult: ((Boolean) -> Unit)) + /* Start collector to collect data: READY => RUNNING */ + fun start() + /* Stop collector to stop collecting data: RUNNING => READY */ + fun stop() + + /* Based on the data, define action */ + var listener: ((DataEntity) -> Unit)? +} \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorState.kt b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorState.kt new file mode 100644 index 0000000..ab9cb65 --- /dev/null +++ b/tracker-library/src/main/java/kaist/iclab/tracker/controller/CollectorState.kt @@ -0,0 +1,14 @@ +package kaist.iclab.tracker.controller + +data class CollectorState( + val flag: FLAG, + val message: String? = null +) { + enum class FLAG { + UNAVAILABLE, // The collector is not available (e.g., sensor not found) + PERMISSION_REQUIRED, // The collector requires permission to collect data + DISABLED, // The collector is not ready for collection (e.g., user not turned on it) + ENABLED, // The collector is enabled for collection, but not running + RUNNING // The collector is running + } +} diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/controller/DataEntity.kt b/tracker-library/src/main/java/kaist/iclab/tracker/controller/DataEntity.kt new file mode 100644 index 0000000..1fec8a7 --- /dev/null +++ b/tracker-library/src/main/java/kaist/iclab/tracker/controller/DataEntity.kt @@ -0,0 +1,5 @@ +package kaist.iclab.tracker.controller + +open class DataEntity( + open val received: Long +) \ No newline at end of file diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/permission/PermissionManagerImpl.kt b/tracker-library/src/main/java/kaist/iclab/tracker/permission/PermissionManagerImpl.kt index e1b6574..20b1156 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/permission/PermissionManagerImpl.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/permission/PermissionManagerImpl.kt @@ -56,6 +56,10 @@ class PermissionManagerImpl( ) == PackageManager.PERMISSION_GRANTED } + override fun isPermissionsGranted(permissions: Array): Boolean { + return permissions.all { isPermissionGranted(it) } + } + /* * Request multiple permissions at once: * It will control all about rationale, priority order of permission diff --git a/tracker-library/src/main/java/kaist/iclab/tracker/permission/PermissionManagerInterface.kt b/tracker-library/src/main/java/kaist/iclab/tracker/permission/PermissionManagerInterface.kt index 0b57c3b..85395c9 100644 --- a/tracker-library/src/main/java/kaist/iclab/tracker/permission/PermissionManagerInterface.kt +++ b/tracker-library/src/main/java/kaist/iclab/tracker/permission/PermissionManagerInterface.kt @@ -4,6 +4,7 @@ interface PermissionManagerInterface { var onPermissionResult: PermissionResultCallback? fun attach(activity: PermissionActivity) fun isPermissionGranted(permission: String): Boolean + fun isPermissionsGranted(permissions: Array): Boolean fun request( permissions: Array, onResult: ((permissionResult: PermissionResult) -> Unit)? = null diff --git a/wearable/src/main/java/kaist/iclab/lab_galaxywatch_tracker/data/collector/WearableSensorCollector.kt b/wearable/src/main/java/kaist/iclab/lab_galaxywatch_tracker/data/collector/WearableSensorCollector.kt index 5da26d2..882d1f1 100644 --- a/wearable/src/main/java/kaist/iclab/lab_galaxywatch_tracker/data/collector/WearableSensorCollector.kt +++ b/wearable/src/main/java/kaist/iclab/lab_galaxywatch_tracker/data/collector/WearableSensorCollector.kt @@ -4,7 +4,7 @@ import android.Manifest import android.content.Context import android.os.Build import com.samsung.android.service.health.tracking.HealthTracker -import kaist.iclab.tracker.collectors.AbstractCollector +import kaist.iclab.tracker.controller.AbstractCollector import kaist.iclab.tracker.database.DatabaseInterface import kaist.iclab.tracker.permission.PermissionManagerInterface import kaist.iclab.lab_galaxywatch_tracker.data.source.HealthTrackerSource