diff options
| author | 2020-04-01 15:19:49 +0100 | |
|---|---|---|
| committer | 2020-04-22 17:23:52 +0000 | |
| commit | c7988a90c749e5c2f8a19d50661492a39c3ec394 (patch) | |
| tree | 5be80254405fdb18aa011747623e28e1047059b5 | |
| parent | 6d5625c5cee23435cfa55ff16fcb57f33caafdae (diff) | |
Allow sysUI to send a11y events for other package and user
As discussed, we:
* Allow apps with INTERACT_ACCROSS_USERS(_FULL) to specify an explicit
user (instead of just USER_CURRENT).
* Introduce new signature permission ACT_AS_PACKAGE_FOR_ACCESSIBILITY
and grant it to sysUI. This permissions allow holders to specify another
package on behalf of which they can perform a11y operations.
This is for toasts since now sysUI renders toasts on behalf of the app
for apps targeting R+.
Bug: 152839254
Test: atest FrameworksServicesTests:AccessibilitySecurityPolicyTest
FrameworksServicesTests:AccessibilityWindowManagerTest
android.widget.cts.ToastTest
Change-Id: I3541045d574518571f348051d53e24ff1a4a67ef
7 files changed, 116 insertions, 18 deletions
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ee25ac27b25c..62c2c5124ae2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3201,6 +3201,11 @@ <permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" android:protectionLevel="signature" /> + <!-- @hide Allows an application to perform accessibility operations (e.g. send events) on + behalf of another package. --> + <permission android:name="android.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY" + android:protectionLevel="signature" /> + <!-- @hide Allows an application to change the accessibility volume. --> <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" android:protectionLevel="signature" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 133d375b8c6e..37900fb13496 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -219,6 +219,7 @@ <!-- accessibility --> <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" /> <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" /> + <uses-permission android:name="android.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY" /> <!-- to control accessibility volume --> <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" /> diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 1a72cf023453..c7eb3ce37f4f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -599,7 +599,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // Make sure the reported package is one the caller has access to. event.setPackageName(mSecurityPolicy.resolveValidReportedPackageLocked( - event.getPackageName(), UserHandle.getCallingAppId(), resolvedUserId)); + event.getPackageName(), UserHandle.getCallingAppId(), resolvedUserId, + getCallingPid())); // This method does nothing for a background user. if (resolvedUserId == mCurrentUserId) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index d98e31eadb22..41f32075fb77 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -167,11 +167,12 @@ public class AccessibilitySecurityPolicy { * @param packageName The package name the app wants to expose * @param appId The app's id * @param userId The app's user id + * @param pid The app's process pid that requested this * @return A package name that is valid to report */ @Nullable public String resolveValidReportedPackageLocked( - @Nullable CharSequence packageName, int appId, int userId) { + @Nullable CharSequence packageName, int appId, int userId, int pid) { // Okay to pass no package if (packageName == null) { return null; @@ -191,6 +192,11 @@ public class AccessibilitySecurityPolicy { .getHostedWidgetPackages(resolvedUid), packageNameStr)) { return packageName.toString(); } + // If app has the targeted permission to act as another package + if (mContext.checkPermission(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY, + pid, resolvedUid) == PackageManager.PERMISSION_GRANTED) { + return packageName.toString(); + } // Otherwise, set the package to the first one in the UID final String[] packageNames = mPackageManager.getPackagesForUid(resolvedUid); if (ArrayUtils.isEmpty(packageNames)) { @@ -403,8 +409,7 @@ public class AccessibilitySecurityPolicy { || userId == UserHandle.USER_CURRENT_OR_SELF) { return currentUserId; } - throw new IllegalArgumentException("Calling user can be changed to only " - + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF."); + return resolveProfileParentLocked(userId); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index 5d97d213928f..468e93a8f683 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -955,7 +955,8 @@ public class AccessibilityWindowManager { // Makes sure the reported package is one the caller has access to. packageName = mSecurityPolicy.resolveValidReportedPackageLocked( - packageName, UserHandle.getCallingAppId(), resolvedUserId); + packageName, UserHandle.getCallingAppId(), resolvedUserId, + Binder.getCallingPid()); windowId = sNextWindowId++; // If the window is from a process that runs across users such as diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java index 5a96347c4ae1..cc8ac86d6b59 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java @@ -28,10 +28,12 @@ import static org.junit.Assert.assertThat; import static org.mockito.AdditionalAnswers.returnsFirstArg; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.AppOpsManager; import android.appwidget.AppWidgetManagerInternal; @@ -71,6 +73,8 @@ public class AccessibilitySecurityPolicyTest { private static final int WINDOWID = 0x000a; private static final int WINDOWID2 = 0x000b; private static final int APP_UID = 10400; + private static final int APP_PID = 2000; + private static final int SYSTEM_PID = 558; private static final String PERMISSION = "test-permission"; private static final String FUNCTION = "test-function-name"; @@ -196,13 +200,13 @@ public class AccessibilitySecurityPolicyTest { @Test public void resolveValidReportedPackage_nullPkgName_returnNull() { assertNull(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - null, Process.SYSTEM_UID, UserHandle.USER_SYSTEM)); + null, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID)); } @Test public void resolveValidReportedPackage_uidIsSystem_returnPkgName() { assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - PACKAGE_NAME, Process.SYSTEM_UID, UserHandle.USER_SYSTEM), + PACKAGE_NAME, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID), PACKAGE_NAME); } @@ -213,7 +217,7 @@ public class AccessibilitySecurityPolicyTest { .thenReturn(APP_UID); assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - PACKAGE_NAME, APP_UID, UserHandle.USER_SYSTEM), + PACKAGE_NAME, APP_UID, UserHandle.USER_SYSTEM, APP_PID), PACKAGE_NAME); } @@ -221,6 +225,7 @@ public class AccessibilitySecurityPolicyTest { public void resolveValidReportedPackage_uidIsWidgetHost_pkgNameIsAppWidget_returnPkgName() throws PackageManager.NameNotFoundException { final int widgetHostUid = APP_UID; + final int widgetHostPid = APP_PID; final String hostPackageName = PACKAGE_NAME; final String widgetPackageName = PACKAGE_NAME2; final ArraySet<String> widgetPackages = new ArraySet<>(); @@ -232,7 +237,7 @@ public class AccessibilitySecurityPolicyTest { .thenReturn(widgetHostUid); assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - widgetPackageName, widgetHostUid, UserHandle.USER_SYSTEM), + widgetPackageName, widgetHostUid, UserHandle.USER_SYSTEM, widgetHostPid), widgetPackageName); } @@ -247,10 +252,52 @@ public class AccessibilitySecurityPolicyTest { .thenThrow(PackageManager.NameNotFoundException.class); when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID)) .thenReturn(new ArraySet<>()); + when(mMockContext.checkPermission( + eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); - assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked( - invalidPackageName, APP_UID, UserHandle.USER_SYSTEM), - PACKAGE_NAME); + assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked( + invalidPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID)); + } + + @Test + public void resolveValidReportedPackage_anotherPkgNameWithActAsPkgPermission_returnPkg() + throws PackageManager.NameNotFoundException { + final String wantedPackageName = PACKAGE_NAME2; + final int wantedUid = APP_UID + 1; + final String[] uidPackages = {PACKAGE_NAME}; + when(mMockPackageManager.getPackagesForUid(APP_UID)) + .thenReturn(uidPackages); + when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM)) + .thenReturn(wantedUid); + when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID)) + .thenReturn(new ArraySet<>()); + when(mMockContext.checkPermission( + eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID))) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + assertEquals(wantedPackageName, mA11ySecurityPolicy.resolveValidReportedPackageLocked( + wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID)); + } + + @Test + public void resolveValidReportedPackage_anotherPkgNameWithoutActAsPkgPermission_returnUidPkg() + throws PackageManager.NameNotFoundException { + final String wantedPackageName = PACKAGE_NAME2; + final int wantedUid = APP_UID + 1; + final String[] uidPackages = {PACKAGE_NAME}; + when(mMockPackageManager.getPackagesForUid(APP_UID)) + .thenReturn(uidPackages); + when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM)) + .thenReturn(wantedUid); + when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID)) + .thenReturn(new ArraySet<>()); + when(mMockContext.checkPermission( + eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + + assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked( + wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID)); } @Test @@ -432,21 +479,59 @@ public class AccessibilitySecurityPolicyTest { UserHandle.USER_CURRENT_OR_SELF); } - @Test(expected = IllegalArgumentException.class) - public void resolveCallingUserId_callingParentNotCurrentUser_userIdIsInvalid_shouldException() { + @Test + public void resolveCallingUserId_anotherUserIdWithCrossUserPermission_returnUserId() { final AccessibilitySecurityPolicy spySecurityPolicy = Mockito.spy(mA11ySecurityPolicy); final int callingUserId = UserHandle.getUserId(Process.myUid()); final int callingParentId = 20; final int currentUserId = 30; - final int invalidUserId = 40; + final int wantedUserId = 40; when(mMockA11yUserManager.getCurrentUserIdLocked()) .thenReturn(currentUserId); doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( callingUserId); - when(mMockContext.checkCallingPermission(any())) + when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + + assertEquals(wantedUserId, + spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId)); + } + + @Test + public void resolveCallingUserId_anotherUserIdWithCrossUserFullPermission_returnUserId() { + final AccessibilitySecurityPolicy spySecurityPolicy = Mockito.spy(mA11ySecurityPolicy); + final int callingUserId = UserHandle.getUserId(Process.myUid()); + final int callingParentId = 20; + final int currentUserId = 30; + final int wantedUserId = 40; + when(mMockA11yUserManager.getCurrentUserIdLocked()) + .thenReturn(currentUserId); + doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( + callingUserId); + when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) .thenReturn(PackageManager.PERMISSION_GRANTED); - spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(invalidUserId); + assertEquals(wantedUserId, + spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId)); + } + + @Test(expected = SecurityException.class) + public void resolveCallingUserId_anotherUserIdWithoutCrossUserPermission_shouldException() { + final AccessibilitySecurityPolicy spySecurityPolicy = Mockito.spy(mA11ySecurityPolicy); + final int callingUserId = UserHandle.getUserId(Process.myUid()); + final int callingParentId = 20; + final int currentUserId = 30; + final int wantedUserId = 40; + when(mMockA11yUserManager.getCurrentUserIdLocked()) + .thenReturn(currentUserId); + doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked( + callingUserId); + when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS)) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)) + .thenReturn(PackageManager.PERMISSION_DENIED); + + spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index 10a86f9ea527..e4d51e4374a7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -134,7 +134,7 @@ public class AccessibilityWindowManagerTest { when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked( USER_SYSTEM_ID)).thenReturn(USER_SYSTEM_ID); when(mMockA11ySecurityPolicy.resolveValidReportedPackageLocked( - anyString(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME); + anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME); mA11yWindowManager = new AccessibilityWindowManager(new Object(), mHandler, mMockWindowManagerInternal, |