• 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.uiautomatorhelpers.DeviceHelpers
25 import android.util.Log
26 import androidx.core.content.getSystemService
27 import androidx.test.platform.app.InstrumentationRegistry
28 import java.util.concurrent.CompletableFuture
29 import java.util.concurrent.TimeUnit
30 import org.junit.Assume.assumeTrue
31 import org.junit.AssumptionViolatedException
32 
33 /** A helper class to manage [RoleManager.ROLE_NOTES] in end to end tests. */
34 class NotesRoleUtil(private val context: Context) {
35 
36     private val roleManager = context.getSystemService<RoleManager>()!!
37 
38     /**
39      * Checks if the following assumptions are true:
40      * - Android version is at least the supplied required android version. By default U+.
41      * - Notes role is available.
42      * - The required package is installed.
43      *
44      * @throws [AssumptionViolatedException] when any of the above assumptions are not met to help
45      *   skip a test
46      */
checkAndroidRolePackageAssumptionsnull47     fun checkAndroidRolePackageAssumptions(
48         requiredAndroidVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
49         requiredPackage: String,
50     ) {
51         assumeTrue(
52             "Build SDK should be at least $requiredAndroidVersion",
53             Build.VERSION.SDK_INT >= requiredAndroidVersion,
54         )
55         assumeTrue(
56             "Notes role should be enabled",
57             roleManager.isRoleAvailable(RoleManager.ROLE_NOTES),
58         )
59         assumeTrue("$requiredPackage should be installed", isPackageInstalled(requiredPackage))
60     }
61 
62     /** Returns the Notes role holder package name. */
getRoleHolderPackageNamenull63     fun getRoleHolderPackageName(userHandle: UserHandle = context.user): String =
64         callWithManageRoleHolderPermission {
65             roleManager
66                 .getRoleHoldersAsUser(RoleManager.ROLE_NOTES, userHandle)
67                 .firstOrNull()
68                 .orEmpty()
69         }
70 
71     /** Force stops the supplied package */
forceStopPackagenull72     fun forceStopPackage(packageName: String) {
73         DeviceHelpers.shell("am force-stop $packageName")
74     }
75 
76     /**
77      * Sets the Notes role holder to "None" by clearing existing role holders. This is possible
78      * because Notes role is an exclusive role.
79      */
clearRoleHoldernull80     fun clearRoleHolder(userHandle: UserHandle = context.user) {
81         val clearRoleHoldersFuture = CompletableFuture<Boolean>()
82         callWithManageRoleHolderPermission {
83             roleManager.clearRoleHoldersAsUser(
84                 RoleManager.ROLE_NOTES,
85                 /* flags= */ 0,
86                 userHandle,
87                 context.mainExecutor,
88                 clearRoleHoldersFuture::complete,
89             )
90         }
91 
92         // Synchronously wait for the role to update.
93         assert(
94             clearRoleHoldersFuture.get(
95                 ROLE_MANAGER_VERIFICATION_TIMEOUT_IN_SECONDS,
96                 TimeUnit.SECONDS,
97             )
98         ) {
99             "Failed to clear notes role holder"
100         }
101     }
102 
103     /** Sets the supplied package as the Notes role holder app. */
setRoleHoldernull104     fun setRoleHolder(packageName: String, userHandle: UserHandle = context.user) {
105         val currentRoleHolderPackageName = getRoleHolderPackageName()
106         Log.d(
107             TAG,
108             "Trying to set note role to $packageName. Current role holder: " +
109                 "$currentRoleHolderPackageName",
110         )
111 
112         // Return early if current role holder package is the same as supplied package name.
113         if (currentRoleHolderPackageName == packageName) {
114             return
115         }
116 
117         // Notes role is an exclusive role, so clear other role holders before adding the supplied
118         // package to the role. RoleManager has an "addRoleHolder" but no setRoleHolder API so we
119         // have to clear the current role holder before adding the supplied package as role holder.
120         clearRoleHolder(userHandle)
121         Log.d(TAG, "After clear note role")
122 
123         // If the supplied package name is empty it indicates that we want to select "None" as the
124         // new Notes role holder, so return early in this case.
125         if (packageName.isEmpty()) {
126             return
127         }
128 
129         // Add the supplied package name to the Notes role.
130         val addRoleHolderFuture = CompletableFuture<Boolean>()
131         Log.d(TAG, "Start setting note holder to $packageName")
132         callWithManageRoleHolderPermission {
133             roleManager.addRoleHolderAsUser(
134                 RoleManager.ROLE_NOTES,
135                 packageName,
136                 /* flags= */ 0,
137                 userHandle,
138                 context.mainExecutor,
139             ) {
140                 Log.d(TAG, "Callback for setting note role to $packageName: $it")
141                 addRoleHolderFuture.complete(it)
142             }
143         }
144 
145         // Synchronously wait for the role to update.
146         assert(
147             addRoleHolderFuture.get(ROLE_MANAGER_VERIFICATION_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
148         ) {
149             "Failed to set $packageName as default notes role holder"
150         }
151         Log.d(TAG, "After setting note role to $packageName")
152     }
153 
154     /** Calls the supplied callable with the provided permissions using shell identity. */
callWithShellIdentityPermissionsnull155     fun <T> callWithShellIdentityPermissions(vararg permissions: String, callable: () -> T): T {
156         val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
157 
158         try {
159             uiAutomation.adoptShellPermissionIdentity(*permissions)
160             return callable()
161         } finally {
162             uiAutomation.dropShellPermissionIdentity()
163         }
164     }
165 
isPackageInstallednull166     private fun isPackageInstalled(packageName: String) =
167         runCatching { context.packageManager.getPackageInfo(packageName, /* flags= */ 0) }.isSuccess
168 
callWithManageRoleHolderPermissionnull169     private fun <T> callWithManageRoleHolderPermission(callable: () -> T): T {
170         return callWithShellIdentityPermissions(MANAGE_ROLE_HOLDERS, INTERACT_ACROSS_USERS) {
171             callable()
172         }
173     }
174 
175     private companion object {
176         const val TAG = "NotesRoleUtil"
177         const val ROLE_MANAGER_VERIFICATION_TIMEOUT_IN_SECONDS = 60L
178     }
179 }
180