Как стать автором
Обновить
0
Газпром-Медиа Холдинг
Блог о технологиях холдинга «Газпром-Медиа»

Туда и обратно: как мы пытались отследить актуальное время в Android

Время на прочтение4 мин
Количество просмотров2.6K
Марти, серия еще доступна ?
Марти, серия еще доступна ?

Эта статья будет посвящена тому, как мы в команде PREMIER, пытались установить актуальность загруженного контента и что из этого вышло. Возможно публикация будет полезна тем, кто решил следить за переводами времени устройства в условиях отсутствующего соединения.

Предыстория

По договоренности с правообладателем все скачанные фильмы и сериалы могут быть доступны пользователю только в течение 14 дней. Поэтому мы должны ограничивать пользователя в доступе к просмотру загрузок по истечении этого срока. Время окончания доступности для серии рассчитывается в момент ее загрузки и проверяется с помощью времени на устройстве.

Но может возникнуть ситуация, когда пользователь умышлено отключит интернет и, убрав последнюю возможность синхронизировать время с сервером, переведет время на устройстве назад, продлив тем самым для себя возможность просмотра контента. Чтобы избежать проблем с правообладателями и не ограничивать пользователей, которые по каким-то причинам перевели время вперед, уменьшив для себя время доступности контента, было принято решение пробовать отслеживать реальное оставшееся время для загрузок.

Засучив рукава

Изначально была идея использовать библиотеку AndroidTrueTime, но из-за того, что в ней используется NTP(Network Time Protocol), а нам требовалось работать в оффлайне, ее пришлось откинуть. Также была мысль использовать системное время из линукс (Hardware Time), который бы не изменялся после перевода времени, но для его использования нужно чтобы у пользователя был установлен BusyBox или что-то подобное.

Изменения времени решили устанавливать самостоятельно по системному уведомлению приходящему из BroadcastReceiver’a. Так как Android не присылает никакой информации о том, на сколько было изменено время на девайсе, а просто шлет уведомление о событии, то появилась потребности рассчитывать разницу между старым и новым временем устройства.

Для этого воспользовались связкой системных переменных:

System.currentTimeMillis() // возвращает текущее время время в миллисекундах
SystemClock.elapsedRealtime() // возвращает время от старта системы в миллисекундах

Разница между ними:

private fun currentTimestamp(): Long {
    return System.currentTimeMillis() - SystemClock.elapsedRealtime()
}

Это позволило получить неизменяемый timestamp, который бы отличался только при изменении времени на устройстве и при перезагрузке устройства, но об этом позже.

Таким образом, для того чтобы узнать насколько изменилось время, мы находим разницу:

val timeChangeInMillis = savedTimestamp - currentTimestamp

savedTimestamp устанавливается методом currentTimestamp() при первом запуске приложения и служит базовой точкой отсчета при вычислениях разницы времени.

Изменение во времени сохраняем в переменную timeChangingDelta.

Вывод актуального времени стал выглядеть так:

val timeChangingDelta = preferences.getLong(KEY_LAST_DIFFERENCE, NO_TIME)
val timestamp = System.currentTimeMillis() + timeChangingDelta

Непреодолимые трудности

Но что делать, если устройство было перезагружено, ведь в этот момент elapsedRealtime сбрасывается к 0, ломая наши вычисления? Правильно, дополнительно сохранять его в переменную elapsingTimeChangingDelta. Перезагрузку устройства можно определить по признаку того, что сохраненное значение станет больше реального(т.к. реальное сбросится) или как аналог можно воспользоваться Settings.Global.BOOT_COUNT, которая возвращает количество перезагрузок устройства.

val wasDeviceRebooted = Settings.Global.BOOT_COUNT > savedBootCount

Если девайс был перезагружен, то вычисляем изменение elapsedRealtime и суммируем с предыдущими изменениями:

val elapsedTimeDelta = preferences.getLong(KEY_ELAPSED_TIME, NO_TIME) - SystemClock.elapsedRealtime()
preferences.edit {
	val previousSum = getLong(KEY_ELAPSING_DELTA_SUM, NO_TIME)
    putLong(KEY_ELAPSING_DELTA_SUM, previousSum + elapsedTimeDelta)
}

Метод получения времени стал выглядеть так:

fun timestamp(): Long {
        when {
            hasTimestamps() -> updateTimestamp() 
            else -> initializeTimestamps()
        }

        val timeChangingDelta = preferences.getLong(KEY_LAST_DIFFERENCE, NO_TIME)
        val elapsingTimeChangingDelta = preferences.getLong(KEY_ELAPSING_DELTA_SUM, NO_TIME)
        return System.currentTimeMillis() + (timeChangingDelta + elapsingTimeChangingDelta)
    }

Сохраненное значение elapsedRealtime суммируется к нашей timeChangingDelta, и таким образом мы находим сдвиг времени.

Еще одним препятствием оказалось то, что событие о переводе времени приходит с задержкой или, в зависимости от ОС девайса, вовсе не приходит при закрытом приложении. Все вычисления потребовалось выполнять уже в момент отображения контента, что могло произойти с большой задержкой после перевода времени или перезагрузки.

Сматывая удочки

Стало ясно, что в текущем виде без дополнительной синхронизации устанавливать актуальное время не удастся.

В итоге, подход не справился с задачей в полной мере и от него пришлось отказаться. Пользователь все равно сможет обойти защиту с помощью перезагрузки и перевода времени. Но решение может оказаться для кого-то полезным в случае изменений времени в рамках одного цикла перезагрузки девайса.

Проблему пришлось решить другим способом, сохранив последнее время открытия серии в lastOpenTimestamp и при обнаружении, что время девайса меньше сохраненного, блокировать доступ к серии. (currentTimestamp < lastOpenTimestamp) . Это не решило всех проблем, но позволило защитить контент.

Теги:
Хабы:
Всего голосов 5: ↑4 и ↓1+5
Комментарии10

Публикации

Информация

Сайт
www.gazprom-media.com
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Россия

Истории