summaryrefslogtreecommitdiff
path: root/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
blob: e10131c3f66a5eed211d30618557ce14ef1671e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
 * Copyright (C) 2019 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.permission2.cts

import android.Manifest.permission.ACCEPT_HANDOVER
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.Manifest.permission.ACTIVITY_RECOGNITION
import android.Manifest.permission.ADD_VOICEMAIL
import android.Manifest.permission.ANSWER_PHONE_CALLS
import android.Manifest.permission.BLUETOOTH_ADVERTISE
import android.Manifest.permission.BLUETOOTH_CONNECT
import android.Manifest.permission.BLUETOOTH_SCAN
import android.Manifest.permission.BODY_SENSORS
import android.Manifest.permission.CALL_PHONE
import android.Manifest.permission.CAMERA
import android.Manifest.permission.GET_ACCOUNTS
import android.Manifest.permission.NEARBY_WIFI_DEVICES
import android.Manifest.permission.PACKAGE_USAGE_STATS
import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.PROCESS_OUTGOING_CALLS
import android.Manifest.permission.READ_CALENDAR
import android.Manifest.permission.READ_CALL_LOG
import android.Manifest.permission.READ_CELL_BROADCASTS
import android.Manifest.permission.READ_CONTACTS
import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
import android.Manifest.permission.READ_PHONE_NUMBERS
import android.Manifest.permission.READ_PHONE_STATE
import android.Manifest.permission.READ_SMS
import android.Manifest.permission.RECEIVE_MMS
import android.Manifest.permission.RECEIVE_SMS
import android.Manifest.permission.RECEIVE_WAP_PUSH
import android.Manifest.permission.RECORD_AUDIO
import android.Manifest.permission.SEND_SMS
import android.Manifest.permission.USE_SIP
import android.Manifest.permission.UWB_RANGING
import android.Manifest.permission.WRITE_CALENDAR
import android.Manifest.permission.WRITE_CALL_LOG
import android.Manifest.permission.WRITE_CONTACTS
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.Manifest.permission_group.UNDEFINED
import android.app.AppOpsManager.permissionToOp
import android.content.pm.PackageManager.GET_PERMISSIONS
import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS
import android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP
import android.os.Build
import android.permission.PermissionManager
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class RuntimePermissionProperties {
    private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
    private val pm = context.packageManager

    private val platformPkg = pm.getPackageInfo("android", GET_PERMISSIONS)
    private val platformRuntimePerms = platformPkg.permissions
            .filter { it.protection == PROTECTION_DANGEROUS }
    private val platformBgPermNames = platformRuntimePerms.mapNotNull { it.backgroundPermission }

    @Test
    fun allRuntimeForegroundPermissionNeedAnAppOp() {
        val platformFgPerms =
            platformRuntimePerms.filter { !platformBgPermNames.contains(it.name) }

        for (perm in platformFgPerms) {
            assertWithMessage("AppOp for ${perm.name}").that(permissionToOp(perm.name)).isNotNull()
        }
    }

    @Test
    fun groupOfRuntimePermissionsShouldBeUnknown() {
        for (perm in platformRuntimePerms) {
            assertWithMessage("Group of ${perm.name}").that(perm.group).isEqualTo(UNDEFINED)
        }
    }

    @Test
    fun allAppOpPermissionNeedAnAppOp() {
        val platformAppOpPerms = platformPkg.permissions
                .filter { (it.protectionFlags and PROTECTION_FLAG_APPOP) != 0 }
                .filter {
                    // Grandfather incomplete definition of PACKAGE_USAGE_STATS
                    it.name != PACKAGE_USAGE_STATS
                }

        for (perm in platformAppOpPerms) {
            assertWithMessage("AppOp for ${perm.name}").that(permissionToOp(perm.name)).isNotNull()
        }
    }

    /**
     * The permission of a background permission is the one of its foreground permission
     */
    @Test
    fun allRuntimeBackgroundPermissionCantHaveAnAppOp() {
        val platformBgPerms =
            platformRuntimePerms.filter { platformBgPermNames.contains(it.name) }

        for (perm in platformBgPerms) {
            assertWithMessage("AppOp for ${perm.name}").that(permissionToOp(perm.name)).isNull()
        }
    }

    /**
     * Commonly a new runtime permission is created by splitting an old one into twice
     */
    @Test
    fun runtimePermissionsShouldHaveBeenSplitFromPreviousPermission() {
        // Runtime permissions in Android P
        val expectedPerms = mutableSetOf(READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS, READ_CALENDAR,
            WRITE_CALENDAR, SEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_MMS, RECEIVE_WAP_PUSH,
            READ_CELL_BROADCASTS, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE,
            ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, READ_CALL_LOG, WRITE_CALL_LOG,
            PROCESS_OUTGOING_CALLS, READ_PHONE_STATE, READ_PHONE_NUMBERS, CALL_PHONE,
            ADD_VOICEMAIL, USE_SIP, ANSWER_PHONE_CALLS, ACCEPT_HANDOVER, RECORD_AUDIO, CAMERA,
            BODY_SENSORS)

        // Add permission split since P
        for (sdkVersion in Build.VERSION_CODES.P + 1..Build.VERSION_CODES.CUR_DEVELOPMENT + 1) {
            for (splitPerm in
                context.getSystemService(PermissionManager::class.java)!!.splitPermissions) {
                if (splitPerm.targetSdk == sdkVersion &&
                    expectedPerms.contains(splitPerm.splitPermission)) {
                    expectedPerms.addAll(splitPerm.newPermissions)
                }
            }
        }

        // Add runtime permission added in Q which were _not_ split from a previously existing
        // runtime permission
        expectedPerms.add(ACTIVITY_RECOGNITION)

        // Add runtime permissions added in S which were _not_ split from a previously existing
        // runtime permission
        expectedPerms.add(BLUETOOTH_ADVERTISE)
        expectedPerms.add(BLUETOOTH_CONNECT)
        expectedPerms.add(BLUETOOTH_SCAN)
        expectedPerms.add(UWB_RANGING)

        // Add runtime permissions added in T which were _not_ split from a previously existing
        // runtime permission
        expectedPerms.add(POST_NOTIFICATIONS)
        expectedPerms.add(NEARBY_WIFI_DEVICES)

        // Add runtime permissions added in U which were _not_ split from a previously existing
        // runtime permission
        expectedPerms.add(READ_MEDIA_VISUAL_USER_SELECTED)
        assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
    }
}