Skip to content

Commit

Permalink
Merge pull request #259 from amosproj/event-refactor
Browse files Browse the repository at this point in the history
Event refactor
  • Loading branch information
fhilgers authored Feb 4, 2025
2 parents fb52b98 + 046f182 commit 207dbe4
Show file tree
Hide file tree
Showing 76 changed files with 1,270 additions and 759 deletions.
3 changes: 1 addition & 2 deletions frontend/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,7 @@ dependencies {
detektPlugins(libs.detekt.compose.rules)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.ycharts)


implementation(libs.kotlinx.datetime)
}

tasks.cyclonedxBom {
Expand Down
38 changes: 37 additions & 1 deletion frontend/app/src/main/java/de/amosproj3/ziofa/OverlayService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,23 @@ import de.amosproj3.ziofa.ui.overlay.OverlayRoot
import de.amosproj3.ziofa.ui.visualization.data.OverlayPosition
import timber.log.Timber

/**
* This service is required by the overlay for starting, stopping it and feeding it data. The
* lifecycle of the ViewModels needs to be managed manually via the [lifecycleOwner] Currently, the
* lifecycle of the overlay viewmodels is tied to the lifecycle of the service.
*/
class OverlayService : Service() {

/** Required to manage the view models. * */
class OverlayViewModelStoreOwner : ViewModelStoreOwner {
override val viewModelStore: ViewModelStore =
ViewModelStore().also { Timber.i("created viewmodel store $it") }
}

/**
* The layout parameters for the overlay window. The position should a added later so we can
* change it. Currently, the overlay is touch-through and half-transparent.
*/
private val layoutParams =
WindowManager.LayoutParams(
/* w = */ WindowManager.LayoutParams.WRAP_CONTENT,
Expand All @@ -44,15 +54,22 @@ class OverlayService : Service() {
)

private val windowManager by lazy { getSystemService(WINDOW_SERVICE) as WindowManager }

/** It is important that this is only initialized once and not created multiple times. */
private val lifecycleOwner by lazy { OverlayLifecycleOwner() }

/** Keep the active View for tearing it down upon stopping the service */
private var activeOverlay: View? = null

override fun onCreate() {
Timber.i("onCreate()")
super.onCreate()
}

/**
* When the service is started, the overlay will be rendered. By getting the extra from the
* intent, we set the position of the overlay.
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.i("onStartCommand")
setTheme(R.style.Theme_ZIOFA)
Expand All @@ -61,34 +78,49 @@ class OverlayService : Service() {
return super.onStartCommand(intent, flags, startId)
}

/**
* First tear down any active overlays to prevent overlapping, then display the overlay with the
* [layoutParams].
*/
private fun showOverlay() {
val composeView = createComposeViewWithLifecycle { OverlayRoot() }
teardownOverlay() // Make sure only one overlay window is active at a time
setupOverlay(composeView, layoutParams)
}

/**
* The service is stopped, we have to pass that lifecycle event to the [lifecycleOwner] as well
* and remove the active window.
*/
override fun onDestroy() {
super.onDestroy()
Timber.i("onDestroy()")
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
activeOverlay?.let { windowManager.removeView(it) }
teardownOverlay()
}

/** We don't need to bind to the service. */
override fun onBind(p0: Intent?): IBinder? {
return null
}

/** Add the overlay view as a window and pass the lifecycle event to the [lifecycleOwner] */
private fun setupOverlay(view: View, params: WindowManager.LayoutParams) {
windowManager.addView(view, params)
activeOverlay = view
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START)
}

/** Remove the active overlay window */
private fun teardownOverlay() {
activeOverlay?.let { windowManager.removeView(it) }
activeOverlay = null
}

/**
* Very important, the [lifecycleOwner] needs to be attached and the [Lifecycle.Event.ON_CREATE]
* passed, otherwise the viewmodel will never be instantiated properly.
*/
private fun createComposeViewWithLifecycle(content: @Composable () -> Unit): ComposeView {
val composeView = ComposeView(this)
lifecycleOwner.attach()
Expand All @@ -102,6 +134,10 @@ class OverlayService : Service() {
return composeView
}

/**
* Convert the overlay position passed via the intent into a gravity value for the layout params
* of the window.
*/
private fun WindowManager.LayoutParams.applyOverlayPositionAsGravity(intent: Intent?) {
val overlayPosition =
intent?.getStringExtra(OVERLAY_POSITION_EXTRA)?.let { OverlayPosition.valueOf(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ package de.amosproj3.ziofa.api.configuration

import kotlinx.coroutines.flow.StateFlow

/**
* Provides access to the [ConfigurationState] and allows modifying it.
*
* @property configurationState State of the synchronization between backend and frontend
* configuration.
*/
interface ConfigurationAccess {
val configurationState: StateFlow<ConfigurationState>

/**
* Performs an action on the current [ConfigurationState].
*
* @param action action to perform.
*/
suspend fun performAction(action: ConfigurationAction)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,23 @@ package de.amosproj3.ziofa.api.configuration

import de.amosproj3.ziofa.ui.symbols.data.SymbolsEntry

/** To be used in implementation of [SymbolsAccess.searchSymbols] */
sealed class GetSymbolsRequestState {

/**
* The request has finished successfully
*
* @param symbols the symbols that were found
*/
data class Response(val symbols: List<SymbolsEntry>) : GetSymbolsRequestState()

/**
* The request has failed
*
* @param errorMessage the error message
*/
data class Error(val errorMessage: String) : GetSymbolsRequestState()

/** The request is currently in progress */
data object Loading : GetSymbolsRequestState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ package de.amosproj3.ziofa.api.configuration

/** To be used in implementation of [SymbolsAccess.indexSymbols] */
sealed class IndexingRequestState {

/** The indexing request is not started yet */
data object NotStarted : IndexingRequestState()

/** The indexing request is currently in progress */
data object Started : IndexingRequestState()

/** The indexing request has finished successfully */
data object Done : IndexingRequestState()

/** There was an error while indexing */
data class Error(val error: Throwable) : IndexingRequestState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ interface SymbolsAccess {

/**
* Search all symbols of the given [pids] for a string that **contains** the search query. The
* search is case-insensitive.
* search is case-insensitive. Implementation should return a flow that completes once the
* search is finished.
*
* @param pids the PID whose binaries should be searched
* @param searchQuery the string to search for using
* @return a flow that describes the state of the request
*/
fun searchSymbols(pids: List<UInt>, searchQuery: String): Flow<GetSymbolsRequestState>

/**
* Start indexing all symbols of the system into the backend database This needs to be done once
* before searching for symbols. Implementations should only index the backend symbols once to
* avoid indexing the same symbols multiple times.
*
* @return a flow that describes the status of the indexing request
*/
fun indexSymbols(): Flow<IndexingRequestState>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ import kotlinx.coroutines.flow.Flow

interface DataStreamProvider {

/** Returns a flow of all [Event.VfsWrite] events for the given [pids]. */
fun vfsWriteEvents(pids: List<UInt>?): Flow<Event.VfsWrite>

/** Returns a flow of all [Event.SysSendmsg] events for the given [pids]. */
fun sendMessageEvents(pids: List<UInt>?): Flow<Event.SysSendmsg>

/** Returns a flow of all [Event.JniReferences] events for the given [pids]. */
fun jniReferenceEvents(pids: List<UInt>?): Flow<Event.JniReferences>

/** Returns a flow of all [Event.SysSigquit] events for the given [pids]. */
fun sigquitEvents(pids: List<UInt>?): Flow<Event.SysSigquit>

/** Returns a flow of all [Event.Gc] events for the given [pids]. */
fun gcEvents(pids: List<UInt>?): Flow<Event.Gc>

/** Returns a flow of all [Event.SysFdTracking] events for the given [pids]. */
fun fileDescriptorTrackingEvents(pids: List<UInt>?): Flow<Event.SysFdTracking>
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ package de.amosproj3.ziofa.api.overlay
import de.amosproj3.ziofa.ui.visualization.data.OverlaySettings
import de.amosproj3.ziofa.ui.visualization.data.SelectionData

/** Possible interaction with the overlay state */
sealed class OverlayAction {

/** Change the settings of the overlay, like size, position, etc. */
data class ChangeSettings(val newSettings: OverlaySettings) : OverlayAction()

/** Enable the overlay for the given [selectionData] */
data class Enable(val selectionData: SelectionData) : OverlayAction()

/** Disable the overlay */
data object Disable : OverlayAction()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ package de.amosproj3.ziofa.api.overlay

import kotlinx.coroutines.flow.Flow

/** Bridge between the frontend of the application and the overlay. */
interface OverlayController {
/** The current state of the overlay */
val overlayState: Flow<OverlayState>

fun dispatchAction(action: OverlayAction)
/**
* Performs an action on the current [OverlayState].
*
* @param action action to perform.
*/
fun performAction(action: OverlayAction)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ package de.amosproj3.ziofa.api.overlay
import de.amosproj3.ziofa.ui.visualization.data.OverlaySettings
import de.amosproj3.ziofa.ui.visualization.data.SelectionData

/** State of the overlay */
sealed class OverlayState(open val overlaySettings: OverlaySettings) {

/** The overlay is disabled, but we require the so we can change them while it is disabled */
data class Disabled(override val overlaySettings: OverlaySettings) :
OverlayState(overlaySettings)

/** The overlay is enabled for the given [selectionData] */
data class Enabled(
override val overlaySettings: OverlaySettings,
val selectionData: SelectionData? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ package de.amosproj3.ziofa.api.processes

import android.graphics.drawable.Drawable

/** Wrapper class for app information */
data class InstalledPackageInfo(val displayName: String, val icon: Drawable)
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ package de.amosproj3.ziofa.api.processes

import de.amosproj3.ziofa.client.Process

/**
* Wrapper class for running components, that can either be groups of processes or single processes
*/
sealed class RunningComponent(val pids: List<UInt>) {

/** Standalone process that does not belong to an app */
data class StandaloneProcess(val process: Process) :
RunningComponent(pids = listOf(process.pid))

/** Processes that belong to an app, along with the app's information like the icon */
data class Application(val packageInfo: InstalledPackageInfo, val processList: List<Process>) :
RunningComponent(pids = processList.map { it.pid })
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package de.amosproj3.ziofa.api.processes

import kotlinx.coroutines.flow.Flow

/** Provides access to the list of running components */
interface RunningComponentsAccess {

/** The list of running components retrieved from the backend */
val runningComponentsList: Flow<List<RunningComponent>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import de.amosproj3.ziofa.client.VfsWriteConfig
import de.amosproj3.ziofa.ui.configuration.data.BackendFeatureOptions
import de.amosproj3.ziofa.ui.shared.DURATION_THRESHOLD

/**
* There has to be a cleaner way to do this, but for now it works. Given a [ChangeFeature] action,
* apply the change to the [Configuration] and return changed configuration The [ChangeFeature]
* action contains a "delta" that will be applied to the configuration. (that may be removing or
* adding PIDs)
*/
@Suppress("CyclomaticComplexMethod", "LongMethod")
fun Configuration.applyChange(action: ConfigurationAction.ChangeFeature): Configuration {

Expand All @@ -28,12 +34,8 @@ fun Configuration.applyChange(action: ConfigurationAction.ChangeFeature): Config
this.copy(
vfsWrite =
this.vfsWrite.updatePIDs(
pidsToAdd =
if (enable) pids.associateWith { DURATION_THRESHOLD }.entries
else setOf(),
pidsToRemove =
if (!enable) pids.associateWith { DURATION_THRESHOLD }.entries
else setOf(),
pidsToAdd = if (enable) pids else setOf(),
pidsToRemove = if (!enable) pids else setOf(),
)
)

Expand Down Expand Up @@ -110,16 +112,11 @@ fun Configuration.applyChange(action: ConfigurationAction.ChangeFeature): Config
}

fun VfsWriteConfig?.updatePIDs(
pidsToAdd: Set<Map.Entry<UInt, ULong>> = setOf(),
pidsToRemove: Set<Map.Entry<UInt, ULong>> = setOf(),
pidsToAdd: Set<UInt> = setOf(),
pidsToRemove: Set<UInt> = setOf(),
): VfsWriteConfig {
val config = this ?: VfsWriteConfig(mapOf())
return config.copy(
entries =
config.entries.entries.plus(pidsToAdd).minus(pidsToRemove).associate {
it.key to it.value
}
)
val config = this ?: VfsWriteConfig(listOf())
return config.copy(pids = config.pids.plus(pidsToAdd).minus(pidsToRemove))
}

fun SysSendmsgConfig?.updatePIDs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import de.amosproj3.ziofa.api.configuration.ConfigurationState
import de.amosproj3.ziofa.client.Client
import de.amosproj3.ziofa.client.ClientFactory
import de.amosproj3.ziofa.client.Configuration
import de.amosproj3.ziofa.ui.configuration.utils.EMPTY_CONFIGURATION
import de.amosproj3.ziofa.ui.shared.EMPTY_CONFIGURATION
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ import de.amosproj3.ziofa.api.configuration.GetSymbolsRequestState
import de.amosproj3.ziofa.api.configuration.IndexingRequestState
import de.amosproj3.ziofa.api.configuration.SymbolsAccess
import de.amosproj3.ziofa.client.ClientFactory
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

/**
* Currently unused as there is no "uprobe event". -> There is no point in setting uprobes from the
* UI.
*/
class UProbeManager(private val clientFactory: ClientFactory) : SymbolsAccess {

/** We should do this on the backend in the future. */
@OptIn(ExperimentalCoroutinesApi::class)
/**
* Currently unused as there is no "uprobe event". -> There is no point in setting uprobes from
* the UI.
*/
override fun searchSymbols(
pids: List<UInt>,
searchQuery: String,
Expand Down
Loading

0 comments on commit 207dbe4

Please sign in to comment.