• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (C) 2023 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 package android.platform.helpers.notesrole
17 
18 import android.Manifest.permission.INTERACT_ACROSS_USERS
19 import android.Manifest.permission.MANAGE_ROLE_HOLDERS
20 import android.app.role.RoleManager
21 import android.content.Context
22 import android.os.Build
23 import android.os.UserHandle
24 import android.platform.uiautomator_helpers.DeviceHelpers
25 import androidx.core.content.getSystemService
26 import androidx.test.platform.app.InstrumentationRegistry
27 import java.util.concurrent.CompletableFuture
28 import java.util.concurrent.TimeUnit
29 import org.junit.Assume.assumeTrue
30 import org.junit.AssumptionViolatedException
31 
32 /** A helper class to manage [RoleManager.ROLE_NOTES] in end to end tests. */
33 class NotesRoleUtil(private val context: Context) {
34 
35     private val roleManager = context.getSystemService<RoleManager>()!!
36 
37     /**
38      * Checks if the following assumptions are true:
39      * - Android version is at least the supplied required android version. By default U+.
40      * - Notes role is available.
41      * - The required package is installed.
42      *
43      * @throws [AssumptionViolatedException] when any of the above assumptions are not met to help
44      *   skip a test
45      */
checkAndroidRolePackageAssumptionsnull46     fun checkAndroidRolePackageAssumptions(
47         requiredAndroidVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
48         requiredPackage: String,
49     ) {
50         assumeTrue(
51             "Build SDK should be at least $requiredAndroidVersion",
52             Build.VERSION.SDK_INT >= requiredAndroidVersion
53         )
54         assumeTrue(
55             "Notes role should be enabled",
56             roleManager.isRoleAvailable(RoleManager.ROLE_NOTES)
57         )
58         assumeTrue("$requiredPackage should be installed", isPackageInstalled(requiredPackage))
59     }
60 
61     /** Returns the Notes role holder package name. */
getRoleHolderPackageNamenull62     fun getRoleHolderPackageName(userHandle: UserHandle = context.user): String =
63         callWithManageRoleHolderPermission {
64             roleManager
65                 .getRoleHoldersAsUser(RoleManager.ROLE_NOTES, userHandle)
66                 .firstOrNull()
67                 .orEmpty()
68         }
69 
70     /** Force stops the supplied package */
forceStopPackagenull71     fun forceStopPackage(packageName: String) {
72         DeviceHelpers.shell("am force-stop $packageName")
73     }
74 
75     /**
76      * Sets the Notes role holder to "None" by clearing existing role holders. This is possible
77      * because Notes role is an exclusive role.
78      */
clearRoleHoldernull79     fun clearRoleHolder(userHandle: UserHandle = context.user) {
80         val clearRoleHoldersFuture = CompletableFuture<Boolean>()
81         callWithManageRoleHolderPermission {
82             roleManager.clearRoleHoldersAsUser(
83                 RoleManager.ROLE_NOTES,
84                 /* flags= */ 0,
85                 userHandle,
86                 context.mainExecutor,
87                 clearRoleHoldersFuture::complete
88             )
89         }
90 
91         // Synchronously wait for the role to update.
92         assert(
93             clearRoleHoldersFuture.get(
94                 ROLE_MANAGER_VERIFICATION_TIMEOUT_IN_SECONDS,
95                 TimeUnit.SECONDS
96             )
97         ) {
98             "Failed to clear notes role holder"
99         }
100     }
101 
102     /** Sets the supplied package as the Notes role holder app. */
setRoleHoldernull103     fun setRoleHolder(packageName: String, userHandle: UserHandle = context.user) {
104         val currentRoleHolderPackageName = getRoleHolderPackageName()
105 
106         // Return early if current role holder package is the same as supplied package name.
107         if (currentRoleHolderPackageName == packageName) {
108             return
109         }
110 
111         // Notes role is an exclusive role, so clear other role holders before adding the supplied
112         // package to the role. RoleManager has an "addRoleHolder" but no setRoleHolder API so we
113         // have to clear the current role holder before adding the supplied package as role holder.
114         clearRoleHolder(userHandle)
115 
116         // If the supplied package name is empty it indicates that we want to select "None" as the
117         // new Notes role holder, so return early in this case.
118         if (packageName.isEmpty()) {
119             return
120         }
121 
122         // Add the supplied package name to the Notes role.
123         val addRoleHolderFuture = CompletableFuture<Boolean>()
124         callWithManageRoleHolderPermission {
125             roleManager.addRoleHolderAsUser(
126                 RoleManager.ROLE_NOTES,
127                 packageName,
128                 /* flags= */ 0,
129                 userHandle,
130                 context.mainExecutor,
131                 addRoleHolderFuture::complete
132             )
133         }
134 
135         // Synchronously wait for the role to update.
136         assert(
137             addRoleHolderFuture.get(ROLE_MANAGER_VERIFICATION_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
138         ) {
139             "Failed to set $packageName as default notes role holder"
140         }
141     }
142 
143     /** Calls the supplied callable with the provided permissions using shell identity. */
callWithShellIdentityPermissionsnull144     fun <T> callWithShellIdentityPermissions(vararg permissions: String, callable: () -> T): T {
145         val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
146 
147         try {
148             uiAutomation.adoptShellPermissionIdentity(*permissions)
149             return callable()
150         } finally {
151             uiAutomation.dropShellPermissionIdentity()
152         }
153     }
154 
isPackageInstallednull155     private fun isPackageInstalled(packageName: String) =
156         runCatching { context.packageManager.getPackageInfo(packageName, /* flags= */ 0) }.isSuccess
157 
callWithManageRoleHolderPermissionnull158     private fun <T> callWithManageRoleHolderPermission(callable: () -> T): T {
159         return callWithShellIdentityPermissions(MANAGE_ROLE_HOLDERS, INTERACT_ACROSS_USERS) {
160             callable()
161         }
162     }
163 
164     private companion object {
165         const val ROLE_MANAGER_VERIFICATION_TIMEOUT_IN_SECONDS = 10L
166     }
167 }
168