Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,12 @@ interface SensorManager {
} else SensingEngineConfiguration()
with(sensingEngineConfiguration) {
val database = Database.getInstance(context, databaseConfiguration)
SensorManagerImpl(context, database).apply {
registerSensorFactory(InternalSensorType.CAMERA, CameraSensorFactor)
}
SensorManagerImpl(
context,
database,
sensingEngineConfiguration.serverConfiguration
)
.apply { registerSensorFactory(InternalSensorType.CAMERA, CameraSensorFactor) }
}
}
.also { instance = it }
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,25 @@

package com.google.android.sensing.capture

import android.content.Context
import android.content.res.ColorStateList
import androidx.camera.core.TorchState
import androidx.core.content.ContextCompat
import com.google.android.fitbit.research.sensing.common.libraries.camera.Camera2InteropSensor
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.sensing.R
import com.google.android.sensing.model.CaptureType
import com.google.android.sensing.model.InternalSensorType
import java.io.BufferedOutputStream
import java.io.FileOutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

class CaptureUtil {
companion object {
fun toggleFlashWithView(
camera: Camera2InteropSensor,
toggleFlashFab: FloatingActionButton,
context: Context
) {
val c = camera.cameraXSensor.camera
if (c!!.cameraInfo.torchState.value == TorchState.ON) {
// Turn off flash
c.cameraControl.enableTorch(false)
toggleFlashFab.setImageResource(R.drawable.flashlight_off)
toggleFlashFab.backgroundTintList =
ColorStateList.valueOf(ContextCompat.getColor(context, android.R.color.black))
} else {
// Turn on flash
c.cameraControl.enableTorch(true)
toggleFlashFab.setImageResource(R.drawable.flashlight_on)
toggleFlashFab.backgroundTintList =
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.colorPrimary))
}
}

