blob: 6cbdca3a38c5b16508bc942445c8e1b3edec6a60 [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.Instrumentation
import android.content.Context
import android.os.Process
import android.os.SystemClock
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.util.ArrayList
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import java.util.function.Consumer
import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.UiDevice
import org.junit.Rule
@RunWith(AndroidJUnit4::class)
class HistoricalAppopsTest {
private val uid = Process.myUid()
private var appOpsManager: AppOpsManager? = null
private var packageName: String? = null
// 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 device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.wakeUp()
device.executeShellCommand("wm dismiss-keyguard")
}
@Before
fun setUpTest() {
appOpsManager = getContext().getSystemService(AppOpsManager::class.java)
packageName = getContext().packageName
val uiAutomation = getInstrumentation().getUiAutomation()
uiAutomation.adoptShellPermissionIdentity()
appOpsManager!!.clearHistory()
appOpsManager!!.resetHistoryParameters()
}
@After
fun tearDownTest() {
appOpsManager!!.clearHistory()
appOpsManager!!.resetHistoryParameters()
val uiAutomation = getInstrumentation().getUiAutomation()
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(appOpsManager!!, 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(appOpsManager!!, 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(appOpsManager!!, 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(appOpsManager!!, 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(appOpsManager!!, 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(appOpsManager!!, 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(appOpsManager!!, uid, packageName!!,
null /*opNames*/, firstIntervalBeginMillis, firstIntervalEndMillis)
assertHasCounts(fourthOps!!, 194)
// Validate the data for the second interval
val fifthOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
null /*opNames*/, secondIntervalBeginMillis, secondIntervalEndMillis)
assertThat(fifthOps).isNotNull()
assertHasCounts(fifthOps!!, 1703)
}
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(appOpsManager!!, 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)
activityRule.activity.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(getContext().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!!, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseAccessCount(AppOpsManager.OP_START_FOREGROUND, uid,
packageName!!, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseRejectCount(AppOpsManager.OP_START_FOREGROUND, uid,
packageName!!, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseRejectCount(AppOpsManager.OP_START_FOREGROUND, uid,
packageName!!, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseAccessDuration(AppOpsManager.OP_START_FOREGROUND, uid,
packageName!!, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
chunk.increaseAccessDuration(AppOpsManager.OP_START_FOREGROUND, uid,
packageName!!, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
return chunk
}
private fun getHistoricalOps(appOpsManager: AppOpsManager, 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(if (opNames != null) ArrayList(opNames) else null)
.build()
appOpsManager.getHistoricalOps(request, getContext().getMainExecutor(),
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(appOpsManager: AppOpsManager, 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(if (opNames != null) ArrayList(opNames) else null)
.build()
appOpsManager.getHistoricalOpsFromDiskRaw(request, getContext().getMainExecutor(),
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
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
}
private fun getInstrumentation(): Instrumentation {
return InstrumentationRegistry.getInstrumentation()
}
private fun getContext(): Context {
return getInstrumentation().context
}
}
}