libdvr: Add performance API to platform library.

- Add dvrPerformanceSetSchedulerPolicy API. Only this API is exposed
  through the DVR platform library, the older API will be deprecated.
- Add permission checks to all performanced APIs.
- Allow services with android.permission.RESTRICTED_VR_ACCESS to change
  scheduler policy for VR apps.
- Minor updates to use the updated PDX service API. The older API will
  be deprecated soon.
- Add tests for permission checks and policy API.

Most of the tests are automatic however, there is one manual step for
testing the android.permission.RESTRICTED_VR_ACCESS (e.g. trusted uid)
check. Because there is no reliable way to determine the UID of VrCore
from the gtest, instead the gtest looks for an env var named
GTEST_TRUSTED_UID; if this is set the value is used as a uid and the
trusted uid tests are enabled.

Bug: 62468109
Test: 'GTEST_TRUSTED_UID=<VrCore UID> performance_service_tests' passes.
Change-Id: I9047b298a015a69535b655a299ca26c179e2d57d
diff --git a/libs/vr/libdvr/Android.bp b/libs/vr/libdvr/Android.bp
index 4041d6b..7fe9825 100644
--- a/libs/vr/libdvr/Android.bp
+++ b/libs/vr/libdvr/Android.bp
@@ -31,6 +31,7 @@
     "dvr_configuration_data.cpp",
     "dvr_display_manager.cpp",
     "dvr_hardware_composer_client.cpp",
+    "dvr_performance.cpp",
     "dvr_surface.cpp",
     "dvr_vsync.cpp",
 ]
@@ -45,6 +46,7 @@
     "libvr_hwc-impl",
     "libvr_hwc-binder",
     "libgrallocusage",
+    "libperformance",
     "libpdx_default_transport",
 ]
 
diff --git a/libs/vr/libdvr/dvr_api.cpp b/libs/vr/libdvr/dvr_api.cpp
index dc31917..7d4e2d1 100644
--- a/libs/vr/libdvr/dvr_api.cpp
+++ b/libs/vr/libdvr/dvr_api.cpp
@@ -10,6 +10,7 @@
 #include <dvr/dvr_buffer_queue.h>
 #include <dvr/dvr_configuration_data.h>
 #include <dvr/dvr_display_manager.h>
+#include <dvr/dvr_performance.h>
 #include <dvr/dvr_surface.h>
 #include <dvr/dvr_vsync.h>
 
diff --git a/libs/vr/libdvr/dvr_performance.cpp b/libs/vr/libdvr/dvr_performance.cpp
new file mode 100644
index 0000000..599101f
--- /dev/null
+++ b/libs/vr/libdvr/dvr_performance.cpp
@@ -0,0 +1,18 @@
+#include "include/dvr/dvr_performance.h"
+
+#include <private/dvr/performance_client.h>
+
+using android::dvr::PerformanceClient;
+
+extern "C" {
+
+int dvrPerformanceSetSchedulerPolicy(pid_t task_id,
+                                     const char* scheduler_policy) {
+  int error;
+  if (auto client = PerformanceClient::Create(&error))
+    return client->SetSchedulerPolicy(task_id, scheduler_policy);
+  else
+    return error;
+}
+
+}  // extern "C"
diff --git a/libs/vr/libdvr/include/dvr/dvr_api.h b/libs/vr/libdvr/include/dvr/dvr_api.h
index 4b530b2..ca41e52 100644
--- a/libs/vr/libdvr/include/dvr/dvr_api.h
+++ b/libs/vr/libdvr/include/dvr/dvr_api.h
@@ -4,6 +4,7 @@
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <unistd.h>
 #include <cstdio>
 
 #include <dvr/dvr_display_types.h>
@@ -320,6 +321,10 @@
                                                            size_t layer_index,
                                                            size_t index);
 
+// dvr_performance.h
+typedef int (*DvrPerformanceSetSchedulerPolicyPtr)(
+    pid_t task_id, const char* scheduler_policy);
+
 // The buffer metadata that an Android Surface (a.k.a. ANativeWindow)
 // will populate. A DvrWriteBufferQueue must be created with this metadata iff
 // ANativeWindow access is needed. Please do not remove, modify, or reorder
diff --git a/libs/vr/libdvr/include/dvr/dvr_api_entries.h b/libs/vr/libdvr/include/dvr/dvr_api_entries.h
index 64e5e71..09bae84 100644
--- a/libs/vr/libdvr/include/dvr/dvr_api_entries.h
+++ b/libs/vr/libdvr/include/dvr/dvr_api_entries.h
@@ -151,3 +151,6 @@
 
 // Read the native display metrics from the hardware composer
 DVR_V1_API_ENTRY(GetNativeDisplayMetrics);
