Allow callers with automotive projection role to call startBugreport().

See go/delayed-bugreport-retrieval-v2 for full context. The delayed bugreport retrieval API was added for Android Auto, but Android Auto does not have access to use it without this change.

BYPASS_INCLUSIVE_LANGUAGE_REASON=The name of the config field cannot be changed.

Bug: 288141332
Test: Treehugger passes and tested on phone
Change-Id: I2b612b367650f0e1f9ddef4e6c95da4540a12149
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 0605345..3ba307b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -64,6 +65,8 @@
     private static final int LOCAL_LOG_SIZE = 20;
     private static final String TAG = "BugreportManagerService";
     private static final boolean DEBUG = false;
+    private static final String ROLE_SYSTEM_AUTOMOTIVE_PROJECTION =
+            "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION";
 
     private static final String BUGREPORT_SERVICE = "bugreportd";
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
@@ -326,11 +329,22 @@
 
         // To gain access through the DUMP permission, the OEM has to allow this package explicitly
         // via sysconfig and privileged permissions.
-        if (mBugreportAllowlistedPackages.contains(callingPackage)
-                && mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                        == PackageManager.PERMISSION_GRANTED) {
+        boolean allowlisted = mBugreportAllowlistedPackages.contains(callingPackage);
+        if (!allowlisted) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                allowlisted = mContext.getSystemService(RoleManager.class).getRoleHolders(
+                        ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        if (allowlisted && mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP) == PackageManager.PERMISSION_GRANTED) {
             return;
         }
+
         // For carrier privileges, this can include user-installed apps. This is essentially a
         // function of the current active SIM(s) in the device to let carrier apps through.
         final long token = Binder.clearCallingIdentity();
@@ -346,7 +360,8 @@
 
         String message =
                 callingPackage
-                        + " does not hold the DUMP permission or is not bugreport-whitelisted "
+                        + " does not hold the DUMP permission or is not bugreport-whitelisted or "
+                        + "does not have an allowed role "
                         + (checkCarrierPrivileges ? "and does not have carrier privileges " : "")
                         + "to request a bugreport";
         Slog.w(TAG, message);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 107dde2..fa0a971 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -110,6 +110,7 @@
     <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
     <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+    <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 52c6777..24029b1 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,15 +16,18 @@
 
 package com.android.server.os;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
 
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
 import android.os.IBinder;
 import android.os.IDumpstateListener;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -37,21 +40,23 @@
 import org.junit.runner.RunWith;
 
 import java.io.FileDescriptor;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
 public class BugreportManagerServiceImplTest {
 
-    Context mContext;
-    BugreportManagerServiceImpl mService;
-    BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
+    private Context mContext;
+    private BugreportManagerServiceImpl mService;
+    private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
 
-    int mCallingUid = 1234;
-    String mCallingPackage  = "test.package";
+    private int mCallingUid = 1234;
+    private String mCallingPackage  = "test.package";
 
-    String mBugreportFile = "bugreport-file.zip";
-    String mBugreportFile2 = "bugreport-file2.zip";
+    private String mBugreportFile = "bugreport-file.zip";
+    private String mBugreportFile2 = "bugreport-file2.zip";
 
     @Before
     public void setUp() {
@@ -109,6 +114,36 @@
                 BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
     }
 
+    @Test
+    public void testCancelBugreportWithoutRole() throws Exception {
+        // Clear out allowlisted packages.
+        mService = new BugreportManagerServiceImpl(
+                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>()));
+
+        assertThrows(SecurityException.class, () -> mService.cancelBugreport(
+                Binder.getCallingUid(), mContext.getPackageName()));
+    }
+
+    @Test
+    public void testCancelBugreportWithRole() throws Exception {
+        // Clear out allowlisted packages.
+        mService = new BugreportManagerServiceImpl(
+                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>()));
+        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+        CallbackFuture future = new CallbackFuture();
+        runWithShellPermissionIdentity(() -> roleManager.setBypassingRoleQualification(true));
+        runWithShellPermissionIdentity(() -> roleManager.addRoleHolderAsUser(
+                "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
+                mContext.getPackageName(),
+                /* flags= */ 0,
+                Process.myUserHandle(),
+                mContext.getMainExecutor(),
+                future));
+
+        assertThat(future.get()).isEqualTo(true);
+        mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
+    }
+
     private static class Listener implements IDumpstateListener {
         CountDownLatch mLatch;
         int mErrorCode;
@@ -149,4 +184,12 @@
             return mErrorCode;
         }
     }
+
+    private static class CallbackFuture extends CompletableFuture<Boolean>
+            implements Consumer<Boolean> {
+        @Override
+        public void accept(Boolean successful) {
+            complete(successful);
+        }
+    }
 }