1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #pragma once 18 19 #include <android-base/thread_annotations.h> 20 #include <com/android/internal/app/BnAppOpsCallback.h> 21 #include <cutils/android_filesystem_config.h> 22 #include <log/log.h> 23 #include <utils/RefBase.h> 24 25 #include <functional> 26 27 #include "media/ValidatedAttributionSourceState.h" 28 29 namespace android::media::permission { 30 31 using ValidatedAttributionSourceState = 32 com::android::media::permission::ValidatedAttributionSourceState; 33 34 struct Ops { 35 int attributedOp = -1; // same as OP_NONE 36 int additionalOp = -1; 37 }; 38 39 /** 40 * This session manages an ongoing data access corresponding with appops. 41 * 42 * This access can be temporarily stopped by appops or the data source. When access is revoked by 43 * AppOps, the registered callback will be called in order to ensure that the data delivery is 44 * halted. When halted by the data source, AppOps will be notified that the access ended. 45 * Note, this session does not ref-count on itself. It should represent a single access, which 46 * necessarily cannot nest. 47 * This class is fully locked since notifications from appops are async. Public interface can be 48 * slow due to binder calls. 49 */ 50 template <typename AppOpsFacade> 51 // Abstract interface that permits minor differences in how appops is called per client usage requires(AppOpsFacade x,const ValidatedAttributionSourceState attr)52 requires requires(AppOpsFacade x, const ValidatedAttributionSourceState attr) { 53 { x.startAccess(attr, Ops{}) } -> std::same_as<bool>; // true if permitted 54 { x.stopAccess(attr, Ops{}) } -> std::same_as<void>; 55 { x.checkAccess(attr, Ops{}) } -> std::same_as<bool>; // true if permitted 56 { 57 x.addChangeCallback(attr, Ops{}, std::function<void(bool)>{}) 58 } -> std::same_as<uintptr_t>; 59 // no more calls after return is required 60 { x.removeChangeCallback(uintptr_t{}) } -> std::same_as<void>; 61 } 62 class AppOpsSession { 63 public: 64 /** 65 * @param attr - AttributionChain which the access is attributed to. 66 * @param ops - The ops required for this delivery 67 * @param opChangedCb - A callback (async) which notifies the data source that the permitted 68 * state due to appops has changed. This is only called if a delivery request is ongoing (i.e. 69 * after a `beginDeliveryRequest` but before a `endDeliveryRequest`, regardless of the return 70 * value of the former). Upon calling the cb, appops has been updated, so the post-condition is 71 * that the data source delivers data iff the parameter is true. If the delivery fails for some 72 * reason, `endDeliveryRequest` should be called shortly, however, there is no re-entrancy into 73 * this class. The client should never change the access request state based on this cb. 74 * @param appOpsFacade - See the requires clause -- an interface which encapsulates the calls to 75 * AppOpsService. 76 */ 77 AppOpsSession(ValidatedAttributionSourceState attr, Ops ops, 78 std::function<void(bool)> opChangedCb, AppOpsFacade appOpsFacade = {}) mAttr(std::move (attr))79 : mAttr(std::move(attr)), 80 mOps(ops), 81 mCb(std::move(opChangedCb)), 82 mAppOps(std::move(appOpsFacade)), 83 mCookie(mAppOps.addChangeCallback(mAttr, ops, 84 [this](bool x) { this->onPermittedChanged(x); })), 85 mDeliveryRequested(false), 86 mDeliveryPermitted(mAppOps.checkAccess(mAttr, ops)) { } 87 ~AppOpsSession()88 ~AppOpsSession() { 89 endDeliveryRequest(); 90 mAppOps.removeChangeCallback(mCookie); 91 } 92 93 /** 94 * Source intends to start delivering data. Updates AppOps if applicable. 95 * @return true if data should be delivered (i.e. AppOps also permits delivery) 96 */ beginDeliveryRequest()97 bool beginDeliveryRequest() { 98 std::lock_guard l{mLock}; 99 if (mDeliveryRequested) { 100 ALOG(LOG_WARN, "AppOpsSession", "Redundant beginDeliveryRequest ignored"); 101 return mDeliveryPermitted; 102 } 103 mDeliveryRequested = true; 104 if (mDeliveryPermitted) { 105 mDeliveryPermitted = mAppOps.startAccess(mAttr, mOps); 106 } 107 return mDeliveryPermitted; 108 } 109 110 /** 111 * Source intends to stop delivering data. Updates AppOps if applicable. 112 */ endDeliveryRequest()113 void endDeliveryRequest() { 114 std::lock_guard l{mLock}; 115 if (!mDeliveryRequested) return; 116 mDeliveryRequested = false; 117 if (mDeliveryPermitted) { 118 mAppOps.stopAccess(mAttr, mOps); 119 } 120 } 121 122 /** 123 * Check if delivery is permitted. 124 */ isDeliveryPermitted()125 bool isDeliveryPermitted() const { 126 std::lock_guard l{mLock}; 127 return mDeliveryPermitted; 128 } 129 130 private: 131 /** 132 * AppOps permitted state has changed. From callback thread. 133 */ onPermittedChanged(bool isPermitted)134 void onPermittedChanged(bool isPermitted) { 135 std::lock_guard l{mLock}; 136 if (mDeliveryPermitted == isPermitted) return; 137 const bool oldIsPermitted = mDeliveryPermitted; 138 mDeliveryPermitted = isPermitted; 139 if (!mDeliveryRequested) return; 140 if (mDeliveryPermitted) { 141 mDeliveryPermitted = mAppOps.startAccess(mAttr, mOps); 142 } else { 143 mAppOps.stopAccess(mAttr, mOps); 144 } 145 if (oldIsPermitted != mDeliveryPermitted) { 146 mCb(mDeliveryPermitted); 147 } 148 } 149 150 mutable std::mutex mLock{}; 151 const ValidatedAttributionSourceState mAttr; 152 const Ops mOps; 153 const std::function<void(bool)> mCb; 154 AppOpsFacade mAppOps GUARDED_BY(mLock); 155 const uintptr_t mCookie; 156 bool mDeliveryRequested GUARDED_BY(mLock); 157 bool mDeliveryPermitted GUARDED_BY(mLock); 158 }; 159 160 class DefaultAppOpsFacade { 161 public: 162 bool startAccess(const ValidatedAttributionSourceState&, Ops); 163 void stopAccess(const ValidatedAttributionSourceState&, Ops); 164 bool checkAccess(const ValidatedAttributionSourceState&, Ops); 165 uintptr_t addChangeCallback(const ValidatedAttributionSourceState&, Ops, 166 std::function<void(bool)> cb); 167 void removeChangeCallback(uintptr_t); 168 169 class OpMonitor : public com::android::internal::app::BnAppOpsCallback { 170 public: OpMonitor(ValidatedAttributionSourceState attr,Ops ops,std::function<void (bool)> cb)171 OpMonitor(ValidatedAttributionSourceState attr, Ops ops, std::function<void(bool)> cb) 172 : mAttr(std::move(attr)), mOps(ops), mCb(std::move(cb)) { } 173 174 binder::Status opChanged(int32_t op, int32_t uid, const String16& packageName, 175 const String16& persistenDeviceId) override; 176 stopListening()177 void stopListening() { 178 std::lock_guard l_{mLock}; 179 mCb = nullptr; 180 } 181 182 private: 183 const ValidatedAttributionSourceState mAttr; 184 const Ops mOps; 185 std::mutex mLock; 186 std::function<void(bool)> mCb GUARDED_BY(mLock); 187 }; 188 189 private: 190 static inline std::mutex sMapLock{}; 191 static inline std::unordered_map<uintptr_t, sp<OpMonitor>> sCbMap{}; 192 }; 193 194 } // namespace android::media::permission 195