Add permission caching to PermissionController

Audioserver currently calls upwards to system_server for all permission
checks.

Invert the call direction, and maintain mapping of uids which hold a
particular permission. Add AIDL interface for system_server and enum
representing relevant permissions.

Test: atest audiopermissioncontroller_test
Bug: 338089555
Flag: com.android.media.audio.audioserver_permissions
Change-Id: I45be63217fa58544e6dd9530a5d9e29b99f31ade
diff --git a/aidl/com/android/media/permission/INativePermissionController.aidl b/aidl/com/android/media/permission/INativePermissionController.aidl
index 5766e33..a14092d 100644
--- a/aidl/com/android/media/permission/INativePermissionController.aidl
+++ b/aidl/com/android/media/permission/INativePermissionController.aidl
@@ -16,6 +16,7 @@
 
 package com.android.media.permission;
 
+import com.android.media.permission.PermissionEnum;
 import com.android.media.permission.UidPackageState;
 
 /**
@@ -33,4 +34,13 @@
      * If the list is empty, the package no longer exists.
      */
     void updatePackagesForUid(in UidPackageState newPackageState);
+    /**
+     * Populate or replace the list of uids which holds a particular permission.
+     * Runtime permissions will need additional checks, and should not use the cache as-is.
+     * Not virtual device aware.
+     * Is is possible for updates to the permission state to be delayed during high traffic.
+     * @param perm - Enum representing the permission for which holders are being supplied
+     * @param uids - Uids (not app-ids) which hold the permission. Should be sorted
+     */
+    void populatePermissionState(in PermissionEnum perm, in int[] uids);
 }
diff --git a/aidl/com/android/media/permission/PermissionEnum.aidl b/aidl/com/android/media/permission/PermissionEnum.aidl
new file mode 100644
index 0000000..834a275
--- /dev/null
+++ b/aidl/com/android/media/permission/PermissionEnum.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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 com.android.media.permission;
+
+/**
+ * Enumerates permissions which are tracked/pushed by NativePermissionController
+ * {@hide}
+ */
+enum PermissionEnum {
+    MODIFY_AUDIO_ROUTING = 0,
+    MODIFY_PHONE_STATE = 1,
+    CALL_AUDIO_INTERCEPTION = 2,
+    // This is a runtime + WIU permission, which means data delivery should be protected by AppOps
+    // We query the controller only for early fails/hard errors
+    RECORD_AUDIO = 3,
+    ENUM_SIZE = 4, // Not for actual usage
+}
diff --git a/services/audiopolicy/permission/NativePermissionController.cpp b/services/audiopolicy/permission/NativePermissionController.cpp
index d88c69f..9fcf22d 100644
--- a/services/audiopolicy/permission/NativePermissionController.cpp
+++ b/services/audiopolicy/permission/NativePermissionController.cpp
@@ -88,6 +88,19 @@
     return Status::ok();
 }
 
+Status NativePermissionController::populatePermissionState(PermissionEnum perm,
+                                                           const std::vector<int>& uids) {
+    if (perm >= PermissionEnum::ENUM_SIZE || static_cast<int>(perm) < 0) {
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
+    }
+    std::lock_guard l{m_};
+    auto& cursor = permission_map_[static_cast<size_t>(perm)];
+    cursor = std::vector<uid_t>{uids.begin(), uids.end()};
+    // should be sorted
+    std::sort(cursor.begin(), cursor.end());
+    return Status::ok();
+}
+
 // -- End Binder methods
 
 Result<std::vector<std::string>> NativePermissionController::getPackagesForUid(uid_t uid) const {
@@ -120,4 +133,15 @@
            (std::find(cursor->second.begin(), cursor->second.end(), packageName) !=
             cursor->second.end());
 }
+
+Result<bool> NativePermissionController::checkPermission(PermissionEnum perm, uid_t uid) const {
+    std::lock_guard l{m_};
+    const auto& uids = permission_map_[static_cast<size_t>(perm)];
+    if (!uids.empty()) {
+        return std::binary_search(uids.begin(), uids.end(), uid);
+    } else {
+        return unexpected{::android::NO_INIT};
+    }
+}
+
 }  // namespace com::android::media::permission
diff --git a/services/audiopolicy/permission/include/media/IPermissionProvider.h b/services/audiopolicy/permission/include/media/IPermissionProvider.h
index 9184ffb..27a61ea 100644
--- a/services/audiopolicy/permission/include/media/IPermissionProvider.h
+++ b/services/audiopolicy/permission/include/media/IPermissionProvider.h
@@ -21,6 +21,7 @@
 #include <optional>
 #include <vector>
 
+#include <com/android/media/permission/PermissionEnum.h>
 #include <error/Result.h>
 
 namespace com::android::media::permission {
@@ -38,6 +39,11 @@
     // Fails if the provider does not know about the app-id.
     virtual ::android::error::Result<bool> validateUidPackagePair(
             uid_t uid, const std::string& packageName) const = 0;
+
+    // True iff the uid holds the permission (user aware).
+    // Fails with NO_INIT if cache hasn't been populated.
+    virtual ::android::error::Result<bool> checkPermission(PermissionEnum permission,
+                                                           uid_t uid) const = 0;
     virtual ~IPermissionProvider() = default;
 };
 }  // namespace com::android::media::permission
