Loading media/libaudiopermission/NativePermissionController.cpp +2 −2 Original line number Diff line number Diff line Loading @@ -128,8 +128,8 @@ BinderResult<bool> NativePermissionController::validateUidPackagePair( uid = uid % AID_USER_OFFSET; const auto fixed_package_opt = getFixedPackageName(uid); if (fixed_package_opt.has_value()) { return (uid == AID_ROOT || uid == AID_SYSTEM) ? true : packageName == fixed_package_opt.value(); return (uid == AID_ROOT || uid == AID_SYSTEM) ? true : packageName == fixed_package_opt.value(); } std::lock_guard l{m_}; if (!is_package_populated_) { Loading media/libaudiopermission/include/media/AppOpsSession.h 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * 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 <android-base/thread_annotations.h> #include <log/log.h> #include <media/ValidatedAttributionSourceState.h> #include <utils/RefBase.h> #include <functional> namespace android::media::permission { using ValidatedAttributionSourceState = com::android::media::permission::ValidatedAttributionSourceState; struct Ops { int attributedOp = -1; int additionalOp = -1; }; /** * This session manages an ongoing data access corresponding with appops. * * This access can be temporarily stopped by appops or the data source. When access is revoked by * AppOps, the registered callback will be called in order to ensure that the data delivery is * halted. When halted by the data source, AppOps will be notified that the access ended. * Note, this session does not ref-count on itself. It should represent a single access, which * necessarily cannot nest. * This class is fully locked since notifications from appops are async. Public interface can be * slow due to binder calls. */ template <typename AppOpsFacade> // Abstract interface that permits minor differences in how appops is called per client usage requires requires(AppOpsFacade x, const ValidatedAttributionSourceState attr) { { x.startAccess(attr, Ops{}) } -> std::same_as<bool>; // true if permitted { x.stopAccess(attr, Ops{}) } -> std::same_as<void>; { x.checkAccess(attr, Ops{}) } -> std::same_as<bool>; // true if permitted { x.addChangeCallback(attr, Ops{}, std::function<void(bool)>{}) } -> std::same_as<uintptr_t>; // no more calls after return is required { x.removeChangeCallback(uintptr_t{}) } -> std::same_as<void>; } class AppOpsSession { public: /** * @param attr - AttributionChain which the access is attributed to. * @param ops - The ops required for this delivery * @param opChangedCb - A callback (async) which notifies the data source that the permitted * state due to appops has changed. This is only called if a delivery request is ongoing (i.e. * after a `beginDeliveryRequest` but before a `endDeliveryRequest`, regardless of the return * value of the former). Upon calling the cb, appops has been updated, so the post-condition is * that the data source delivers data iff the parameter is true. If the delivery fails for some * reason, `endDeliveryRequest` should be called shortly, however, there is no re-entrancy into * this class. The client should never change the access request state based on this cb. * @param appOpsFacade - See the requires clause -- an interface which encapsulates the calls to * AppOpsService. */ AppOpsSession(ValidatedAttributionSourceState attr, Ops ops, std::function<void(bool)> opChangedCb, AppOpsFacade appOpsFacade = {}) : mAttr(std::move(attr)), mOps(ops), mCb(std::move(opChangedCb)), mAppOps(std::move(appOpsFacade)), mCookie(mAppOps.addChangeCallback(attr, ops, [this](bool x) { this->onPermittedChanged(x); })), mDeliveryRequested(false), mDeliveryPermitted(mAppOps.checkAccess(attr, ops)) {} ~AppOpsSession() { endDeliveryRequest(); mAppOps.removeChangeCallback(mCookie); } /** * Source intends to start delivering data. Updates AppOps if applicable. * @return true if data should be delivered (i.e. AppOps also permits delivery) */ bool beginDeliveryRequest() { std::lock_guard l{mLock}; if (mDeliveryRequested) { ALOG(LOG_WARN, "AppOpsSession", "Redundant beginDeliveryRequest ignored"); return mDeliveryPermitted; } mDeliveryRequested = true; if (mDeliveryPermitted) { mDeliveryPermitted = mAppOps.startAccess(mAttr, mOps); } return mDeliveryPermitted; } /** * Source intends to stop delivering data. Updates AppOps if applicable. */ void endDeliveryRequest() { std::lock_guard l{mLock}; if (!mDeliveryRequested) return; mDeliveryRequested = false; if (mDeliveryPermitted) { mAppOps.stopAccess(mAttr, mOps); } } /** * Check if delivery is permitted. */ bool isDeliveryPermitted() const { std::lock_guard l{mLock}; return mDeliveryPermitted; } private: /** * AppOps permitted state has changed. From callback thread. */ void onPermittedChanged(bool isPermitted) { std::lock_guard l{mLock}; if (mDeliveryPermitted == isPermitted) return; const bool oldIsPermitted = mDeliveryPermitted; mDeliveryPermitted = isPermitted; if (!mDeliveryRequested) return; if (mDeliveryPermitted) { mDeliveryPermitted = mAppOps.startAccess(mAttr, mOps); } else { mAppOps.stopAccess(mAttr, mOps); } if (oldIsPermitted != mDeliveryPermitted) { mCb(mDeliveryPermitted); } } mutable std::mutex mLock{}; const ValidatedAttributionSourceState mAttr; const Ops mOps; const std::function<void(bool)> mCb; AppOpsFacade mAppOps GUARDED_BY(mLock); const uintptr_t mCookie; bool mDeliveryRequested GUARDED_BY(mLock); bool mDeliveryPermitted GUARDED_BY(mLock); }; } // namespace com::android::media::permission media/libaudiopermission/tests/AppOpsSessionTests.cpp 0 → 100644 +261 −0 Original line number Diff line number Diff line /* * 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/AppOpsSession.h> #include <media/ValidatedAttributionSourceState.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <functional> using ::android::content::AttributionSourceState; using ::android::media::permission::AppOpsSession; using ::android::media::permission::Ops; using ::com::android::media::permission::ValidatedAttributionSourceState; using ::testing::ElementsAreArray; using ::testing::Eq; using ::testing::Ne; using ::testing::IsEmpty; class AppOpsSessionTests; class AppOpsTestFacade { friend AppOpsSessionTests; public: bool startAccess(const ValidatedAttributionSourceState&, Ops) { if (allowed_) ++running_; return allowed_; } void stopAccess(const ValidatedAttributionSourceState&, Ops) { --running_; } bool checkAccess(const ValidatedAttributionSourceState&, Ops) { return allowed_; } uintptr_t addChangeCallback(const ValidatedAttributionSourceState&, Ops, std::function<void(bool)> cb) { cb_ = cb; return 42; } void removeChangeCallback(uintptr_t) {} private: // Static abuse since this is copied into the test, and represents "global" state static inline std::function<void(bool)> cb_; static inline bool allowed_; static inline int running_; }; class AppOpsSessionTests : public ::testing::Test { protected: static constexpr Ops mOps = {100, 101}; // We must manually clear the facade state, since it is static, unlike the members of this // class, since the fixture is constructed per-test. void SetUp() override { AppOpsTestFacade::cb_ = nullptr; AppOpsTestFacade::running_ = 0; AppOpsTestFacade::allowed_ = false; } void facadeSetAllowed(bool isAllowed) { AppOpsTestFacade::allowed_ = isAllowed; } int facadeGetRunning() { return AppOpsTestFacade::running_; } void facadeTriggerChange(bool isPermitted) { EXPECT_THAT(isPermitted, Ne(AppOpsTestFacade::allowed_)); facadeSetAllowed(isPermitted); AppOpsTestFacade::cb_(isPermitted); } // Trigger a change callback, but without modifying the underlying state. // Allows for simulating a callback which is reversed quickly and callbacks which may not // apply to our package. void facadeTriggerSpuriousChange(bool isPermitted) { facadeSetAllowed(isPermitted); } void dataDeliveryCb(bool shouldDeliver) { mDeliveredCbs.push_back(shouldDeliver); } const AttributionSourceState mAttr = []() { AttributionSourceState attr; attr.uid = 1; attr.pid = 2; attr.deviceId = 3; return attr; }(); void initSession() { mAppOpsSession.emplace( ValidatedAttributionSourceState::createFromTrustedSource(mAttr), mOps, [this](bool x) { dataDeliveryCb(x); }, AppOpsTestFacade{}); } // For verification of delivered callbacks // vector<bool> since it's a test std::vector<bool> mDeliveredCbs; std::optional<AppOpsSession<AppOpsTestFacade>> mAppOpsSession; }; TEST_F(AppOpsSessionTests, beginDeliveryRequest_Allowed) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, beginDeliveryRequest_Denied) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, endDeliveryRequest_Ongoing) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); mAppOpsSession->endDeliveryRequest(); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, endDeliveryRequest_Paused) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); mAppOpsSession->endDeliveryRequest(); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, endDeliveryRequest_PausedByCb) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); facadeTriggerChange(false); EXPECT_EQ(facadeGetRunning(), 0); mAppOpsSession->endDeliveryRequest(); EXPECT_EQ(facadeGetRunning(), 0); } TEST_F(AppOpsSessionTests, onPermittedFalse_Ongoing_Change) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); facadeTriggerChange(false); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, ElementsAreArray({false})); } TEST_F(AppOpsSessionTests, onPermittedTrue_Ongoing_Change) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); facadeTriggerChange(true); EXPECT_EQ(facadeGetRunning(), 1); EXPECT_THAT(mDeliveredCbs, ElementsAreArray({true})); } TEST_F(AppOpsSessionTests, onPermittedTrue_Ongoing_Change_Spurious) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); facadeTriggerSpuriousChange(true); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, onPermittedFalse_Ongoing_Same) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); facadeTriggerSpuriousChange(false); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, onPermittedTrue_Ongoing_Same) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); facadeTriggerSpuriousChange(true); EXPECT_EQ(facadeGetRunning(), 1); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, onPermittedFalse_Paused_Change) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); mAppOpsSession->endDeliveryRequest(); EXPECT_EQ(facadeGetRunning(), 0); facadeTriggerChange(false); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, onPermittedTrue_Paused_Change) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); mAppOpsSession->endDeliveryRequest(); facadeTriggerChange(true); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, dtor_Running) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); // call dtor mAppOpsSession.reset(); EXPECT_EQ(facadeGetRunning(), 0); } TEST_F(AppOpsSessionTests, dtor_NotRunning) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); // call dtor mAppOpsSession.reset(); EXPECT_EQ(facadeGetRunning(), 0); } Loading
media/libaudiopermission/NativePermissionController.cpp +2 −2 Original line number Diff line number Diff line Loading @@ -128,8 +128,8 @@ BinderResult<bool> NativePermissionController::validateUidPackagePair( uid = uid % AID_USER_OFFSET; const auto fixed_package_opt = getFixedPackageName(uid); if (fixed_package_opt.has_value()) { return (uid == AID_ROOT || uid == AID_SYSTEM) ? true : packageName == fixed_package_opt.value(); return (uid == AID_ROOT || uid == AID_SYSTEM) ? true : packageName == fixed_package_opt.value(); } std::lock_guard l{m_}; if (!is_package_populated_) { Loading
media/libaudiopermission/include/media/AppOpsSession.h 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * 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 <android-base/thread_annotations.h> #include <log/log.h> #include <media/ValidatedAttributionSourceState.h> #include <utils/RefBase.h> #include <functional> namespace android::media::permission { using ValidatedAttributionSourceState = com::android::media::permission::ValidatedAttributionSourceState; struct Ops { int attributedOp = -1; int additionalOp = -1; }; /** * This session manages an ongoing data access corresponding with appops. * * This access can be temporarily stopped by appops or the data source. When access is revoked by * AppOps, the registered callback will be called in order to ensure that the data delivery is * halted. When halted by the data source, AppOps will be notified that the access ended. * Note, this session does not ref-count on itself. It should represent a single access, which * necessarily cannot nest. * This class is fully locked since notifications from appops are async. Public interface can be * slow due to binder calls. */ template <typename AppOpsFacade> // Abstract interface that permits minor differences in how appops is called per client usage requires requires(AppOpsFacade x, const ValidatedAttributionSourceState attr) { { x.startAccess(attr, Ops{}) } -> std::same_as<bool>; // true if permitted { x.stopAccess(attr, Ops{}) } -> std::same_as<void>; { x.checkAccess(attr, Ops{}) } -> std::same_as<bool>; // true if permitted { x.addChangeCallback(attr, Ops{}, std::function<void(bool)>{}) } -> std::same_as<uintptr_t>; // no more calls after return is required { x.removeChangeCallback(uintptr_t{}) } -> std::same_as<void>; } class AppOpsSession { public: /** * @param attr - AttributionChain which the access is attributed to. * @param ops - The ops required for this delivery * @param opChangedCb - A callback (async) which notifies the data source that the permitted * state due to appops has changed. This is only called if a delivery request is ongoing (i.e. * after a `beginDeliveryRequest` but before a `endDeliveryRequest`, regardless of the return * value of the former). Upon calling the cb, appops has been updated, so the post-condition is * that the data source delivers data iff the parameter is true. If the delivery fails for some * reason, `endDeliveryRequest` should be called shortly, however, there is no re-entrancy into * this class. The client should never change the access request state based on this cb. * @param appOpsFacade - See the requires clause -- an interface which encapsulates the calls to * AppOpsService. */ AppOpsSession(ValidatedAttributionSourceState attr, Ops ops, std::function<void(bool)> opChangedCb, AppOpsFacade appOpsFacade = {}) : mAttr(std::move(attr)), mOps(ops), mCb(std::move(opChangedCb)), mAppOps(std::move(appOpsFacade)), mCookie(mAppOps.addChangeCallback(attr, ops, [this](bool x) { this->onPermittedChanged(x); })), mDeliveryRequested(false), mDeliveryPermitted(mAppOps.checkAccess(attr, ops)) {} ~AppOpsSession() { endDeliveryRequest(); mAppOps.removeChangeCallback(mCookie); } /** * Source intends to start delivering data. Updates AppOps if applicable. * @return true if data should be delivered (i.e. AppOps also permits delivery) */ bool beginDeliveryRequest() { std::lock_guard l{mLock}; if (mDeliveryRequested) { ALOG(LOG_WARN, "AppOpsSession", "Redundant beginDeliveryRequest ignored"); return mDeliveryPermitted; } mDeliveryRequested = true; if (mDeliveryPermitted) { mDeliveryPermitted = mAppOps.startAccess(mAttr, mOps); } return mDeliveryPermitted; } /** * Source intends to stop delivering data. Updates AppOps if applicable. */ void endDeliveryRequest() { std::lock_guard l{mLock}; if (!mDeliveryRequested) return; mDeliveryRequested = false; if (mDeliveryPermitted) { mAppOps.stopAccess(mAttr, mOps); } } /** * Check if delivery is permitted. */ bool isDeliveryPermitted() const { std::lock_guard l{mLock}; return mDeliveryPermitted; } private: /** * AppOps permitted state has changed. From callback thread. */ void onPermittedChanged(bool isPermitted) { std::lock_guard l{mLock}; if (mDeliveryPermitted == isPermitted) return; const bool oldIsPermitted = mDeliveryPermitted; mDeliveryPermitted = isPermitted; if (!mDeliveryRequested) return; if (mDeliveryPermitted) { mDeliveryPermitted = mAppOps.startAccess(mAttr, mOps); } else { mAppOps.stopAccess(mAttr, mOps); } if (oldIsPermitted != mDeliveryPermitted) { mCb(mDeliveryPermitted); } } mutable std::mutex mLock{}; const ValidatedAttributionSourceState mAttr; const Ops mOps; const std::function<void(bool)> mCb; AppOpsFacade mAppOps GUARDED_BY(mLock); const uintptr_t mCookie; bool mDeliveryRequested GUARDED_BY(mLock); bool mDeliveryPermitted GUARDED_BY(mLock); }; } // namespace com::android::media::permission
media/libaudiopermission/tests/AppOpsSessionTests.cpp 0 → 100644 +261 −0 Original line number Diff line number Diff line /* * 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/AppOpsSession.h> #include <media/ValidatedAttributionSourceState.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <functional> using ::android::content::AttributionSourceState; using ::android::media::permission::AppOpsSession; using ::android::media::permission::Ops; using ::com::android::media::permission::ValidatedAttributionSourceState; using ::testing::ElementsAreArray; using ::testing::Eq; using ::testing::Ne; using ::testing::IsEmpty; class AppOpsSessionTests; class AppOpsTestFacade { friend AppOpsSessionTests; public: bool startAccess(const ValidatedAttributionSourceState&, Ops) { if (allowed_) ++running_; return allowed_; } void stopAccess(const ValidatedAttributionSourceState&, Ops) { --running_; } bool checkAccess(const ValidatedAttributionSourceState&, Ops) { return allowed_; } uintptr_t addChangeCallback(const ValidatedAttributionSourceState&, Ops, std::function<void(bool)> cb) { cb_ = cb; return 42; } void removeChangeCallback(uintptr_t) {} private: // Static abuse since this is copied into the test, and represents "global" state static inline std::function<void(bool)> cb_; static inline bool allowed_; static inline int running_; }; class AppOpsSessionTests : public ::testing::Test { protected: static constexpr Ops mOps = {100, 101}; // We must manually clear the facade state, since it is static, unlike the members of this // class, since the fixture is constructed per-test. void SetUp() override { AppOpsTestFacade::cb_ = nullptr; AppOpsTestFacade::running_ = 0; AppOpsTestFacade::allowed_ = false; } void facadeSetAllowed(bool isAllowed) { AppOpsTestFacade::allowed_ = isAllowed; } int facadeGetRunning() { return AppOpsTestFacade::running_; } void facadeTriggerChange(bool isPermitted) { EXPECT_THAT(isPermitted, Ne(AppOpsTestFacade::allowed_)); facadeSetAllowed(isPermitted); AppOpsTestFacade::cb_(isPermitted); } // Trigger a change callback, but without modifying the underlying state. // Allows for simulating a callback which is reversed quickly and callbacks which may not // apply to our package. void facadeTriggerSpuriousChange(bool isPermitted) { facadeSetAllowed(isPermitted); } void dataDeliveryCb(bool shouldDeliver) { mDeliveredCbs.push_back(shouldDeliver); } const AttributionSourceState mAttr = []() { AttributionSourceState attr; attr.uid = 1; attr.pid = 2; attr.deviceId = 3; return attr; }(); void initSession() { mAppOpsSession.emplace( ValidatedAttributionSourceState::createFromTrustedSource(mAttr), mOps, [this](bool x) { dataDeliveryCb(x); }, AppOpsTestFacade{}); } // For verification of delivered callbacks // vector<bool> since it's a test std::vector<bool> mDeliveredCbs; std::optional<AppOpsSession<AppOpsTestFacade>> mAppOpsSession; }; TEST_F(AppOpsSessionTests, beginDeliveryRequest_Allowed) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, beginDeliveryRequest_Denied) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, endDeliveryRequest_Ongoing) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); mAppOpsSession->endDeliveryRequest(); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, endDeliveryRequest_Paused) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); mAppOpsSession->endDeliveryRequest(); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, endDeliveryRequest_PausedByCb) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); facadeTriggerChange(false); EXPECT_EQ(facadeGetRunning(), 0); mAppOpsSession->endDeliveryRequest(); EXPECT_EQ(facadeGetRunning(), 0); } TEST_F(AppOpsSessionTests, onPermittedFalse_Ongoing_Change) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); facadeTriggerChange(false); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, ElementsAreArray({false})); } TEST_F(AppOpsSessionTests, onPermittedTrue_Ongoing_Change) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); facadeTriggerChange(true); EXPECT_EQ(facadeGetRunning(), 1); EXPECT_THAT(mDeliveredCbs, ElementsAreArray({true})); } TEST_F(AppOpsSessionTests, onPermittedTrue_Ongoing_Change_Spurious) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); facadeTriggerSpuriousChange(true); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, onPermittedFalse_Ongoing_Same) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); facadeTriggerSpuriousChange(false); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, onPermittedTrue_Ongoing_Same) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); facadeTriggerSpuriousChange(true); EXPECT_EQ(facadeGetRunning(), 1); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, onPermittedFalse_Paused_Change) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); mAppOpsSession->endDeliveryRequest(); EXPECT_EQ(facadeGetRunning(), 0); facadeTriggerChange(false); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, onPermittedTrue_Paused_Change) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); mAppOpsSession->endDeliveryRequest(); facadeTriggerChange(true); EXPECT_EQ(facadeGetRunning(), 0); EXPECT_THAT(mDeliveredCbs, IsEmpty()); } TEST_F(AppOpsSessionTests, dtor_Running) { facadeSetAllowed(true); initSession(); EXPECT_TRUE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 1); // call dtor mAppOpsSession.reset(); EXPECT_EQ(facadeGetRunning(), 0); } TEST_F(AppOpsSessionTests, dtor_NotRunning) { facadeSetAllowed(false); initSession(); EXPECT_FALSE(mAppOpsSession->beginDeliveryRequest()); EXPECT_EQ(facadeGetRunning(), 0); // call dtor mAppOpsSession.reset(); EXPECT_EQ(facadeGetRunning(), 0); }