+
+// Performance
+DVR_V1_API_ENTRY(PerformanceSetSchedulerPolicy);
diff --git a/libs/vr/libdvr/include/dvr/dvr_performance.h b/libs/vr/libdvr/include/dvr/dvr_performance.h
new file mode 100644
index 0000000..5df35ad
--- /dev/null
+++ b/libs/vr/libdvr/include/dvr/dvr_performance.h
@@ -0,0 +1,26 @@
+#ifndef ANDROID_DVR_PERFORMANCE_H_
+#define ANDROID_DVR_PERFORMANCE_H_
+
+#include <stddef.h>
+#include <unistd.h>
+
+__BEGIN_DECLS
+
+/// Sets the scheduler policy for a task.
+///
+/// Sets the scheduler policy for a task to the class described by a semantic
+/// string.
+///
+/// Supported policies are device-specific.
+///
+/// @param task_id The task id of task to set the policy for. When task_id is 0
+/// the current task id is substituted.
+/// @param scheduler_policy NULL-terminated ASCII string containing the desired
+/// scheduler policy.
+/// @returns Returns 0 on success or a negative errno error code on error.
+int dvrPerformanceSetSchedulerPolicy(pid_t task_id, const char* scheduler_policy);
+
+__END_DECLS
+
+#endif  // ANDROID_DVR_PERFORMANCE_H_
+
diff --git a/libs/vr/libperformance/include/dvr/performance_client_api.h b/libs/vr/libperformance/include/dvr/performance_client_api.h
index 2216e38..9d617cb 100644
--- a/libs/vr/libperformance/include/dvr/performance_client_api.h
+++ b/libs/vr/libperformance/include/dvr/performance_client_api.h
@@ -8,6 +8,20 @@
 extern "C" {
 #endif
 
+/// Sets the scheduler policy for a task.
+///
+/// Sets the scheduler policy for a task to the class described by a semantic
+/// string.
+///
+/// Supported policies are device-specific.
+///
+/// @param task_id The task id of task to set the policy for. When task_id is 0
+/// the current task id is substituted.
+/// @param scheduler_policy NULL-terminated ASCII string containing the desired
+/// scheduler policy.
+/// @returns Returns 0 on success or a negative errno error code on error.
+int dvrSetSchedulerPolicy(pid_t task_id, const char* scheduler_policy);
+
 /// Sets the CPU partition for a task.
 ///
 /// Sets the CPU partition for a task to the partition described by a CPU
diff --git a/libs/vr/libperformance/include/private/dvr/performance_client.h b/libs/vr/libperformance/include/private/dvr/performance_client.h
index a61c6b2..3bd90dc 100644
--- a/libs/vr/libperformance/include/private/dvr/performance_client.h
+++ b/libs/vr/libperformance/include/private/dvr/performance_client.h
@@ -14,6 +14,10 @@
 
 class PerformanceClient : public pdx::ClientBase<PerformanceClient> {
  public:
+  int SetSchedulerPolicy(pid_t task_id, const std::string& scheduler_policy);
+  int SetSchedulerPolicy(pid_t task_id, const char* scheduler_policy);
+
+  // TODO(eieio): Consider deprecating this API.
   int SetCpuPartition(pid_t task_id, const std::string& partition);
   int SetCpuPartition(pid_t task_id, const char* partition);
   int SetSchedulerClass(pid_t task_id, const std::string& scheduler_class);
diff --git a/libs/vr/libperformance/include/private/dvr/performance_rpc.h b/libs/vr/libperformance/include/private/dvr/performance_rpc.h
index 73bdaa7..d57bbe8 100644
--- a/libs/vr/libperformance/include/private/dvr/performance_rpc.h
+++ b/libs/vr/libperformance/include/private/dvr/performance_rpc.h
@@ -21,14 +21,17 @@
     kOpSetCpuPartition = 0,
     kOpSetSchedulerClass,
     kOpGetCpuPartition,
+    kOpSetSchedulerPolicy,
   };
 
   // Methods.
   PDX_REMOTE_METHOD(SetCpuPartition, kOpSetCpuPartition,
-                    int(pid_t, const std::string&));
+                    void(pid_t, const std::string&));
   PDX_REMOTE_METHOD(SetSchedulerClass, kOpSetSchedulerClass,
-                    int(pid_t, const std::string&));
+                    void(pid_t, const std::string&));
   PDX_REMOTE_METHOD(GetCpuPartition, kOpGetCpuPartition, std::string(pid_t));
+  PDX_REMOTE_METHOD(SetSchedulerPolicy, kOpSetSchedulerPolicy,
+                    void(pid_t, const std::string&));
 };
 
 }  // namespace dvr
diff --git a/libs/vr/libperformance/performance_client.cpp b/libs/vr/libperformance/performance_client.cpp
index 2124162..bf3726e 100644
--- a/libs/vr/libperformance/performance_client.cpp
+++ b/libs/vr/libperformance/performance_client.cpp
@@ -37,6 +37,26 @@
           task_id, WrapString(partition)));
 }
 
