blob: eaef444ff8e0879699adfabdd2274bff079cfcc8 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.appops.cts
import android.app.AppOpsManager
import android.app.AppOpsManager.HistoricalOp
import android.app.AppOpsManager.HistoricalOps
import android.app.AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES
import android.app.AppOpsManager.OP_FLAGS_ALL
import android.os.Process
import android.os.SystemClock
import android.provider.DeviceConfig
import androidx.test.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import androidx.test.uiautomator.UiDevice
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import java.util.function.Consumer
const val PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"
@RunWith(AndroidJUnit4::class)
class HistoricalAppopsTest {
private val uid = Process.myUid()
private lateinit var appOpsManager: AppOpsManager
private lateinit var packageName: String
private var wasPermissionsHubEnabled = false
// Start an activity to make sure this app counts as being in the foreground
@Rule @JvmField
var activityRule = ActivityTestRule(UidStateForceActivity::class.java)
@Before
fun wakeScreenUp() {
val uiDevice = UiDevice.getInstance(instrumentation)
uiDevice.wakeUp()
uiDevice.executeShellCommand("wm dismiss-keyguard")
}
@Before
fun setUpTest() {
appOpsManager = context.getSystemService(AppOpsManager::class.java)!!
packageName = context.packageName!!
uiAutomation.adoptShellPermissionIdentity()
wasPermissionsHubEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_PERMISSIONS_HUB_ENABLED, false)
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PERMISSIONS_HUB_ENABLED,
true.toString(), false)
appOpsManager.clearHistory()
appOpsManager.resetHistoryParameters()
}
@After
fun tearDownTest() {
appOpsManager.clearHistory()
appOpsManager.resetHistoryParameters()
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PERMISSIONS_HUB_ENABLED,
wasPermissionsHubEnabled.toString(), false)
uiAutomation.dropShellPermissionIdentity()
}
@Test
fun testGetHistoricalPackageOpsForegroundAccessInMemoryBucket() {
testGetHistoricalPackageOpsForegroundAtDepth(0)
}
@Test
fun testGetHistoricalPackageOpsForegroundAccessFirstOnDiskBucket() {
testGetHistoricalPackageOpsForegroundAtDepth(1)
}
@Test
fun testHistoricalAggregationOneLevelsDeep() {
testHistoricalAggregationSomeLevelsDeep(0)
}
@Test
fun testHistoricalAggregationTwoLevelsDeep() {
testHistoricalAggregationSomeLevelsDeep(1)
}
@Test
fun testHistoricalAggregationOverflow() {
// Configure historical registry behavior.
appOpsManager.setHistoryParameters(
AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
SNAPSHOT_INTERVAL_MILLIS,
INTERVAL_COMPRESSION_MULTIPLIER)
// Add the data to the history
val chunk = createDataChunk()
val chunkCount = (INTERVAL_COMPRESSION_MULTIPLIER * 2) + 3
for (i in 0 until chunkCount) {
appOpsManager.addHistoricalOps(chunk)
}
// Validate the data for the first interval
val firstIntervalBeginMillis = computeIntervalBeginRawMillis(0)
val firstIntervalEndMillis = computeIntervalBeginRawMillis(1)
val firstOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
firstIntervalBeginMillis, firstIntervalEndMillis)
assertHasCounts(firstOps!!, 197)
// Validate the data for the second interval
val secondIntervalBeginMillis = computeIntervalBeginRawMillis(1)
val secondIntervalEndMillis = computeIntervalBeginRawMillis(2)
val secondOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
secondIntervalBeginMillis, secondIntervalEndMillis)
assertHasCounts(secondOps!!, 33)
// Validate the data for both intervals
val thirdIntervalBeginMillis = firstIntervalEndMillis - SNAPSHOT_INTERVAL_MILLIS
val thirdIntervalEndMillis = secondIntervalBeginMillis + SNAPSHOT_INTERVAL_MILLIS
val thirdOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
thirdIntervalBeginMillis, thirdIntervalEndMillis)
assertHasCounts(thirdOps!!, 33)
}
@Test
fun testHistoryTimeTravel() {
// Configure historical registry behavior.
appOpsManager.setHistoryParameters(
AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
SNAPSHOT_INTERVAL_MILLIS,
INTERVAL_COMPRESSION_MULTIPLIER)
// Fill the first two intervals with data
val chunk = createDataChunk()
val chunkCount = computeSlotCount(2) * SNAPSHOT_INTERVAL_MILLIS / chunk.endTimeMillis
for (i in 0 until chunkCount) {
appOpsManager.addHistoricalOps(chunk)
}
// Move history in past with the first interval duration
val firstIntervalDurationMillis = computeIntervalDurationMillis(0)
appOpsManager.offsetHistory(firstIntervalDurationMillis)
// Validate the data for the first interval
val firstIntervalBeginMillis = computeIntervalBeginRawMillis(0)
val firstIntervalEndMillis = firstIntervalBeginMillis + firstIntervalDurationMillis
val firstOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
firstIntervalBeginMillis, firstIntervalEndMillis)
assertThat(firstOps).isNotNull()
assertThat(firstOps!!.uidCount).isEqualTo(0)
// Validate the data for the second interval
val secondIntervalBeginMillis = computeIntervalBeginRawMillis(1)
val secondIntervalDurationMillis = computeIntervalDurationMillis(1)
val secondIntervalEndMillis = secondIntervalBeginMillis + secondIntervalDurationMillis
val secondOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
secondIntervalBeginMillis, secondIntervalEndMillis)
val secondChunkCount = ((computeSlotCount(2) - computeSlotCount(1))
.times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis)
assertHasCounts(secondOps!!, 10 * secondChunkCount)
// Validate the data for the third interval
val thirdIntervalBeginMillis = computeIntervalBeginRawMillis(2)
val thirdIntervalDurationMillis = computeIntervalDurationMillis(2)
val thirdIntervalEndMillis = thirdIntervalBeginMillis + thirdIntervalDurationMillis
val thirdOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
thirdIntervalBeginMillis, thirdIntervalEndMillis)
val thirdChunkCount = secondChunkCount / INTERVAL_COMPRESSION_MULTIPLIER
assertHasCounts(thirdOps!!, 10 * thirdChunkCount)
// Move history in future with the first interval duration
appOpsManager.offsetHistory(- (2.5f * firstIntervalDurationMillis).toLong())
// Validate the data for the first interval
val fourthOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
firstIntervalBeginMillis, firstIntervalEndMillis)
assertHasCounts(fourthOps!!, 194)
// Validate the data for the second interval
val fifthOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
secondIntervalBeginMillis, secondIntervalEndMillis)
assertThat(fifthOps).isNotNull()
assertHasCounts(fifthOps!!, 1703)
}
@Test
fun testGetHistoricalAggregationOverAttributions() {
// Configure historical registry behavior.
appOpsManager.setHistoryParameters(
AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
SNAPSHOT_INTERVAL_MILLIS,
INTERVAL_COMPRESSION_MULTIPLIER)
appOpsManager.setUidMode(OPSTR_REQUEST_DELETE_PACKAGES, uid, AppOpsManager.MODE_ALLOWED)
UidStateForceActivity.waitForResumed()
appOpsManager.noteOp(OPSTR_REQUEST_DELETE_PACKAGES, uid, packageName, "firstAttribution",
null)
appOpsManager.noteOp(OPSTR_REQUEST_DELETE_PACKAGES, uid, packageName, "secondAttribution",
null)
val memOps = getHistoricalOps(appOpsManager, uid = uid)!!
assertThat(memOps.getUidOpsAt(0).getPackageOpsAt(0).getOp(OPSTR_REQUEST_DELETE_PACKAGES)!!
.getForegroundAccessCount(OP_FLAGS_ALL)).isEqualTo(2)
assertThat(memOps.getUidOpsAt(0).getPackageOpsAt(0).getAttributedOps("firstAttribution")!!
.getOp(OPSTR_REQUEST_DELETE_PACKAGES)!!.getForegroundAccessCount(OP_FLAGS_ALL))
.isEqualTo(1)
assertThat(memOps.getUidOpsAt(0).getPackageOpsAt(0).getAttributedOps("secondAttribution")!!
.getOp(OPSTR_REQUEST_DELETE_PACKAGES)!!.getForegroundAccessCount(OP_FLAGS_ALL))
.isEqualTo(1)
// Wait until data is on disk and verify no entry got lost
Thread.sleep(SNAPSHOT_INTERVAL_MILLIS)
val diskOps = getHistoricalOps(appOpsManager, uid = uid)!!
assertThat(diskOps.getUidOpsAt(0)).isEqualTo(memOps.getUidOpsAt(0))
}
private fun testHistoricalAggregationSomeLevelsDeep(depth: Int) {
// Configure historical registry behavior.
appOpsManager.setHistoryParameters(
AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
SNAPSHOT_INTERVAL_MILLIS,
INTERVAL_COMPRESSION_MULTIPLIER)
// Add the data to the history
val chunk = createDataChunk()
val chunkCount = (computeSlotCount(depth + 1)
.times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis)
for (i in 0 until chunkCount) {
appOpsManager.addHistoricalOps(chunk)
}
// Validate the data for the full interval
val intervalBeginMillis = computeIntervalBeginRawMillis(depth)
val intervalEndMillis = computeIntervalBeginRawMillis(depth + 1)
val ops = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
intervalBeginMillis, intervalEndMillis)
val expectedOpCount = ((computeSlotCount(depth + 1) - computeSlotCount(depth))
.times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis) * 10
assertHasCounts(ops!!, expectedOpCount)
}
private fun testGetHistoricalPackageOpsForegroundAtDepth(depth: Int) {
// Configure historical registry behavior.
appOpsManager.setHistoryParameters(
AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
SNAPSHOT_INTERVAL_MILLIS,
INTERVAL_COMPRESSION_MULTIPLIER)
appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
AppOpsManager.MODE_ALLOWED)
appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
AppOpsManager.MODE_ALLOWED)
UidStateForceActivity.waitForResumed()
try {
val noteCount = 5
var beginTimeMillis = 0L
var endTimeMillis = 0L
// Note ops such that we have data at all levels
for (d in depth downTo 0) {
for (i in 0 until noteCount) {
appOpsManager.noteOp(AppOpsManager.OPSTR_START_FOREGROUND, uid, packageName)
}
if (d > 0) {
val previousIntervalDuration = computeIntervalDurationMillis(d - 2)
val currentIntervalDuration = computeIntervalDurationMillis(d - 1)
endTimeMillis -= previousIntervalDuration
beginTimeMillis -= currentIntervalDuration
val sleepDurationMillis = currentIntervalDuration / 2
SystemClock.sleep(sleepDurationMillis)
}
}
val nowMillis = System.currentTimeMillis()
if (depth > 0) {
beginTimeMillis += nowMillis
endTimeMillis += nowMillis
} else {
beginTimeMillis = nowMillis - SNAPSHOT_INTERVAL_MILLIS
endTimeMillis = Long.MAX_VALUE
}
// Get all ops for the package
val allOps = getHistoricalOps(appOpsManager, uid, packageName,
null, beginTimeMillis, endTimeMillis)
assertThat(allOps).isNotNull()
assertThat(allOps!!.uidCount).isEqualTo(1)
assertThat(allOps.beginTimeMillis).isEqualTo(beginTimeMillis)
assertThat(allOps.endTimeMillis).isGreaterThan(beginTimeMillis)
val uidOps = allOps.getUidOpsAt(0)
assertThat(uidOps).isNotNull()
assertThat(uidOps.uid).isEqualTo(Process.myUid())
assertThat(uidOps.packageCount).isEqualTo(1)
val packageOps = uidOps.getPackageOpsAt(0)
assertThat(packageOps).isNotNull()
assertThat(packageOps.packageName).isEqualTo(packageName)
assertThat(packageOps.opCount).isEqualTo(1)
val op = packageOps.getOpAt(0)
assertThat(op).isNotNull()
assertThat(op.opName).isEqualTo(AppOpsManager.OPSTR_START_FOREGROUND)
assertThat(op.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL))
.isEqualTo(noteCount)
assertThat(op.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(0)
assertThat(getAccessCount(op, AppOpsManager.UID_STATE_PERSISTENT)).isEqualTo(0)
assertThat(getAccessCount(op, AppOpsManager.UID_STATE_TOP)).isEqualTo(noteCount)
assertThat(getAccessCount(op, AppOpsManager.UID_STATE_FOREGROUND_SERVICE_LOCATION))
.isEqualTo(0)
assertThat(getAccessCount(op, AppOpsManager.UID_STATE_FOREGROUND_SERVICE))
.isEqualTo(0)
assertThat(getAccessCount(op, AppOpsManager.UID_STATE_FOREGROUND)).isEqualTo(0)
assertThat(getAccessCount(op, AppOpsManager.UID_STATE_BACKGROUND)).isEqualTo(0)
assertThat(getAccessCount(op, AppOpsManager.UID_STATE_CACHED)).isEqualTo(0)
assertThat(op.getForegroundAccessDuration(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(0)
assertThat(op.getBackgroundAccessDuration(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(0)
assertThat(op.getAccessDuration(AppOpsManager.UID_STATE_TOP,
AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAGS_ALL))
.isEqualTo(0)
assertThat(getAccessDuration(op, AppOpsManager.UID_STATE_PERSISTENT)).isEqualTo(0)
assertThat(getAccessDuration(op, AppOpsManager.UID_STATE_TOP)).isEqualTo(0)
assertThat(getAccessDuration(op, AppOpsManager.UID_STATE_FOREGROUND_SERVICE_LOCATION))
.isEqualTo(0)
assertThat(getAccessDuration(op, AppOpsManager.UID_STATE_FOREGROUND_SERVICE))
.isEqualTo(0)
assertThat(getAccessDuration(op, AppOpsManager.UID_STATE_FOREGROUND)).isEqualTo(0)
assertThat(getAccessDuration(op, AppOpsManager.UID_STATE_BACKGROUND)).isEqualTo(0)
assertThat(getAccessDuration(op, AppOpsManager.UID_STATE_CACHED)).isEqualTo(0)
assertThat(op.getForegroundRejectCount(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(0)
assertThat(op.getBackgroundRejectCount(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(0)
assertThat(op.getRejectCount(AppOpsManager.UID_STATE_TOP,
AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAGS_ALL))
.isEqualTo(0)
assertThat(getRejectCount(op, AppOpsManager.UID_STATE_PERSISTENT)).isEqualTo(0)
assertThat(getRejectCount(op, AppOpsManager.UID_STATE_TOP)).isEqualTo(0)
assertThat(getRejectCount(op, AppOpsManager.UID_STATE_FOREGROUND_SERVICE_LOCATION))
.isEqualTo(0)
assertThat(getRejectCount(op, AppOpsManager.UID_STATE_FOREGROUND_SERVICE))
.isEqualTo(0)
assertThat(getRejectCount(op, AppOpsManager.UID_STATE_FOREGROUND)).isEqualTo(0)
assertThat(getRejectCount(op, AppOpsManager.UID_STATE_BACKGROUND)).isEqualTo(0)
assertThat(getRejectCount(op, AppOpsManager.UID_STATE_CACHED)).isEqualTo(0)
} finally {
appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
AppOpsManager.MODE_FOREGROUND)
appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
AppOpsManager.MODE_FOREGROUND)
}
}
private fun createDataChunk(): HistoricalOps {
val chunk = HistoricalOps(SNAPSHOT_INTERVAL_MILLIS / 4,
SNAPSHOT_INTERVAL_MILLIS / 2)
chunk.increaseAccessCount(AppOpsManager.OP_START_FOREGROUND, uid, packageName, null,
AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseAccessCount(AppOpsManager.OP_START_FOREGROUND, uid, packageName, null,
AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseRejectCount(AppOpsManager.OP_START_FOREGROUND, uid, packageName, null,
AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseRejectCount(AppOpsManager.OP_START_FOREGROUND, uid, packageName, null,
AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseAccessDuration(AppOpsManager.OP_START_FOREGROUND, uid, packageName, null,
AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseAccessDuration(AppOpsManager.OP_START_FOREGROUND, uid, packageName, null,
AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
return chunk
}
private fun getHistoricalOps(
appOpsManager: AppOpsManager,
uid: Int = Process.INVALID_UID,
packageName: String? = null,
opNames: List<String>? = null,
beginTimeMillis: Long = 0,
endTimeMillis: Long = Long.MAX_VALUE
): HistoricalOps? {
val array = arrayOfNulls<HistoricalOps>(1)
val lock = ReentrantLock()
val condition = lock.newCondition()
try {
lock.lock()
val request = AppOpsManager.HistoricalOpsRequest.Builder(
beginTimeMillis, endTimeMillis)
.setUid(uid)
.setPackageName(packageName)
.setOpNames(opNames?.toList())
.build()
appOpsManager.getHistoricalOps(request, context.mainExecutor, Consumer { ops ->
array[0] = ops
try {
lock.lock()
condition.signalAll()
} finally {
lock.unlock()
}
})
condition.await(5, TimeUnit.SECONDS)
return array[0]
} finally {
lock.unlock()
}
}
private fun assertHasCounts(ops: HistoricalOps, count: Long) {
assertThat(ops).isNotNull()
assertThat(ops.uidCount).isEqualTo(1)
val uidOps = ops.getUidOpsAt(0)
assertThat(uidOps).isNotNull()
val packageOps = uidOps.getPackageOpsAt(0)
assertThat(packageOps).isNotNull()
val op = packageOps.getOpAt(0)
assertThat(op).isNotNull()
assertThat(op.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(count)
assertThat(op.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(count)
assertThat(op.getForegroundRejectCount(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(count)
assertThat(op.getBackgroundRejectCount(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(count)
assertThat(op.getForegroundAccessDuration(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(count)
assertThat(op.getBackgroundAccessDuration(AppOpsManager.OP_FLAGS_ALL)).isEqualTo(count)
}
private fun getAccessCount(op: HistoricalOp, uidState: Int): Long {
return op.getAccessCount(uidState, uidState, AppOpsManager.OP_FLAGS_ALL)
}
private fun getRejectCount(op: HistoricalOp, uidState: Int): Long {
return op.getRejectCount(uidState, uidState, AppOpsManager.OP_FLAGS_ALL)
}
private fun getAccessDuration(op: HistoricalOp, uidState: Int): Long {
return op.getAccessDuration(uidState, uidState, AppOpsManager.OP_FLAGS_ALL)
}
private fun getHistoricalOpsFromDiskRaw(
uid: Int,
packageName: String,
opNames: List<String>?,
beginTimeMillis: Long,
endTimeMillis: Long
): HistoricalOps? {
val array = arrayOfNulls<HistoricalOps>(1)
val lock = ReentrantLock()
val condition = lock.newCondition()
try {
lock.lock()
val request = AppOpsManager.HistoricalOpsRequest.Builder(
beginTimeMillis, endTimeMillis)
.setUid(uid)
.setPackageName(packageName)
.setOpNames(opNames?.toList())
.build()
appOpsManager.getHistoricalOpsFromDiskRaw(request, context.mainExecutor,
Consumer { ops ->
array[0] = ops
try {
lock.lock()
condition.signalAll()
} finally {
lock.unlock()
}
})
condition.await(5, TimeUnit.SECONDS)
return array[0]
} finally {
lock.unlock()
}
}
companion object {
const val INTERVAL_COMPRESSION_MULTIPLIER = 10
const val SNAPSHOT_INTERVAL_MILLIS = 1000L
val instrumentation get() = InstrumentationRegistry.getInstrumentation()
val context get() = instrumentation.context
val uiAutomation get() = instrumentation.uiAutomation
private fun computeIntervalDurationMillis(depth: Int): Long {
return Math.pow(INTERVAL_COMPRESSION_MULTIPLIER.toDouble(),
(depth + 1).toDouble()).toLong() * SNAPSHOT_INTERVAL_MILLIS
}
private fun computeSlotCount(depth: Int): Int {
var count = 0
for (i in 1..depth) {
count += Math.pow(INTERVAL_COMPRESSION_MULTIPLIER.toDouble(), i.toDouble()).toInt()
}
return count
}
private fun computeIntervalBeginRawMillis(depth: Int): Long {
var beginTimeMillis: Long = 0
for (i in 0 until depth + 1) {
beginTimeMillis += Math.pow(INTERVAL_COMPRESSION_MULTIPLIER.toDouble(),
i.toDouble()).toLong()
}
return beginTimeMillis * SNAPSHOT_INTERVAL_MILLIS
}
}
}