diff --git a/services/audiopolicy/permission/include/media/NativePermissionController.h b/services/audiopolicy/permission/include/media/NativePermissionController.h
index c0e717c..d464023 100644
--- a/services/audiopolicy/permission/include/media/NativePermissionController.h
+++ b/services/audiopolicy/permission/include/media/NativePermissionController.h
@@ -33,16 +33,22 @@
   public:
     Status populatePackagesForUids(const std::vector<UidPackageState>& initialPackageStates) final;
     Status updatePackagesForUid(const UidPackageState& newPackageState) final;
+    Status populatePermissionState(PermissionEnum permission, const std::vector<int>& uids) final;
     // end binder methods
 
     ::android::error::Result<std::vector<std::string>> getPackagesForUid(uid_t uid) const final;
     ::android::error::Result<bool> validateUidPackagePair(
             uid_t uid, const std::string& packageName) const final;
+    ::android::error::Result<bool> checkPermission(PermissionEnum permission,
+                                                   uid_t uid) const final;
 
   private:
     mutable std::mutex m_;
     // map of app_ids to the set of packages names which could run in them (should be 1)
     std::unordered_map<uid_t, std::vector<std::string>> package_map_ GUARDED_BY(m_);
     bool is_package_populated_ GUARDED_BY(m_);
+    // (logical) map of PermissionEnum to list of uids (not appid) which hold the perm
+    std::array<std::vector<uid_t>, static_cast<size_t>(PermissionEnum::ENUM_SIZE)> permission_map_
+            GUARDED_BY(m_);
 };
 }  // namespace com::android::media::permission
diff --git a/services/audiopolicy/permission/tests/NativePermissionControllerTest.cpp b/services/audiopolicy/permission/tests/NativePermissionControllerTest.cpp
index f23a8b3..3f6b787 100644
--- a/services/audiopolicy/permission/tests/NativePermissionControllerTest.cpp
+++ b/services/audiopolicy/permission/tests/NativePermissionControllerTest.cpp
@@ -24,6 +24,7 @@
 using ::android::base::unexpected;
 using ::android::binder::Status;
 using com::android::media::permission::NativePermissionController;
+using com::android::media::permission::PermissionEnum;
 using com::android::media::permission::UidPackageState;
 
 class NativePermissionControllerTest : public ::testing::Test {
@@ -177,3 +178,41 @@
 
     UNWRAP_EQ(controller_.validateUidPackagePair(12000, "any.package"), false);
 }
+
+TEST_F(NativePermissionControllerTest, populatePermissionState_InvalidPermission) {
+    EXPECT_EQ(controller_.populatePermissionState(PermissionEnum::ENUM_SIZE, {}).exceptionCode(),
+              Status::EX_ILLEGAL_ARGUMENT);
+    EXPECT_EQ(controller_
+                      .populatePermissionState(
+                              static_cast<PermissionEnum>(
+                                      static_cast<int>(PermissionEnum::ENUM_SIZE) + 1),
+                              {})
+                      .exceptionCode(),
+              Status::EX_ILLEGAL_ARGUMENT);
+}
+
+TEST_F(NativePermissionControllerTest, populatePermissionState_HoldsPermission) {
+    // Unsorted
+    std::vector<int> uids{3, 1, 2, 4, 5};
+
+    EXPECT_TRUE(
+            controller_.populatePermissionState(PermissionEnum::MODIFY_AUDIO_ROUTING, uids).isOk());
+
+    EXPECT_TRUE(*controller_.checkPermission(PermissionEnum::MODIFY_AUDIO_ROUTING, 3));
+}
+
+TEST_F(NativePermissionControllerTest, populatePermissionState_DoesNotHoldPermission) {
+    // Unsorted
+    std::vector<int> uids{3, 1, 2, 4, 5};
+
+    EXPECT_TRUE(
+            controller_.populatePermissionState(PermissionEnum::MODIFY_AUDIO_ROUTING, uids).isOk());
+
+    EXPECT_FALSE(*controller_.checkPermission(PermissionEnum::MODIFY_AUDIO_ROUTING, 6));
+}
+
+TEST_F(NativePermissionControllerTest, populatePermissionState_NotInitialized) {
+    const auto res = controller_.checkPermission(PermissionEnum::MODIFY_AUDIO_ROUTING, 3);
+    ASSERT_FALSE(res.has_value());
+    EXPECT_EQ(res.error(), ::android::NO_INIT);
+}
diff --git a/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp b/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp
index f4d6556..efc318b 100644
--- a/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp
+++ b/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp
@@ -27,6 +27,7 @@
 using ::android::content::AttributionSourceState;
 using ::android::error::Result;
 using ::com::android::media::permission::IPermissionProvider;
+using ::com::android::media::permission::PermissionEnum;
 using ::com::android::media::permission::ValidatedAttributionSourceState;
 using ::testing::Return;
 
@@ -36,6 +37,7 @@
                 (override, const));
     MOCK_METHOD(Result<bool>, validateUidPackagePair, (uid_t uid, const std::string&),
                 (override, const));
+    MOCK_METHOD(Result<bool>, checkPermission, (PermissionEnum perm, uid_t), (override, const));
 };
 
 class ValidatedAttributionSourceStateTest : public ::testing::Test {