Fix screen delta extraction for iOS and Android
parent
0958590850
commit
619a6e6e2b
|
@ -32,7 +32,8 @@ rule battery_deltas:
|
|||
|
||||
rule screen_deltas:
|
||||
input:
|
||||
"data/raw/{pid}/screen_with_datetime.csv"
|
||||
screen = "data/raw/{pid}/screen_with_datetime.csv",
|
||||
participant_info = "data/external/{pid}"
|
||||
output:
|
||||
"data/processed/{pid}/screen_deltas.csv"
|
||||
script:
|
||||
|
|
|
@ -1,44 +1,94 @@
|
|||
source("packrat/init.R")
|
||||
|
||||
library("tidyverse")
|
||||
library(dplyr)
|
||||
library(tidyr)
|
||||
library(stringr)
|
||||
|
||||
screen <- read.csv(snakemake@input[[1]])
|
||||
screen <- read.csv(snakemake@input[["screen"]])
|
||||
participant_info <- snakemake@input[["participant_info"]]
|
||||
platform <- readLines(participant_info, n=2)[[2]]
|
||||
|
||||
if(nrow(screen) > 0){
|
||||
unlock_episodes <-
|
||||
screen %>%
|
||||
# in iOS there are unlock (3) events on the same second, discard them
|
||||
distinct(screen_status, utc_date_time, .keep_all = TRUE) %>%
|
||||
# in Android we discard on and off events (0,1) for now (iOS does not collect them)
|
||||
filter(screen_status == 2 | screen_status == 3) %>%
|
||||
# create groups of consecutive unlock/lock (3/2) events
|
||||
mutate(screen_episode = cumsum(c(1, head(screen_status, -1) == 2 & tail(screen_status, -1) == 3))) %>%
|
||||
group_by(screen_episode) %>%
|
||||
# in Android there are multiple consecutive unlock/lock events so we keep the closest pair
|
||||
# this happens because ACTION_SCREEN_OFF and ON are "sent when the device becomes non-interactive
|
||||
# which may have nothing to do with the screen turning off" see:
|
||||
# https://developer.android.com/reference/android/content/Intent.html#ACTION_SCREEN_OFF
|
||||
filter((screen_status == 2 & screen_status != lag(screen_status, default="1")) |
|
||||
(screen_status == 3 & screen_status != lead(screen_status, default="1"))) %>%
|
||||
filter(n() == 2) %>%
|
||||
summarize(episode = "unlock",
|
||||
time_diff = (last(timestamp) - first(timestamp)) / (1000 * 60),
|
||||
# Screen States
|
||||
# Android: https://github.com/denzilferreira/aware-client/blob/78ccc22f0f822f8421bef9b1a73d36e71b8aa85b/aware-core/src/main/java/com/aware/Screen.java
|
||||
# iOS: https://github.com/tetujin/aware-client-ios-v2/blob/master/Pods/AWAREFramework/AWAREFramework/Classes/Sensors/Screen/Screen.m#L120
|
||||
# 0. OFF (Not existent for iOS due to API limitations)
|
||||
# 1. ON (Not existent for iOS due to API limitations)
|
||||
# 2. LOCKED
|
||||
# 3. UNLOCKED
|
||||
|
||||
swap_screen_status <- function(data, status1, status2, time_buffer){
|
||||
# 800L is an auxiliary state to swap
|
||||
data %>% mutate(screen_status = case_when(screen_status == status2 & lag(screen_status) == status1 & (timestamp - lag(timestamp)) < time_buffer ~ 800L,
|
||||
TRUE ~ screen_status),
|
||||
screen_status = case_when(screen_status == status1 & lead(screen_status) == 800L ~ status2,
|
||||
TRUE ~ screen_status),
|
||||
screen_status = ifelse(screen_status == 800L, status1, screen_status))
|
||||
}
|
||||
|
||||
get_ios_screen_episodes <- function(screen){
|
||||
episodes <- screen %>%
|
||||
# only keep consecutive pairs of 3,2 events
|
||||
filter( (screen_status == 3 & lead(screen_status) == 2) | (screen_status == 2 & lag(screen_status) == 3) ) %>%
|
||||
# in iOS and after our filtering, screen episodes should end with a LOCK event (2)
|
||||
mutate(episode_id = ifelse(screen_status == 2, 1:n(), NA_integer_)) %>%
|
||||
fill(episode_id, .direction = "updown") %>%
|
||||
group_by(episode_id) %>%
|
||||
summarise(episode = "unlock",
|
||||
screen_sequence = toString(screen_status),
|
||||
time_diff = (last(timestamp) - first(timestamp)) / 1000,
|
||||
local_start_date_time = first(local_date_time),
|
||||
local_end_date_time = last(local_date_time),
|
||||
local_start_date = first(local_date),
|
||||
local_end_date = last(local_date),
|
||||
local_start_day_segment = first(local_day_segment),
|
||||
local_end_day_segment = last(local_day_segment)) %>%
|
||||
select(-screen_episode)
|
||||
} else {
|
||||
unlock_episodes <- data.frame(episode = character(),
|
||||
time_diff = numeric(),
|
||||
local_start_date_time = character(),
|
||||
local_end_date_time = character(),
|
||||
local_start_date = character(),
|
||||
local_end_date = character(),
|
||||
local_start_day_segment = character(),
|
||||
local_end_day_segment = character())
|
||||
local_end_day_segment = last(local_day_segment))
|
||||
}
|
||||
|
||||
write.csv(unlock_episodes, snakemake@output[[1]], row.names = FALSE)
|
||||
get_android_screen_episodes <- function(screen){
|
||||
episodes <- screen %>%
|
||||
# filter out UNLOCK events (2) that come within 50 milliseconds of an ON or OFF event
|
||||
filter(!(screen_status == 2 & lag(screen_status) == 1 & timestamp - lag(timestamp) < 50)) %>%
|
||||
filter(!(screen_status == 2 & lag(screen_status) == 0 & timestamp - lag(timestamp) < 50)) %>%
|
||||
# in Android and after our filtering, screen episodes should end with a OFF event (0)
|
||||
mutate(episode_id = ifelse(screen_status == 0, 1:n(), NA_integer_)) %>%
|
||||
fill(episode_id, .direction = "updown") %>%
|
||||
group_by(episode_id) %>%
|
||||
# Rarely, UNLOCK events (3) get logged just before ON events (1). If this happens within 800ms, swap them
|
||||
swap_screen_status(3L, 1L, 800) %>%
|
||||
# to be consistent with iOS we get rid off events (and thus sequences) starting with an ON (1) event
|
||||
filter(screen_status != 1) %>%
|
||||
summarise(episode = "unlock",
|
||||
screen_sequence = toString(screen_status),
|
||||
time_diff = (last(timestamp) - first(timestamp)) / 1000,
|
||||
local_start_date_time = first(local_date_time),
|
||||
local_end_date_time = last(local_date_time),
|
||||
local_start_date = first(local_date),
|
||||
local_end_date = last(local_date),
|
||||
local_start_day_segment = first(local_day_segment),
|
||||
local_end_day_segment = last(local_day_segment)) %>%
|
||||
filter(str_detect(screen_sequence,
|
||||
paste0("^(",
|
||||
paste(c(3), collapse = "|"), # Filter sequences that start with 3 (UNLOCK) AND
|
||||
").*(",
|
||||
paste(c(0), collapse = "|"), # Filter sequences that end with 0 (OFF)
|
||||
")$")))
|
||||
}
|
||||
|
||||
if(nrow(screen) < 1){
|
||||
episodes <- data.frame(episode = character(),
|
||||
time_diff = numeric(),
|
||||
local_start_date_time = character(),
|
||||
local_end_date_time = character(),
|
||||
local_start_date = character(),
|
||||
local_end_date = character(),
|
||||
local_start_day_segment = character(),
|
||||
local_end_day_segment = character())
|
||||
} else if(platform == "ios"){
|
||||
episodes <- get_ios_screen_episodes(screen)
|
||||
} else if(platform == "android"){
|
||||
episodes <- get_android_screen_episodes(screen)
|
||||
} else {
|
||||
print(paste0("The platform (second line) in ", participant_info, " should be android or ios"))
|
||||
}
|
||||
|
||||
write.csv(episodes, snakemake@output[[1]], row.names = FALSE)
|
||||
|
|
Loading…
Reference in New Issue