+int PerformanceClient::SetSchedulerPolicy(pid_t task_id,
+                                          const std::string& scheduler_policy) {
+  if (task_id == 0)
+    task_id = gettid();
+
+  return ReturnStatusOrError(
+      InvokeRemoteMethod<PerformanceRPC::SetSchedulerPolicy>(task_id,
+                                                             scheduler_policy));
+}
+
+int PerformanceClient::SetSchedulerPolicy(pid_t task_id,
+                                          const char* scheduler_policy) {
+  if (task_id == 0)
+    task_id = gettid();
+
+  return ReturnStatusOrError(
+      InvokeRemoteMethod<PerformanceRPC::SetSchedulerPolicy>(
+          task_id, WrapString(scheduler_policy)));
+}
+
 int PerformanceClient::SetSchedulerClass(pid_t task_id,
                                          const std::string& scheduler_class) {
   if (task_id == 0)
@@ -101,6 +121,15 @@
     return error;
 }
 
+extern "C" int dvrSetSchedulerPolicy(pid_t task_id,
+                                     const char* scheduler_policy) {
+  int error;
+  if (auto client = android::dvr::PerformanceClient::Create(&error))
+    return client->SetSchedulerPolicy(task_id, scheduler_policy);
+  else
+    return error;
+}
+
 extern "C" int dvrSetSchedulerClass(pid_t task_id,
                                     const char* scheduler_class) {
   int error;
diff --git a/services/vr/performanced/Android.mk b/services/vr/performanced/Android.mk
index 6256e90..dbc66f1 100644
--- a/services/vr/performanced/Android.mk
+++ b/services/vr/performanced/Android.mk
@@ -23,8 +23,10 @@
 staticLibraries := \
 	libperformance \
 	libpdx_default_transport \
+	libvr_manager
 
 sharedLibraries := \
+	libbinder \
 	libbase \
 	libcutils \
 	liblog \
diff --git a/services/vr/performanced/cpu_set.cpp b/services/vr/performanced/cpu_set.cpp
index 1a3723f..1a7264c 100644
--- a/services/vr/performanced/cpu_set.cpp
+++ b/services/vr/performanced/cpu_set.cpp
@@ -15,6 +15,9 @@
 #include "task.h"
 #include "unique_file.h"
 
+using android::pdx::ErrorStatus;
+using android::pdx::Status;
+
 namespace {
 
 constexpr int kDirectoryFlags = O_RDONLY | O_DIRECTORY | O_CLOEXEC;
@@ -176,11 +179,11 @@
                task_id, task.name().c_str(), target_set.c_str(),
                task.thread_group_id(), task.parent_process_id());
 
-      const int ret = target->AttachTask(task_id);
-      ALOGW_IF(ret < 0 && ret != -EINVAL,
+      auto status = target->AttachTask(task_id);
+      ALOGW_IF(!status && status.error() != EINVAL,
                "CpuSetManager::MoveUnboundTasks: Failed to attach task_id=%d "
                "to cpuset=%s: %s",
-               task_id, target_set.c_str(), strerror(-ret));
+               task_id, target_set.c_str(), status.GetErrorMessage().c_str());
     } else {
       ALOGD_IF(TRACE,
                "CpuSet::MoveUnboundTasks: Skipping task_id=%d name=%s cpus=%s.",
@@ -233,7 +236,7 @@
   return fp;
 }
 
-int CpuSet::AttachTask(pid_t task_id) const {
+Status<void> CpuSet::AttachTask(pid_t task_id) const {
   auto file = OpenFile("tasks", O_RDWR);
   if (file.get() >= 0) {
     std::ostringstream stream;
@@ -241,11 +244,15 @@
     std::string value = stream.str();
 
     const bool ret = base::WriteStringToFd(value, file.get());
-    return !ret ? -errno : 0;
+    if (!ret)
+      return ErrorStatus(errno);
+    else
+      return {};
   } else {
+    const int error = errno;
     ALOGE("CpuSet::AttachTask: Failed to open %s/tasks: %s", path_.c_str(),
-          strerror(errno));
-    return -errno;
+          strerror(error));
+    return ErrorStatus(error);
   }
 }
 
diff --git a/services/vr/performanced/cpu_set.h b/services/vr/performanced/cpu_set.h
index fcf280a..6879272 100644
--- a/services/vr/performanced/cpu_set.h
+++ b/services/vr/performanced/cpu_set.h
@@ -11,6 +11,8 @@
 
 #include <android-base/unique_fd.h>
 
+#include <pdx/status.h>
+
 #include "unique_file.h"
 
 namespace android {
@@ -28,7 +30,7 @@
 
   std::string GetCpuList() const;
 
-  int AttachTask(pid_t task_id) const;
+  pdx::Status<void> AttachTask(pid_t task_id) const;
   std::vector<pid_t> GetTasks() const;
 
  private:
diff --git a/services/vr/performanced/performance_service.cpp b/services/vr/performanced/performance_service.cpp
index 955c661..3f7009a 100644
--- a/services/vr/performanced/performance_service.cpp
+++ b/services/vr/performanced/performance_service.cpp
@@ -8,14 +8,20 @@
 #include <pdx/rpc/argument_encoder.h>
 #include <pdx/rpc/message_buffer.h>
 #include <pdx/rpc/remote_method.h>
+#include <private/android_filesystem_config.h>
 #include <private/dvr/performance_rpc.h>
+#include <private/dvr/trusted_uids.h>
 
 #include "task.h"
 
 // This prctl is only available in Android kernels.
 #define PR_SET_TIMERSLACK_PID 41
 
+using android::dvr::IsTrustedUid;
+using android::dvr::Task;
+using android::pdx::ErrorStatus;
 using android::pdx::Message;
+using android::pdx::Status;
 using android::pdx::rpc::DispatchRemoteMethod;
 using android::pdx::default_transport::Endpoint;
 
@@ -26,6 +32,68 @@
 constexpr unsigned long kTimerSlackForegroundNs = 50000;
 constexpr unsigned long kTimerSlackBackgroundNs = 40000000;
 
+// Expands the given parameter pack expression using an initializer list to
+// guarantee ordering and a comma expression to guarantee even void expressions
+// are valid elements of the initializer list.
+#define EXPAND_PACK(...) \
+  std::initializer_list<int> { (__VA_ARGS__, 0)... }
+
+// Returns true if the sender's euid matches any of the uids in |UIDs|.
+template <uid_t... UIDs>
+struct UserId {
+  static bool Check(const Message& sender, const Task&) {
+    const uid_t uid = sender.GetEffectiveUserId();
+    bool allow = false;
+    EXPAND_PACK(allow |= (uid == UIDs));
+    return allow;
+  }
+};
+
+// Returns true if the sender's egid matches any of the gids in |GIDs|.
+template <gid_t... GIDs>
+struct GroupId {
+  static bool Check(const Message& sender, const Task&) {
+    const gid_t gid = sender.GetEffectiveGroupId();
+    bool allow = false;
+    EXPAND_PACK(allow |= (gid == GIDs));
+    return allow;
+  }
+};
+
+// Returns true if the sender's euid is trusted according to VR manager service.
+struct Trusted {
+  static bool Check(const Message& sender, const Task&) {
+    return IsTrustedUid(sender.GetEffectiveUserId(), false);
+  }
+};
+
+// Returns returns true if the task belongs to the sending process.
+struct SameProcess {
+  static bool Check(const Message& sender, const Task& task) {
+    return sender.GetProcessId() == task.thread_group_id();
+  }
+};
+
+// Returns true if any of the checks in |Allows| pass, false otherwise.
+template <typename... Allows>
+struct CheckOr {
+  static bool Check(const Message& sender, const Task& task) {
+    bool allow = false;
+    EXPAND_PACK(allow |= Allows::Check(sender, task));
+    return allow;
+  }
+};
+
+// Returns true if all of the checks in |Allows| pass, false otherwise.
+template <typename... Allows>
+struct CheckAnd {
+  static bool Check(const Message& sender, const Task& task) {
+    bool allow = true;
+    EXPAND_PACK(allow &= Allows::Check(sender, task));
+    return allow;
+  }
+};
+
 }  // anonymous namespace
 
 namespace android {
@@ -48,43 +116,79 @@
   const int fifo_low = sched_fifo_min_priority_;
   const int fifo_medium = sched_fifo_min_priority_ + fifo_range / 5;
 
-  // TODO(eieio): Make this configurable on the command line.
+  // TODO(eieio): Make this configurable on the command line or config file.
   cpuset_.MoveUnboundTasks("/kernel");
 
+  // TODO(eieio): Replace this witha device-specific config file. This is just a
+  // hack for now to put some form of permission logic in place while a longer
+  // term solution is developed.
+  using AllowRootSystem =
+      CheckAnd<SameProcess, CheckOr<UserId<AID_ROOT, AID_SYSTEM>,
+                                    GroupId<AID_SYSTEM>>>;
+  using AllowRootSystemGraphics =
+      CheckAnd<SameProcess, CheckOr<UserId<AID_ROOT, AID_SYSTEM, AID_GRAPHICS>,
+                                    GroupId<AID_SYSTEM, AID_GRAPHICS>>>;
+  using AllowRootSystemAudio =
+      CheckAnd<SameProcess, CheckOr<UserId<AID_ROOT, AID_SYSTEM, AID_AUDIO>,
+                                    GroupId<AID_SYSTEM, AID_AUDIO>>>;
+  using AllowRootSystemTrusted = CheckOr<Trusted, UserId<AID_ROOT, AID_SYSTEM>,
+                                        GroupId<AID_SYSTEM>>;
+
+  partition_permission_check_ = AllowRootSystemTrusted::Check;
+
   // Setup the scheduler classes.
+  // TODO(eieio): Replace this with a device-specific config file.
   scheduler_classes_ = {
       {"audio:low",
        {.timer_slack = kTimerSlackForegroundNs,
         .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
-        .priority = fifo_medium}},
+        .priority = fifo_medium,
+        .permission_check = AllowRootSystemAudio::Check}},
       {"audio:high",
        {.timer_slack = kTimerSlackForegroundNs,
         .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
-        .priority = fifo_medium + 3}},
+        .priority = fifo_medium + 3,
+        .permission_check = AllowRootSystemAudio::Check}},
       {"graphics",
        {.timer_slack = kTimerSlackForegroundNs,
         .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
-        .priority = fifo_medium}},
+        .priority = fifo_medium,
+        .permission_check = AllowRootSystemGraphics::Check}},
       {"graphics:low",
        {.timer_slack = kTimerSlackForegroundNs,
         .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
-        .priority = fifo_medium}},
+        .priority = fifo_medium,
+        .permission_check = AllowRootSystemGraphics::Check}},
       {"graphics:high",
        {.timer_slack = kTimerSlackForegroundNs,
         .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
-        .priority = fifo_medium + 2}},
+        .priority = fifo_medium + 2,
+        .permission_check = AllowRootSystemGraphics::Check}},
       {"sensors",
        {.timer_slack = kTimerSlackForegroundNs,
         .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
-        .priority = fifo_low}},
+        .priority = fifo_low,
+        .permission_check = AllowRootSystem::Check}},
       {"sensors:low",
        {.timer_slack = kTimerSlackForegroundNs,
         .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
-        .priority = fifo_low}},
+        .priority = fifo_low,
+        .permission_check = AllowRootSystem::Check}},
       {"sensors:high",
        {.timer_slack = kTimerSlackForegroundNs,
         .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
-        .priority = fifo_low + 1}},
+        .priority = fifo_low + 1,
+        .permission_check = AllowRootSystem::Check}},
+      {"vr:system:arp",
+       {.timer_slack = kTimerSlackForegroundNs,
+        .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+        .priority = fifo_medium + 2,
+        .permission_check = AllowRootSystemTrusted::Check}},
+      {"vr:app:render",
+       {.timer_slack = kTimerSlackForegroundNs,
+        .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK,
+        .priority = fifo_medium + 1,
+        .permission_check = AllowRootSystemTrusted::Check}},
       {"normal",
        {.timer_slack = kTimerSlackForegroundNs,
         .scheduler_policy = SCHED_NORMAL,
@@ -113,67 +217,94 @@
   return cpuset_.DumpState();
 }
 
-int PerformanceService::OnSetCpuPartition(Message& message, pid_t task_id,
-                                          const std::string& partition) {
+Status<void> PerformanceService::OnSetSchedulerPolicy(
+    Message& message, pid_t task_id, const std::string& scheduler_policy) {
+  // Forward to scheduler class handler for now. In the future this method will
+  // subsume the others by unifying both scheduler class and cpu partiton into a
+  // single policy concept.
+  ALOGI(
+      "PerformanceService::OnSetSchedulerPolicy: task_id=%d "
+      "scheduler_policy=%s",
+      task_id, scheduler_policy.c_str());
+  return OnSetSchedulerClass(message, task_id, scheduler_policy);
+}
+
+Status<void> PerformanceService::OnSetCpuPartition(
+    Message& message, pid_t task_id, const std::string& partition) {
   Task task(task_id);
   if (!task || task.thread_group_id() != message.GetProcessId())
-    return -EINVAL;
+    return ErrorStatus(EINVAL);
+
+  // Temporary permission check.
+  // TODO(eieio): Replace this with a configuration file.
+  if (partition_permission_check_ &&
+      !partition_permission_check_(message, task)) {
+    return ErrorStatus(EINVAL);
+  }
 
   auto target_set = cpuset_.Lookup(partition);
   if (!target_set)
-    return -ENOENT;
+    return ErrorStatus(ENOENT);
 
-  const auto attach_error = target_set->AttachTask(task_id);
-  if (attach_error)
-    return attach_error;
+  auto attach_status = target_set->AttachTask(task_id);
+  if (!attach_status)
+    return attach_status;
 
-  return 0;
+  return {};
 }
 
-int PerformanceService::OnSetSchedulerClass(
+Status<void> PerformanceService::OnSetSchedulerClass(
     Message& message, pid_t task_id, const std::string& scheduler_class) {
-  // Make sure the task id is valid and belongs to the sending process.
   Task task(task_id);
-  if (!task || task.thread_group_id() != message.GetProcessId())
-    return -EINVAL;
+  if (!task)
+    return ErrorStatus(EINVAL);
 
-  struct sched_param param;
-
-  // TODO(eieio): Apply rules based on the requesting process. Applications are
-  // only allowed one audio thread that runs at SCHED_FIFO. System services can
-  // have more than one.
   auto search = scheduler_classes_.find(scheduler_class);
   if (search != scheduler_classes_.end()) {
     auto config = search->second;
+
+    // Make sure the sending process is allowed to make the requested change to
+    // this task.
+    if (!config.IsAllowed(message, task))
+      return ErrorStatus(EINVAL);
+
+    struct sched_param param;
     param.sched_priority = config.priority;
+
     sched_setscheduler(task_id, config.scheduler_policy, &param);
     prctl(PR_SET_TIMERSLACK_PID, config.timer_slack, task_id);
     ALOGI("PerformanceService::OnSetSchedulerClass: Set task=%d to class=%s.",
           task_id, scheduler_class.c_str());
-    return 0;
+    return {};
   } else {
     ALOGE(
         "PerformanceService::OnSetSchedulerClass: Invalid class=%s requested "
         "by task=%d.",
         scheduler_class.c_str(), task_id);
-    return -EINVAL;
+    return ErrorStatus(EINVAL);
   }
 }
 
-std::string PerformanceService::OnGetCpuPartition(Message& message,
-                                                  pid_t task_id) {
+Status<std::string> PerformanceService::OnGetCpuPartition(Message& message,
+                                                          pid_t task_id) {
   // Make sure the task id is valid and belongs to the sending process.
   Task task(task_id);
   if (!task || task.thread_group_id() != message.GetProcessId())
-    REPLY_ERROR_RETURN(message, EINVAL, "");
+    return ErrorStatus(EINVAL);
 
   return task.GetCpuSetPath();
 }
 
-pdx::Status<void> PerformanceService::HandleMessage(Message& message) {
+Status<void> PerformanceService::HandleMessage(Message& message) {
+  ALOGD_IF(TRACE, "PerformanceService::HandleMessage: op=%d", message.GetOp());
   switch (message.GetOp()) {
+    case PerformanceRPC::SetSchedulerPolicy::Opcode:
+      DispatchRemoteMethod<PerformanceRPC::SetSchedulerPolicy>(
+          *this, &PerformanceService::OnSetSchedulerPolicy, message);
+      return {};
+
     case PerformanceRPC::SetCpuPartition::Opcode:
-      DispatchRemoteMethod<PerformanceRPC::SetSchedulerClass>(
+      DispatchRemoteMethod<PerformanceRPC::SetCpuPartition>(
           *this, &PerformanceService::OnSetCpuPartition, message);
       return {};
 
diff --git a/services/vr/performanced/performance_service.h b/services/vr/performanced/performance_service.h
index 34abba7..b812535 100644
--- a/services/vr/performanced/performance_service.h
+++ b/services/vr/performanced/performance_service.h
@@ -1,12 +1,14 @@
 #ifndef ANDROID_DVR_PERFORMANCED_PERFORMANCE_SERVICE_H_
 #define ANDROID_DVR_PERFORMANCED_PERFORMANCE_SERVICE_H_
 
+#include <functional>
 #include <string>
 #include <unordered_map>
 
 #include <pdx/service.h>
 
 #include "cpu_set.h"
+#include "task.h"
 
 namespace android {
 namespace dvr {
@@ -27,11 +29,15 @@
 
   PerformanceService();
 
-  int OnSetCpuPartition(pdx::Message& message, pid_t task_id,
-                        const std::string& partition);
-  int OnSetSchedulerClass(pdx::Message& message, pid_t task_id,
-                          const std::string& scheduler_class);
-  std::string OnGetCpuPartition(pdx::Message& message, pid_t task_id);
+  pdx::Status<void> OnSetSchedulerPolicy(pdx::Message& message, pid_t task_id,
+                                         const std::string& scheduler_class);
+
+  pdx::Status<void> OnSetCpuPartition(pdx::Message& message, pid_t task_id,
+                                      const std::string& partition);
+  pdx::Status<void> OnSetSchedulerClass(pdx::Message& message, pid_t task_id,
+                                        const std::string& scheduler_class);
+  pdx::Status<std::string> OnGetCpuPartition(pdx::Message& message,
+                                             pid_t task_id);
 
   CpuSetManager cpuset_;
 
@@ -43,10 +49,24 @@
     unsigned long timer_slack;
     int scheduler_policy;
     int priority;
+    std::function<bool(const pdx::Message& message, const Task& task)>
+        permission_check;
+
+    // Check the permisison of the given task to use this scheduler class. If a
+    // permission check function is not set then all tasks are allowed.
+    bool IsAllowed(const pdx::Message& message, const Task& task) const {
+      if (permission_check)
+        return permission_check(message, task);
+      else
+        return true;
+    }
   };
 
   std::unordered_map<std::string, SchedulerClassConfig> scheduler_classes_;
 
+  std::function<bool(const pdx::Message& message, const Task& task)>
+      partition_permission_check_;
+
   PerformanceService(const PerformanceService&) = delete;
   void operator=(const PerformanceService&) = delete;
 };
diff --git a/services/vr/performanced/performance_service_tests.cpp b/services/vr/performanced/performance_service_tests.cpp
index b526082..7de1f08 100644
--- a/services/vr/performanced/performance_service_tests.cpp
+++ b/services/vr/performanced/performance_service_tests.cpp
@@ -1,12 +1,22 @@
 #include <errno.h>
 #include <sched.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include <condition_variable>
+#include <cstdlib>
 #include <mutex>
 #include <thread>
 
 #include <dvr/performance_client_api.h>
 #include <gtest/gtest.h>
+#include <private/android_filesystem_config.h>
+
+namespace {
+
+const char kTrustedUidEnvironmentVariable[] = "GTEST_TRUSTED_UID";
+
+}  // anonymous namespace
 
 TEST(DISABLED_PerformanceTest, SetCpuPartition) {
   int error;
@@ -86,6 +96,27 @@
   EXPECT_EQ(-EINVAL, error);
 }
 
+// This API mirrors SetSchedulerClass for now. Replace with with a more specific
+// test once the policy API is fully implemented.
+TEST(PerformanceTest, SetSchedulerPolicy) {
+  int error;
+
+  error = dvrSetSchedulerPolicy(0, "background");
+  EXPECT_EQ(0, error);
+  EXPECT_EQ(SCHED_BATCH, sched_getscheduler(0));
+
+  error = dvrSetSchedulerPolicy(0, "audio:low");
+  EXPECT_EQ(0, error);
+  EXPECT_EQ(SCHED_FIFO | SCHED_RESET_ON_FORK, sched_getscheduler(0));
+
+  error = dvrSetSchedulerPolicy(0, "normal");
+  EXPECT_EQ(0, error);
+  EXPECT_EQ(SCHED_NORMAL, sched_getscheduler(0));
+
+  error = dvrSetSchedulerPolicy(0, "foobar");
+  EXPECT_EQ(-EINVAL, error);
+}
+
 TEST(PerformanceTest, SchedulerClassResetOnFork) {
   int error;
 
@@ -135,3 +166,273 @@
   error = dvrGetCpuPartition(0, nullptr, sizeof(partition));
   EXPECT_EQ(-EINVAL, error);
 }
+
+TEST(PerformanceTest, Permissions) {
+  int error;
+
+  const int original_uid = getuid();
+  const int original_gid = getgid();
+  int trusted_uid = -1;
+
+  // See if the environment variable GTEST_TRUSTED_UID is set. If it is enable
+  // testing the ActivityManager trusted uid permission checks using that uid.
+  const char* trusted_uid_env = std::getenv(kTrustedUidEnvironmentVariable);
+  if (trusted_uid_env)
+    trusted_uid = std::atoi(trusted_uid_env);
+
+  ASSERT_EQ(AID_ROOT, original_uid)
+      << "This test must run as root to function correctly!";
+
+  // Switch the uid/gid to an id that should not have permission to access any
+  // privileged actions.
+  ASSERT_EQ(0, setresgid(AID_NOBODY, AID_NOBODY, -1))
+      << "Failed to set gid: " << strerror(errno);
+  ASSERT_EQ(0, setresuid(AID_NOBODY, AID_NOBODY, -1))
+      << "Failed to set uid: " << strerror(errno);
+
+  // Unprivileged policies.
+  error = dvrSetSchedulerPolicy(0, "batch");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "background");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "foreground");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "normal");
+  EXPECT_EQ(0, error);
+
+  // Privileged policies.
+  error = dvrSetSchedulerPolicy(0, "audio:low");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "audio:high");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "graphics");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:low");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:high");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "sensors");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:low");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:high");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "vr:system:arp");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "vr:app:render");
+  EXPECT_EQ(-EINVAL, error);
+
+  // uid=AID_SYSTEM / gid=AID_NOBODY
+  ASSERT_EQ(0, setresuid(original_uid, original_uid, -1))
+      << "Failed to restore uid: " << strerror(errno);
+  ASSERT_EQ(0, setresuid(AID_SYSTEM, AID_SYSTEM, -1))
+      << "Failed to set uid: " << strerror(errno);
+
+  // Unprivileged policies.
+  error = dvrSetSchedulerPolicy(0, "batch");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "background");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "foreground");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "normal");
+  EXPECT_EQ(0, error);
+
+  // Privileged policies.
+  error = dvrSetSchedulerPolicy(0, "audio:low");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "audio:high");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:low");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:high");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "sensors");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:low");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:high");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "vr:system:arp");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "vr:app:render");
+  EXPECT_EQ(0, error);
+
+  // uid=AID_NOBODY / gid=AID_SYSTEM
+  ASSERT_EQ(0, setresuid(original_uid, original_uid, -1))
+      << "Failed to restore uid: " << strerror(errno);
+  ASSERT_EQ(0, setresgid(original_gid, original_gid, -1))
+      << "Failed to restore gid: " << strerror(errno);
+  ASSERT_EQ(0, setresgid(AID_SYSTEM, AID_SYSTEM, -1))
+      << "Failed to set gid: " << strerror(errno);
+  ASSERT_EQ(0, setresuid(AID_SYSTEM, AID_NOBODY, -1))
+      << "Failed to set uid: " << strerror(errno);
+
+  // Unprivileged policies.
+  error = dvrSetSchedulerPolicy(0, "batch");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "background");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "foreground");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "normal");
+  EXPECT_EQ(0, error);
+
+  // Privileged policies.
+  error = dvrSetSchedulerPolicy(0, "audio:low");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "audio:high");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:low");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:high");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "sensors");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:low");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:high");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "vr:system:arp");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "vr:app:render");
+  EXPECT_EQ(0, error);
+
+  // uid=AID_GRAPHICS / gid=AID_NOBODY
+  ASSERT_EQ(0, setresuid(original_uid, original_uid, -1))
+      << "Failed to restore uid: " << strerror(errno);
+  ASSERT_EQ(0, setresgid(original_gid, original_gid, -1))
+      << "Failed to restore gid: " << strerror(errno);
+  ASSERT_EQ(0, setresgid(AID_NOBODY, AID_NOBODY, -1))
+      << "Failed to set gid: " << strerror(errno);
+  ASSERT_EQ(0, setresuid(AID_GRAPHICS, AID_GRAPHICS, -1))
+      << "Failed to set uid: " << strerror(errno);
+
+  // Unprivileged policies.
+  error = dvrSetSchedulerPolicy(0, "batch");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "background");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "foreground");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "normal");
+  EXPECT_EQ(0, error);
+
+  // Privileged policies.
+  error = dvrSetSchedulerPolicy(0, "audio:low");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "audio:high");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "graphics");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:low");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:high");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "sensors");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:low");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:high");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "vr:system:arp");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "vr:app:render");
+  EXPECT_EQ(-EINVAL, error);
+
+  // uid=AID_NOBODY / gid=AID_GRAPHICS
+  ASSERT_EQ(0, setresuid(original_uid, original_uid, -1))
+      << "Failed to restore uid: " << strerror(errno);
+  ASSERT_EQ(0, setresgid(original_gid, original_gid, -1))
+      << "Failed to restore gid: " << strerror(errno);
+  ASSERT_EQ(0, setresgid(AID_GRAPHICS, AID_GRAPHICS, -1))
+      << "Failed to set gid: " << strerror(errno);
+  ASSERT_EQ(0, setresuid(AID_NOBODY, AID_NOBODY, -1))
+      << "Failed to set uid: " << strerror(errno);
+
+  // Unprivileged policies.
+  error = dvrSetSchedulerPolicy(0, "batch");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "background");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "foreground");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "normal");
+  EXPECT_EQ(0, error);
+
+  // Privileged policies.
+  error = dvrSetSchedulerPolicy(0, "audio:low");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "audio:high");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "graphics");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:low");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "graphics:high");
+  EXPECT_EQ(0, error);
+  error = dvrSetSchedulerPolicy(0, "sensors");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:low");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "sensors:high");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "vr:system:arp");
+  EXPECT_EQ(-EINVAL, error);
+  error = dvrSetSchedulerPolicy(0, "vr:app:render");
+  EXPECT_EQ(-EINVAL, error);
+
+  if (trusted_uid != -1) {
+    // uid=<trusted uid> / gid=AID_NOBODY
+    ASSERT_EQ(0, setresuid(original_uid, original_uid, -1))
+        << "Failed to restore uid: " << strerror(errno);
+    ASSERT_EQ(0, setresgid(original_gid, original_gid, -1))
+        << "Failed to restore gid: " << strerror(errno);
+    ASSERT_EQ(0, setresgid(AID_NOBODY, AID_NOBODY, -1))
+        << "Failed to set gid: " << strerror(errno);
+    ASSERT_EQ(0, setresuid(trusted_uid, trusted_uid, -1))
+        << "Failed to set uid: " << strerror(errno);
+
+    // Unprivileged policies.
+    error = dvrSetSchedulerPolicy(0, "batch");
+    EXPECT_EQ(0, error);
+    error = dvrSetSchedulerPolicy(0, "background");
+    EXPECT_EQ(0, error);
+    error = dvrSetSchedulerPolicy(0, "foreground");
+    EXPECT_EQ(0, error);
+    error = dvrSetSchedulerPolicy(0, "normal");
+    EXPECT_EQ(0, error);
+
+    // Privileged policies.
+    error = dvrSetSchedulerPolicy(0, "audio:low");
+    EXPECT_EQ(-EINVAL, error);
+    error = dvrSetSchedulerPolicy(0, "audio:high");
+    EXPECT_EQ(-EINVAL, error);
+    error = dvrSetSchedulerPolicy(0, "graphics");
+    EXPECT_EQ(0, error);
+    error = dvrSetSchedulerPolicy(0, "graphics:low");
+    EXPECT_EQ(0, error);
+    error = dvrSetSchedulerPolicy(0, "graphics:high");
+    EXPECT_EQ(0, error);
+    error = dvrSetSchedulerPolicy(0, "sensors");
+    EXPECT_EQ(-EINVAL, error);
+    error = dvrSetSchedulerPolicy(0, "sensors:low");
+    EXPECT_EQ(-EINVAL, error);
+    error = dvrSetSchedulerPolicy(0, "sensors:high");
+    EXPECT_EQ(-EINVAL, error);
+    error = dvrSetSchedulerPolicy(0, "vr:system:arp");
+    EXPECT_EQ(0, error);
+    error = dvrSetSchedulerPolicy(0, "vr:app:render");
+    EXPECT_EQ(0, error);
+  }
+
+  // Restore original effective uid/gid.
+  ASSERT_EQ(0, setresgid(original_gid, original_gid, -1))
+      << "Failed to restore gid: " << strerror(errno);
+  ASSERT_EQ(0, setresuid(original_uid, original_uid, -1))
+      << "Failed to restore uid: " << strerror(errno);
+}