fun sensorsInvolved(captureType: CaptureType): List<InternalSensorType> {
return when (captureType) {
CaptureType.VIDEO_PPG -> listOf(InternalSensorType.CAMERA)
CaptureType.IMAGE -> listOf(InternalSensorType.CAMERA)
fun zipDirectory(sourceDirPath: Path, zipPath: Path) {
ZipOutputStream(BufferedOutputStream(FileOutputStream(zipPath.toFile()))).use { zipOut ->
Files.walk(sourceDirPath)
.filter { path -> !Files.isDirectory(path) } // Exclude directories from the initial walk
.forEach { path ->
val zipEntry = ZipEntry(sourceDirPath.relativize(path).toString())
zipOut.putNextEntry(zipEntry)
Files.copy(path, zipOut)
zipOut.closeEntry()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import com.google.android.sensing.capture.CaptureRequest
import com.google.android.sensing.db.impl.entities.CaptureInfoEntity
import com.google.android.sensing.model.CaptureInfo
import com.google.gson.Gson
import java.time.Instant
import java.util.Date

Expand Down Expand Up @@ -55,20 +57,20 @@ internal abstract class CaptureInfoDao {

internal fun CaptureInfo.toCaptureInfoEntity() =
CaptureInfoEntity(
captureId = captureId!!,
captureId = captureId,
externalIdentifier = externalIdentifier,
captureType = captureType,
captureRequestType = captureRequest::class.java.name,
captureRequest = Gson().toJson(captureRequest),
captureFolder = captureFolder,
captureTime = captureTime?.toInstant() ?: Instant.now(),
captureSettings = captureSettings
)

internal fun CaptureInfoEntity.toCaptureInfo() =
CaptureInfo(
captureId = captureId,
externalIdentifier = externalIdentifier,
captureType = captureType,
captureRequest =
Gson().fromJson(captureRequest, Class.forName(captureRequestType)) as CaptureRequest,
captureFolder = captureFolder,
captureSettings = captureSettings,
captureTime = Date.from(captureTime)
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,13 @@ package com.google.android.sensing.db.impl.entities
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import com.google.android.sensing.capture.CaptureSettings
import com.google.android.sensing.model.CaptureType
import com.google.android.sensing.capture.CaptureRequest
import java.time.Instant

/**
* TODO Update this for Sensing1.0.
* * Remove captureType, captureSettings.
* * Rather store captureRequestType: String, requestType: [CaptureRequest].
*/
@Entity(indices = [Index(value = ["captureFolder"], unique = true)])
/**
* Information about the capture: Which participant, type of capture, folder data being captured to,
* and a captureId associated with this capture record. Later we could add capture settings as well.
* Information about the capture: id, request, time, output-folder, and an external-identifier for
* application's reference.
*/
internal data class CaptureInfoEntity(
/** Unique id for each capture. */
Expand All @@ -43,15 +37,15 @@ internal data class CaptureInfoEntity(
*/
val externalIdentifier: String,

/** Tracking capture information like the ones below. May include [CaptureSettings] later. */
val captureType: CaptureType,
/** [CaptureRequest] type to deserialize back the request. */
val captureRequestType: String,

/** Serialized [CaptureRequest] used in a capture */
val captureRequest: String,

/** Unique folder for each capture. */
val captureFolder: String,

/** Time of this capture. */
val captureTime: Instant,

/** Different settings involved in this capture. */
val captureSettings: CaptureSettings,
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,21 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.sensing.SensorFactory
import com.google.android.sensing.SensorManager
import com.google.android.sensing.ServerConfiguration
import com.google.android.sensing.capture.CaptureMode
import com.google.android.sensing.capture.CaptureRequest
import com.google.android.sensing.capture.CaptureSettings
import com.google.android.sensing.capture.CaptureUtil
import com.google.android.sensing.capture.InitConfig
import com.google.android.sensing.capture.sensors.Sensor
import com.google.android.sensing.db.Database
import com.google.android.sensing.db.ResourceNotFoundException
import com.google.android.sensing.model.CaptureInfo
import com.google.android.sensing.model.CaptureType
import com.google.android.sensing.model.RequestStatus
import com.google.android.sensing.model.ResourceInfo
import com.google.android.sensing.model.SensorType
import com.google.android.sensing.model.UploadRequest
import java.io.File
import java.nio.file.Paths
import java.time.Instant
import java.util.Date
import java.util.UUID
Expand All @@ -46,7 +48,11 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import timber.log.Timber

internal class SensorManagerImpl(context: Context, private val database: Database) : SensorManager {
internal class SensorManagerImpl(
context: Context,
private val database: Database,
private val serverConfiguration: ServerConfiguration?
) : SensorManager {
private val sensorFactoryMap = mutableMapOf<SensorType, SensorFactory>()
data class Components(
val sensor: Sensor,
Expand Down Expand Up @@ -129,10 +135,9 @@ internal class SensorManagerImpl(context: Context, private val database: Databas
CaptureInfo(
captureId = this.captureId,
externalIdentifier = externalIdentifier,
captureType = CaptureType.VIDEO_PPG,
captureRequest = request,
captureFolder = File(context.filesDir, outputFolder).absolutePath,
captureTime = Date.from(Instant.now()),
captureSettings = CaptureSettings(emptyMap(), emptyMap(), "")
)
)
try {
Expand Down Expand Up @@ -162,6 +167,8 @@ internal class SensorManagerImpl(context: Context, private val database: Databas
CoroutineScope(Dispatchers.IO).launch {
it.captureRequest?.let { request ->
with(request) {
val toUploadFullUrl =
(serverConfiguration?.getBucketUrl() ?: "") + "/$outputFolder.zip"
database.addResourceInfo(
ResourceInfo(
resourceInfoId = UUID.randomUUID().toString(),
Expand All @@ -170,30 +177,29 @@ internal class SensorManagerImpl(context: Context, private val database: Databas
resourceTitle = outputTitle,
contentType = outputFormat,
localLocation = File(context.filesDir, outputFolder).absolutePath,
/** TODO Update remoteLocation for Sensing1.0. */
remoteLocation = "",
remoteLocation = toUploadFullUrl,
status = RequestStatus.PENDING
)
)
try {
database.getCaptureInfo(this.captureId).let { captureInfo ->
/**
* Back to original coroutine context. For application this will generally run in
* the main thread.
*/
val processedString =
withContext(lifecycleOwner.lifecycleScope.coroutineContext) {
it.listener?.onComplete(captureInfo)
/** TODO PostProcess. */
}
/**
* Back to original coroutine context. For application this will generally run
* in the main thread.
*/
withContext(lifecycleOwner.lifecycleScope.coroutineContext) {
it.listener?.onComplete(captureInfo)
/** TODO PostProcess. */
}
/** TODO move this to a default PostProcessor. */
createUploadRequest(captureInfo, it.captureRequest!!)
}
} catch (e: ResourceNotFoundException) {
} catch (e: Exception) {
it.listener?.onError(e)
stop(sensorType)
}
}
}
/** TODO zipping and creating UploadRequest */
}
}
}
Expand Down Expand Up @@ -227,6 +233,40 @@ internal class SensorManagerImpl(context: Context, private val database: Databas
}
}

/**
* Zips the folder data + metadata were captured to and add an UploadRequest in the database. TODO
* move captureRequest to captureInfo.
*/
private suspend fun createUploadRequest(
captureInfo: CaptureInfo,
captureRequest: CaptureRequest
) {
serverConfiguration?.let {
with(captureInfo) {
val absoluteFolder = resourceInfoList.first().localLocation
val targetZip = File("$absoluteFolder.zip")
CaptureUtil.zipDirectory(Paths.get(absoluteFolder), targetZip.toPath())
val toUploadRelativeUrl = "/${captureRequest.outputFolder}.zip"
val uploadRequest =
UploadRequest(
requestUuid = UUID.randomUUID(),
resourceInfoId = resourceInfoList.first().resourceInfoId,
zipFile = targetZip.absolutePath,
fileSize = targetZip.length(),
fileOffset = 0L,
bucketName = serverConfiguration.bucketName,
uploadRelativeURL = toUploadRelativeUrl,
isMultiPart = serverConfiguration.networkConfiguration.isMultiPart,
nextPart = 1,
uploadId = null,
status = RequestStatus.PENDING,
lastUpdatedTime = Date.from(Instant.now())
)
database.addUploadRequest(uploadRequest)
}
}
}

override suspend fun stop(sensorType: SensorType) {
val sensorTypeMutex = validate(sensorType)
sensorTypeMutex.withLock {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@

package com.google.android.sensing.model

import com.google.android.sensing.capture.CaptureSettings
import com.google.android.sensing.capture.CaptureRequest
import java.util.Date

/** Data class equivalent to [CaptureInfoEntity] for usage outside database. */
data class CaptureInfo(
var captureId: String? = null,
var captureId: String,
val externalIdentifier: String,
val captureType: CaptureType,
val captureRequest: CaptureRequest,
val captureFolder: String,
var captureTime: Date? = null,
val captureSettings: CaptureSettings,
val recapture: Boolean? = false,
val resourceInfoList: List<ResourceInfo> = emptyList()
)

This file was deleted.