blob: 9a854a345ec87e6cefeb07c232b293bc6bf70e8f [file] [log] [blame]
package android.app.appops.cts
/*
* Copyright (C) 2014 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.
*/
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_DEFAULT
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.MODE_IGNORED
import android.app.AppOpsManager.OPSTR_READ_CALENDAR
import android.app.AppOpsManager.OPSTR_READ_SMS
import android.app.AppOpsManager.OPSTR_RECORD_AUDIO
import android.app.appops.cts.AppOpsUtils.Companion.allowedOperationLogged;
import android.app.appops.cts.AppOpsUtils.Companion.rejectedOperationLogged;
import android.app.appops.cts.AppOpsUtils.Companion.setOpMode;
import org.mockito.Mockito.mock
import org.mockito.Mockito.reset
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import android.Manifest.permission
import android.app.AppOpsManager
import android.app.AppOpsManager.OnOpChangedListener
import android.app.Instrumentation
import android.content.Context
import android.os.Process
import androidx.test.runner.AndroidJUnit4
import androidx.test.InstrumentationRegistry
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.util.HashMap
import java.util.HashSet
@RunWith(AndroidJUnit4::class)
class AppOpsTest {
// Notifying OnOpChangedListener callbacks is an async operation, so we define a timeout.
private val MODE_WATCHER_TIMEOUT_MS = 5000L
private lateinit var mAppOps: AppOpsManager
private lateinit var mContext: Context
private lateinit var mOpPackageName: String
companion object {
// These permissions and opStrs must map to the same op codes.
val permissionToOpStr = HashMap<String, String>()
init {
permissionToOpStr[permission.ACCESS_COARSE_LOCATION] =
AppOpsManager.OPSTR_COARSE_LOCATION
permissionToOpStr[permission.ACCESS_FINE_LOCATION] =
AppOpsManager.OPSTR_FINE_LOCATION
permissionToOpStr[permission.READ_CONTACTS] =
AppOpsManager.OPSTR_READ_CONTACTS
permissionToOpStr[permission.WRITE_CONTACTS] =
AppOpsManager.OPSTR_WRITE_CONTACTS
permissionToOpStr[permission.READ_CALL_LOG] =
AppOpsManager.OPSTR_READ_CALL_LOG
permissionToOpStr[permission.WRITE_CALL_LOG] =
AppOpsManager.OPSTR_WRITE_CALL_LOG
permissionToOpStr[permission.READ_CALENDAR] =
AppOpsManager.OPSTR_READ_CALENDAR
permissionToOpStr[permission.WRITE_CALENDAR] =
AppOpsManager.OPSTR_WRITE_CALENDAR
permissionToOpStr[permission.CALL_PHONE] =
AppOpsManager.OPSTR_CALL_PHONE
permissionToOpStr[permission.READ_SMS] =
AppOpsManager.OPSTR_READ_SMS
permissionToOpStr[permission.RECEIVE_SMS] =
AppOpsManager.OPSTR_RECEIVE_SMS
permissionToOpStr[permission.RECEIVE_MMS] =
AppOpsManager.OPSTR_RECEIVE_MMS
permissionToOpStr[permission.RECEIVE_WAP_PUSH] =
AppOpsManager.OPSTR_RECEIVE_WAP_PUSH
permissionToOpStr[permission.SEND_SMS] =
AppOpsManager.OPSTR_SEND_SMS
permissionToOpStr[permission.READ_SMS] =
AppOpsManager.OPSTR_READ_SMS
permissionToOpStr[permission.WRITE_SETTINGS] =
AppOpsManager.OPSTR_WRITE_SETTINGS
permissionToOpStr[permission.SYSTEM_ALERT_WINDOW] =
AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW
permissionToOpStr[permission.ACCESS_NOTIFICATIONS] =
AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS
permissionToOpStr[permission.CAMERA] =
AppOpsManager.OPSTR_CAMERA
permissionToOpStr[permission.RECORD_AUDIO] =
AppOpsManager.OPSTR_RECORD_AUDIO
permissionToOpStr[permission.READ_PHONE_STATE] =
AppOpsManager.OPSTR_READ_PHONE_STATE
permissionToOpStr[permission.ADD_VOICEMAIL] =
AppOpsManager.OPSTR_ADD_VOICEMAIL
permissionToOpStr[permission.USE_SIP] =
AppOpsManager.OPSTR_USE_SIP
permissionToOpStr[permission.PROCESS_OUTGOING_CALLS] =
AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS
permissionToOpStr[permission.BODY_SENSORS] =
AppOpsManager.OPSTR_BODY_SENSORS
permissionToOpStr[permission.READ_CELL_BROADCASTS] =
AppOpsManager.OPSTR_READ_CELL_BROADCASTS
permissionToOpStr[permission.READ_EXTERNAL_STORAGE] =
AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE
permissionToOpStr[permission.WRITE_EXTERNAL_STORAGE] =
AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE
}
}
@Before
fun setUp() {
mContext = InstrumentationRegistry.getContext()
mAppOps = mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
mOpPackageName = mContext.opPackageName
assertNotNull(mAppOps)
// Reset app ops state for this test package to the system default.
AppOpsUtils.reset(mOpPackageName)
}
@Test
fun testNoteOpAndCheckOp() {
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
assertEquals(MODE_ALLOWED, mAppOps.noteOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_ALLOWED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_ALLOWED, mAppOps.unsafeCheckOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_ALLOWED, mAppOps.unsafeCheckOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_IGNORED)
assertEquals(MODE_IGNORED, mAppOps.noteOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_IGNORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_IGNORED, mAppOps.unsafeCheckOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_IGNORED, mAppOps.unsafeCheckOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_DEFAULT)
assertEquals(MODE_DEFAULT, mAppOps.noteOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_DEFAULT, mAppOps.noteOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_DEFAULT, mAppOps.unsafeCheckOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_DEFAULT, mAppOps.unsafeCheckOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED)
assertEquals(MODE_ERRORED, mAppOps.noteOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_ERRORED, mAppOps.unsafeCheckOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
try {
mAppOps.noteOp(OPSTR_READ_SMS, Process.myUid(), mOpPackageName)
fail("SecurityException expected")
} catch (expected: SecurityException) {
}
try {
mAppOps.unsafeCheckOp(OPSTR_READ_SMS, Process.myUid(), mOpPackageName)
fail("SecurityException expected")
} catch (expected: SecurityException) {
}
}
@Test
fun testStartOpAndFinishOp() {
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
assertEquals(MODE_ALLOWED, mAppOps.startOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
mAppOps.finishOp(OPSTR_READ_SMS, Process.myUid(), mOpPackageName)
assertEquals(MODE_ALLOWED, mAppOps.startOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
mAppOps.finishOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName)
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_IGNORED)
assertEquals(MODE_IGNORED, mAppOps.startOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_IGNORED, mAppOps.startOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_DEFAULT)
assertEquals(MODE_DEFAULT, mAppOps.startOp(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
assertEquals(MODE_DEFAULT, mAppOps.startOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED)
assertEquals(MODE_ERRORED, mAppOps.startOpNoThrow(OPSTR_READ_SMS,
Process.myUid(), mOpPackageName))
try {
mAppOps.startOp(OPSTR_READ_SMS, Process.myUid(), mOpPackageName)
fail("SecurityException expected")
} catch (expected: SecurityException) {
}
}
@Test
fun testCheckPackagePassesCheck() {
mAppOps.checkPackage(Process.myUid(), mOpPackageName)
mAppOps.checkPackage(Process.SYSTEM_UID, "android")
}
@Test
fun testCheckPackageDoesntPassCheck() {
try {
// Package name doesn't match UID.
mAppOps.checkPackage(Process.SYSTEM_UID, mOpPackageName)
fail("SecurityException expected")
} catch (expected: SecurityException ) {
}
try {
// Package name doesn't match UID.
mAppOps.checkPackage(Process.myUid(), "android")
fail("SecurityException expected")
} catch (expected: SecurityException) {
}
try {
// Package name missing
mAppOps.checkPackage(Process.myUid(), "")
fail("SecurityException expected")
} catch (expected: SecurityException) {
}
}
@Test
fun testWatchingMode() {
val watcher = mock(OnOpChangedListener::class.java)
try {
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
mAppOps.startWatchingMode(OPSTR_READ_SMS, mOpPackageName, watcher)
// Make a change to the app op's mode.
reset(watcher)
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED)
verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
.onOpChanged(OPSTR_READ_SMS, mOpPackageName)
// Make another change to the app op's mode.
reset(watcher)
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
.onOpChanged(OPSTR_READ_SMS, mOpPackageName)
// Set mode to the same value as before - expect no call to the listener.
reset(watcher)
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED)
verifyZeroInteractions(watcher)
mAppOps.stopWatchingMode(watcher)
// Make a change to the app op's mode. Since we already stopped watching the mode, the
// listener shouldn't be called.
reset(watcher)
setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ERRORED)
verifyZeroInteractions(watcher)
} finally {
// Clean up registered watcher.
mAppOps.stopWatchingMode(watcher)
}
}
@Test
fun testAllOpsHaveOpString() {
val opStrs = HashSet<String>()
for (opStr in AppOpsManager.getOpStrs()) {
assertNotNull("Each app op must have an operation string defined", opStr)
opStrs.add(opStr)
}
assertEquals("Not all op strings are unique", AppOpsManager.getNumOps(), opStrs.size)
}
@Test
fun testOpCodesUnique() {
val opStrs = AppOpsManager.getOpStrs()
val opCodes = HashSet<Int>()
for (opStr in opStrs) {
opCodes.add(AppOpsManager.strOpToOp(opStr))
}
assertEquals("Not all app op codes are unique", opStrs.size, opCodes.size)
}
@Test
fun testPermissionMapping() {
for (entry in permissionToOpStr) {
testPermissionMapping(entry.key, permissionToOpStr[entry.key])
}
}
private fun testPermissionMapping(permission: String, opStr: String?) {
// Do the public value => internal op code lookups.
val mappedOpStr = AppOpsManager.permissionToOp(permission)
assertEquals(mappedOpStr, opStr)
val mappedOpCode = AppOpsManager.permissionToOpCode(permission)
val mappedOpCode2 = AppOpsManager.strOpToOp(opStr)
assertEquals(mappedOpCode, mappedOpCode2)
// Do the internal op code => public value lookup (reverse lookup).
val permissionMappedBack = AppOpsManager.opToPermission(mappedOpCode)
assertEquals(permission, permissionMappedBack)
}
/**
* Test that the app can not change the app op mode for itself.
*/
@Test
fun testCantSetModeForSelf() {
try {
val writeSmsOp = AppOpsManager.permissionToOpCode("android.permission.WRITE_SMS")
mAppOps.setMode(writeSmsOp, Process.myUid(), mOpPackageName, AppOpsManager.MODE_ALLOWED)
fail("Was able to set mode for self")
} catch (expected: SecurityException) {
}
}
@Test
fun testGetOpsForPackageOpsAreLogged() {
// This test checks if operations get logged by the system. It needs to start with a clean
// slate, i.e. these ops can't have been logged previously for this test package. The reason
// is that there's no API for clearing the app op logs before a test run. However, the op
// logs are cleared when this test package is reinstalled between test runs. To make sure
// that other test methods in this class don't affect this test method, here we use
// operations that are not used by any other test cases.
val mustNotBeLogged = "Operation mustn't be logged before the test runs"
assertFalse(mustNotBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO))
assertFalse(mustNotBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR))
setOpMode(mOpPackageName, OPSTR_RECORD_AUDIO, MODE_ALLOWED)
setOpMode(mOpPackageName, OPSTR_READ_CALENDAR, MODE_ERRORED)
// Note an op that's allowed.
mAppOps.noteOp(OPSTR_RECORD_AUDIO, Process.myUid(), mOpPackageName)
val mustBeLogged = "Operation must be logged"
assertTrue(mustBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO))
// Note another op that's not allowed.
mAppOps.noteOpNoThrow(OPSTR_READ_CALENDAR, Process.myUid(), mOpPackageName)
assertTrue(mustBeLogged, allowedOperationLogged(mOpPackageName, OPSTR_RECORD_AUDIO))
assertTrue(mustBeLogged, rejectedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR))
}
}