Add NativePermissionController for audio perms
Audioserver currently sync calls upwards to system_server to check
permission related information.
Add the implementation of NativePermissionController which receives
package/uid updates from system server, and caches the info to provide
and validate package info for audioserver.
Bug: 338089555
Flag: com.android.media.audio.audioserver_permissions
Test: atest audiopermissioncontroller_test
Change-Id: I44f335e32f38ea474e88b3d9970d62a1e886c543
diff --git a/services/audiopolicy/permission/Android.bp b/services/audiopolicy/permission/Android.bp
new file mode 100644
index 0000000..1aa29e0
--- /dev/null
+++ b/services/audiopolicy/permission/Android.bp
@@ -0,0 +1,102 @@
+package {
+ default_team: "trendy_team_android_media_audio_framework",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+ name: "audiopermissioncontroller",
+
+ srcs: [
+ "NativePermissionController.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+
+ header_libs: [
+ "libcutils_headers",
+ "liberror_headers",
+ ],
+ export_header_lib_headers: [
+ "liberror_headers",
+ ],
+ static_libs: [
+ "audio-permission-aidl-cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libutils",
+ "liblog",
+ ],
+
+ host_supported: true,
+ sanitize: {
+ integer_overflow: true,
+ },
+ cflags: [
+ "-Wall",
+ "-Wdeprecated",
+ "-Wextra",
+ "-Werror=format",
+ "-Wextra-semi",
+ "-Wthread-safety",
+ "-Wconditional-uninitialized",
+ "-Wimplicit-fallthrough",
+ "-Wreorder-init-list",
+ "-Werror=reorder-init-list",
+ "-Wshadow-all",
+ "-Wunreachable-code-aggressive",
+ "-Werror",
+ "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
+ "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
+ ],
+ tidy: true,
+ tidy_checks: [
+ "android-*",
+ "bugprone-*",
+ "cert-*",
+ "clang-analyzer-security*",
+ "google-*",
+ "misc-*",
+ "modernize-*",
+ "performance-*",
+ ],
+ tidy_checks_as_errors: [
+ "android-*",
+ "bugprone-*",
+ "cert-*",
+ "clang-analyzer-security*",
+ "google-*",
+ "misc-*",
+ "modernize-*",
+ "performance-*",
+ ],
+}
+
+cc_test {
+ name: "audiopermissioncontroller_test",
+ host_supported: true,
+ defaults: [
+ "libmediautils_tests_config",
+ ],
+ static_libs: [
+ "audio-permission-aidl-cpp",
+ "audiopermissioncontroller",
+ "framework-permission-aidl-cpp",
+ "libgmock",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "liblog",
+ "libutils",
+ ],
+ srcs: [
+ "tests/NativePermissionControllerTest.cpp",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+ test_suites: ["general-tests"],
+}
diff --git a/services/audiopolicy/permission/NativePermissionController.cpp b/services/audiopolicy/permission/NativePermissionController.cpp
new file mode 100644
index 0000000..d88c69f
--- /dev/null
+++ b/services/audiopolicy/permission/NativePermissionController.cpp
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+#include <media/NativePermissionController.h>
+
+#include <algorithm>
+#include <optional>
+#include <utility>
+
+#include <android-base/expected.h>
+#include <cutils/android_filesystem_config.h>
+#include <utils/Errors.h>
+
+using ::android::base::unexpected;
+using ::android::binder::Status;
+using ::android::error::Result;
+
+namespace com::android::media::permission {
+static std::optional<std::string> getFixedPackageName(uid_t uid) {
+ // These values are in sync with AppOpsService
+ switch (uid % AID_USER_OFFSET) {
+ case AID_ROOT:
+ return "root";
+ case AID_SYSTEM:
+ return "system";
+ case AID_SHELL:
+ return "shell";
+ case AID_MEDIA:
+ return "media";
+ case AID_AUDIOSERVER:
+ return "audioserver";
+ case AID_CAMERASERVER:
+ return "cameraserver";
+ // These packages are not handled by AppOps, but labeling may be useful for us
+ case AID_RADIO:
+ return "telephony";
+ case AID_BLUETOOTH:
+ return "bluetooth";
+ default:
+ return std::nullopt;
+ }
+}
+
+// -- Begin Binder methods
+Status NativePermissionController::populatePackagesForUids(
+ const std::vector<UidPackageState>& initialPackageStates) {
+ std::lock_guard l{m_};
+ if (!is_package_populated_) is_package_populated_ = true;
+ package_map_.clear();
+ std::transform(initialPackageStates.begin(), initialPackageStates.end(),
+ std::inserter(package_map_, package_map_.end()),
+ [](const auto& x) -> std::pair<uid_t, std::vector<std::string>> {
+ return {x.uid, x.packageNames};
+ });
+ std::erase_if(package_map_, [](const auto& x) { return x.second.empty(); });
+ return Status::ok();
+}
+
+Status NativePermissionController::updatePackagesForUid(const UidPackageState& newPackageState) {
+ std::lock_guard l{m_};
+ package_map_.insert_or_assign(newPackageState.uid, newPackageState.packageNames);
+ const auto& cursor = package_map_.find(newPackageState.uid);
+
+ if (newPackageState.packageNames.empty()) {
+ if (cursor != package_map_.end()) {
+ package_map_.erase(cursor);
+ }
+ } else {
+ if (cursor != package_map_.end()) {
+ cursor->second = newPackageState.packageNames;
+ } else {
+ package_map_.insert({newPackageState.uid, newPackageState.packageNames});
+ }
+ }
+ return Status::ok();
+}
+
+// -- End Binder methods
+
+Result<std::vector<std::string>> NativePermissionController::getPackagesForUid(uid_t uid) const {
+ uid = uid % AID_USER_OFFSET;
+ const auto fixed_package_opt = getFixedPackageName(uid);
+ if (fixed_package_opt.has_value()) {
+ return Result<std::vector<std::string>>{std::in_place_t{}, {fixed_package_opt.value()}};
+ }
+ std::lock_guard l{m_};
+ if (!is_package_populated_) return unexpected{::android::NO_INIT};
+ const auto cursor = package_map_.find(uid);
+ if (cursor != package_map_.end()) {
+ return cursor->second;
+ } else {
+ return unexpected{::android::BAD_VALUE};
+ }
+}
+
+Result<bool> NativePermissionController::validateUidPackagePair(
+ uid_t uid, const std::string& packageName) const {
+ uid = uid % AID_USER_OFFSET;
+ const auto fixed_package_opt = getFixedPackageName(uid);
+ if (fixed_package_opt.has_value()) {
+ return packageName == fixed_package_opt.value();
+ }
+ std::lock_guard l{m_};
+ if (!is_package_populated_) return unexpected{::android::NO_INIT};
+ const auto cursor = package_map_.find(uid);
+ return (cursor != package_map_.end()) &&
+ (std::find(cursor->second.begin(), cursor->second.end(), packageName) !=
+ cursor->second.end());
+}
+} // namespace com::android::media::permission
diff --git a/services/audiopolicy/permission/include/media/IPermissionProvider.h b/services/audiopolicy/permission/include/media/IPermissionProvider.h
new file mode 100644
index 0000000..9184ffb
--- /dev/null
+++ b/services/audiopolicy/permission/include/media/IPermissionProvider.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <optional>
+#include <vector>
+
+#include <error/Result.h>
+
+namespace com::android::media::permission {
+
+class IPermissionProvider {
+ public:
+ // Get all package names which run under a certain app-id. Returns non-empty.
+ // Not user specific, since packages are across users. Special app-ids (system,
+ // shell, etc.) are handled. Fails if the provider does not know about the
+ // app-id.
+ virtual ::android::error::Result<std::vector<std::string>> getPackagesForUid(
+ uid_t uid) const = 0;
+ // True iff the provided package name runs under the app-id of uid.
+ // Special app-ids (system, shell, etc.) are handled.
+ // 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;
+ 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
new file mode 100644
index 0000000..c0e717c
--- /dev/null
+++ b/services/audiopolicy/permission/include/media/NativePermissionController.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+
+#include "IPermissionProvider.h"
+
+#include <android-base/thread_annotations.h>
+#include <com/android/media/permission/BnNativePermissionController.h>
+
+namespace com::android::media::permission {
+
+class NativePermissionController : public BnNativePermissionController, public IPermissionProvider {
+ using Status = ::android::binder::Status;
+
+ public:
+ Status populatePackagesForUids(const std::vector<UidPackageState>& initialPackageStates) final;
+ Status updatePackagesForUid(const UidPackageState& newPackageState) 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;
+
+ 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_);
+};
+} // namespace com::android::media::permission
diff --git a/services/audiopolicy/permission/tests/NativePermissionControllerTest.cpp b/services/audiopolicy/permission/tests/NativePermissionControllerTest.cpp
new file mode 100644
index 0000000..f23a8b3
--- /dev/null
+++ b/services/audiopolicy/permission/tests/NativePermissionControllerTest.cpp
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+#include <media/NativePermissionController.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android-base/expected.h>
+
+using ::android::base::unexpected;
+using ::android::binder::Status;
+using com::android::media::permission::NativePermissionController;
+using com::android::media::permission::UidPackageState;
+
+class NativePermissionControllerTest : public ::testing::Test {
+ protected:
+ android::sp<NativePermissionController> holder_ =
+ android::sp<NativePermissionController>::make();
+ NativePermissionController& controller_ = *holder_;
+};
+static UidPackageState createState(uid_t uid, std::vector<std::string> packagesNames) {
+ UidPackageState out{};
+ out.uid = uid;
+ out.packageNames = std::move(packagesNames);
+ return out;
+}
+
+static std::vector<std::string> makeVector(const char* one) {
+ return {one};
+}
+
+static std::vector<std::string> makeVector(const char* one, const char* two) {
+ return {one, two};
+}
+
+#define UNWRAP_EQ(expr, desired_expr) \
+ do { \
+ auto tmp_ = (expr); \
+ EXPECT_TRUE(tmp_.has_value()); \
+ if (tmp_.has_value()) EXPECT_EQ(*tmp_, desired_expr); \
+ } while (0)
+
+// --- Tests for non-populated ----
+TEST_F(NativePermissionControllerTest, getPackagesForUid_NotPopulated) {
+ // Verify errors are returned
+ EXPECT_EQ(controller_.getPackagesForUid(10000), unexpected{android::NO_INIT});
+ EXPECT_EQ(controller_.getPackagesForUid(10001), unexpected{android::NO_INIT});
+
+ // fixed uids should work
+ UNWRAP_EQ(controller_.getPackagesForUid(1000), makeVector("system"));
+}
+
+TEST_F(NativePermissionControllerTest, validateUidPackagePair_NotPopulated) {
+ // Verify errors are returned
+ EXPECT_EQ(controller_.validateUidPackagePair(10000, "com.package"),
+ unexpected{android::NO_INIT});
+
+ // fixed uids should work
+ UNWRAP_EQ(controller_.validateUidPackagePair(1000, "system"), true);
+}
+// --- Tests for populatePackagesForUids ----
+TEST_F(NativePermissionControllerTest, populatePackages_EmptyInput) {
+ std::vector<UidPackageState> input;
+
+ // succeeds
+ EXPECT_TRUE(controller_.populatePackagesForUids(input).isOk());
+
+ // Verify unknown uid behavior
+ const auto res1 = controller_.getPackagesForUid(10000);
+ ASSERT_FALSE(res1.has_value());
+ EXPECT_EQ(res1.error(), ::android::BAD_VALUE);
+}
+
+TEST_F(NativePermissionControllerTest, populatePackages_ValidInput) {
+ std::vector<UidPackageState> input{
+ createState(10000, {"com.example.app1", "com.example.app2"}),
+ createState(10001, {"com.example2.app1"}),
+ };
+
+ EXPECT_TRUE(controller_.populatePackagesForUids(input).isOk());
+
+ UNWRAP_EQ(controller_.getPackagesForUid(10000),
+ makeVector("com.example.app1", "com.example.app2"));
+ UNWRAP_EQ(controller_.getPackagesForUid(10001), makeVector("com.example2.app1"));
+}
+
+// --- Tests for updatePackagesForUid ---
+TEST_F(NativePermissionControllerTest, updatePackages_NewUid) {
+ std::vector<UidPackageState> input{
+ createState(10000, {"com.example.app1", "com.example.app2"}),
+ createState(10001, {"com.example2.app1"}),
+ };
+ UidPackageState newState = createState(12000, {"com.example.other"});
+
+ EXPECT_TRUE(controller_.populatePackagesForUids(input).isOk());
+ EXPECT_TRUE(controller_.updatePackagesForUid(newState).isOk());
+
+ // Verify the results: only the updated package should be changed
+ UNWRAP_EQ(controller_.getPackagesForUid(10000),
+ makeVector("com.example.app1", "com.example.app2"));
+ UNWRAP_EQ(controller_.getPackagesForUid(10001), makeVector("com.example2.app1"));
+ UNWRAP_EQ(controller_.getPackagesForUid(12000), makeVector("com.example.other"));
+}
+
+TEST_F(NativePermissionControllerTest, updatePackages_ExistingUid) {
+ std::vector<UidPackageState> input{
+ createState(10000, {"com.example.app1", "com.example.app2", "com.example.app3"}),
+ createState(10001, {"com.example2.app1"}),
+ };
+
+ EXPECT_TRUE(controller_.populatePackagesForUids(input).isOk());
+ // Update packages for existing uid
+ UidPackageState newState = createState(10000, {"com.example.other", "com.example.new"});
+ EXPECT_TRUE(controller_.updatePackagesForUid(newState).isOk());
+
+ // Verify update
+ UNWRAP_EQ(controller_.getPackagesForUid(10000),
+ makeVector("com.example.other", "com.example.new"));
+}
+
+TEST_F(NativePermissionControllerTest, updatePackages_EmptyRemovesEntry) {
+ std::vector<UidPackageState> input{
+ createState(10000, {"com.example.app1"}),
+ };
+
+ EXPECT_TRUE(controller_.populatePackagesForUids(input).isOk());
+
+ UidPackageState newState{}; // Empty package list
+ newState.uid = 10000;
+ EXPECT_TRUE(controller_.updatePackagesForUid(newState).isOk());
+ // getPackages for unknown UID should error out
+ const auto res = controller_.getPackagesForUid(10000);
+ ASSERT_FALSE(res.has_value());
+ EXPECT_EQ(res.error(), ::android::BAD_VALUE);
+}
+
+TEST_F(NativePermissionControllerTest, validateUidPackagePair_ValidPair) {
+ std::vector<UidPackageState> input{
+ createState(10000, {"com.example.app1", "com.example.app2"}),
+ };
+
+ EXPECT_TRUE(controller_.populatePackagesForUids(input).isOk());
+
+ UNWRAP_EQ(controller_.validateUidPackagePair(10000, "com.example.app1"), true);
+}
+
+TEST_F(NativePermissionControllerTest, validateUidPackagePair_InvalidPackage) {
+ std::vector<UidPackageState> input{
+ createState(10000, {"com.example.app1", "com.example.app2"}),
+ };
+
+ EXPECT_TRUE(controller_.populatePackagesForUids(input).isOk());
+
+ UNWRAP_EQ(controller_.validateUidPackagePair(10000, "com.example.other"), false);
+}
+
+TEST_F(NativePermissionControllerTest, validateUidPackagePair_UnknownUid) {
+ std::vector<UidPackageState> input{
+ createState(10000, {"com.example.app1", "com.example.app2"}),
+ };
+
+ EXPECT_TRUE(controller_.populatePackagesForUids(input).isOk());
+
+ UNWRAP_EQ(controller_.validateUidPackagePair(12000, "any.package"), false);
+}