* Copyright (c) 2026 Huawei Device Co., Ltd.
* 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 <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <deque>
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <set>
#include <string>
#include <thread>
#include <tuple>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "selection_log.h"
#include "selection_errors.h"
namespace OHOS {
namespace SelectionFwk {
enum class SelectionEventPriority : int32_t {
LOWEST = 0,
LOW = 25,
NORMAL = 50,
HIGH = 75,
HIGHEST = 100,
};
struct SelectionSubscriptionHandle {
uint64_t id = 0;
std::type_index eventType;
SelectionSubscriptionHandle() : eventType(typeid(void)) {}
SelectionSubscriptionHandle(uint64_t idVal, std::type_index type) : id(idVal), eventType(type) {}
bool IsValid() const { return id != 0; }
};
class ISelectionEventHandler {
public:
virtual ~ISelectionEventHandler() = default;
virtual std::type_index GetEventType() const = 0;
virtual void Invoke(void* event) = 0;
virtual SelectionEventPriority GetPriority() const = 0;
virtual uint64_t GetId() const = 0;
};
template<typename EventT>
class SelectionEventHandler final : public ISelectionEventHandler {
public:
using Callback = std::function<void(const EventT&)>;
SelectionEventHandler(uint64_t id, Callback cb, SelectionEventPriority pri)
: id_(id), callback_(std::move(cb)), priority_(pri) {}
std::type_index GetEventType() const override { return std::type_index(typeid(EventT)); }
void Invoke(void* event) override
{
if (event != nullptr && callback_) {
callback_(*static_cast<EventT*>(event));
}
}
SelectionEventPriority GetPriority() const override { return priority_; }
uint64_t GetId() const override { return id_; }
private:
uint64_t id_;
Callback callback_;
SelectionEventPriority priority_;
};
class SelectionEventBus {
public:
static SelectionEventBus& GetInstance()
{
static SelectionEventBus instance;
return instance;
}
template<typename EventT>
SelectionSubscriptionHandle Subscribe(
typename SelectionEventHandler<EventT>::Callback callback,
SelectionEventPriority priority = SelectionEventPriority::NORMAL)
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
uint64_t id = ++nextHandleId_;
auto handler = std::make_shared<SelectionEventHandler<EventT>>(id, std::move(callback), priority);
std::type_index typeIdx(typeid(EventT));
handlers_[typeIdx].push_back(std::move(handler));
SortHandlers(typeIdx);
return SelectionSubscriptionHandle(id, typeIdx);
}
template<typename EventT>
bool Unsubscribe(const SelectionSubscriptionHandle& handle)
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
std::type_index typeIdx(typeid(EventT));
auto it = handlers_.find(typeIdx);
if (it == handlers_.end()) {
return false;
}
auto& list = it->second;
auto hIt = std::find_if(list.begin(), list.end(),
[&handle](const auto& h) { return h->GetId() == handle.id; });
if (hIt == list.end()) {
return false;
}
list.erase(hIt);
if (list.empty()) {
handlers_.erase(it);
}
return true;
}
template<typename EventT>
void Publish(const EventT& event)
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
auto it = handlers_.find(std::type_index(typeid(EventT)));
if (it == handlers_.end()) {
return;
}
for (const auto& handler : it->second) {
handler->Invoke(const_cast<void*>(static_cast<const void*>(&event)));
}
}
void ClearAll()
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
handlers_.clear();
}
private:
SelectionEventBus() = default;
SelectionEventBus(const SelectionEventBus&) = delete;
SelectionEventBus& operator=(const SelectionEventBus&) = delete;
void SortHandlers(std::type_index typeIdx)
{
auto it = handlers_.find(typeIdx);
if (it == handlers_.end()) {
return;
}
auto& list = it->second;
std::stable_sort(list.begin(), list.end(),
[](const auto& a, const auto& b) {
return static_cast<int32_t>(a->GetPriority()) > static_cast<int32_t>(b->GetPriority());
});
}
mutable std::recursive_mutex mutex_;
std::unordered_map<std::type_index, std::vector<std::shared_ptr<ISelectionEventHandler>>> handlers_;
std::atomic<uint64_t> nextHandleId_ {0};
};
template<typename T>
class SelectionObjectPool {
public:
using Factory = std::function<std::shared_ptr<T>()>;
using Resetter = std::function<void(std::shared_ptr<T>&)>;
explicit SelectionObjectPool(Factory factory, Resetter resetter = nullptr,
size_t maxIdleSize = 16, size_t preAllocSize = 0)
: factory_(std::move(factory)), resetter_(std::move(resetter)), maxIdleSize_(maxIdleSize)
{
for (size_t i = 0; i < preAllocSize; ++i) {
auto obj = factory_();
if (obj) {
idlePool_.push_back(std::move(obj));
}
}
}
~SelectionObjectPool()
{
std::lock_guard<std::mutex> lock(mutex_);
idlePool_.clear();
activeObjs_.clear();
}
std::shared_ptr<T> Acquire()
{
std::lock_guard<std::mutex> lock(mutex_);
std::shared_ptr<T> obj;
if (!idlePool_.empty()) {
obj = idlePool_.back();
idlePool_.pop_back();
} else {
obj = factory_();
if (!obj) {
return nullptr;
}
}
activeObjs_.insert(obj.get());
return std::shared_ptr<T>(obj.get(), [this, obj](T*) { Release(obj); });
}
size_t IdleCount() const
{
std::lock_guard<std::mutex> lock(mutex_);
return idlePool_.size();
}
size_t ActiveCount() const
{
std::lock_guard<std::mutex> lock(mutex_);
return activeObjs_.size();
}
void ShrinkIdlePool(size_t targetSize = 0)
{
std::lock_guard<std::mutex> lock(mutex_);
while (idlePool_.size() > targetSize) {
idlePool_.pop_back();
}
}
private:
void Release(std::shared_ptr<T> obj)
{
std::lock_guard<std::mutex> lock(mutex_);
activeObjs_.erase(obj.get());
if (resetter_) {
resetter_(obj);
}
if (idlePool_.size() < maxIdleSize_) {
idlePool_.push_back(std::move(obj));
}
}
Factory factory_;
Resetter resetter_;
size_t maxIdleSize_;
std::vector<std::shared_ptr<T>> idlePool_;
std::unordered_set<T*> activeObjs_;
mutable std::mutex mutex_;
};
enum class SelectionRateLimitStrategy { SLIDING_WINDOW, TOKEN_BUCKET };
class SelectionRateLimiter {
public:
static std::unique_ptr<SelectionRateLimiter> Create(
SelectionRateLimitStrategy strategy, uint32_t maxRequests, uint32_t windowMs);
virtual ~SelectionRateLimiter() = default;
virtual bool Allow() = 0;
virtual void Reset() = 0;
virtual uint32_t GetRemainingQuota() const = 0;
};
class SelectionSlidingWindowLimiter : public SelectionRateLimiter {
public:
SelectionSlidingWindowLimiter(uint32_t maxRequests, uint32_t windowMs)
: maxRequests_(maxRequests), windowMs_(windowMs) {}
bool Allow() override
{
std::lock_guard<std::mutex> lock(mutex_);
auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
int64_t windowStart = nowMs - static_cast<int64_t>(windowMs_);
while (!timestamps_.empty() && timestamps_.front() <= windowStart) {
timestamps_.pop_front();
}
if (timestamps_.size() < maxRequests_) {
timestamps_.push_back(nowMs);
return true;
}
return false;
}
void Reset() override
{
std::lock_guard<std::mutex> lock(mutex_);
timestamps_.clear();
}
uint32_t GetRemainingQuota() const override
{
std::lock_guard<std::mutex> lock(mutex_);
auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
size_t active = 0;
for (auto ts : timestamps_) {
if (ts > nowMs - static_cast<int64_t>(windowMs_)) {
active++;
}
}
return static_cast<uint32_t>(maxRequests_ > active ? maxRequests_ - active : 0);
}
private:
uint32_t maxRequests_;
uint32_t windowMs_;
std::deque<int64_t> timestamps_;
mutable std::mutex mutex_;
};
class SelectionTokenBucketLimiter : public SelectionRateLimiter {
public:
SelectionTokenBucketLimiter(uint32_t maxTokens, uint32_t refillMs)
: maxTokens_(maxTokens), refillMs_(refillMs), tokens_(static_cast<double>(maxTokens)),
lastRefillTime_(std::chrono::steady_clock::now()) {}
bool Allow() override
{
std::lock_guard<std::mutex> lock(mutex_);
Refill();
static constexpr double singleTokenCost = 1.0;
if (tokens_ >= singleTokenCost) {
tokens_ -= singleTokenCost;
return true;
}
return false;
}
void Reset() override
{
std::lock_guard<std::mutex> lock(mutex_);
tokens_ = static_cast<double>(maxTokens_);
lastRefillTime_ = std::chrono::steady_clock::now();
}
uint32_t GetRemainingQuota() const override
{
std::lock_guard<std::mutex> lock(mutex_);
return static_cast<uint32_t>(tokens_);
}
private:
void Refill()
{
auto now = std::chrono::steady_clock::now();
auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastRefillTime_).count();
tokens_ = std::min(static_cast<double>(maxTokens_),
tokens_ + static_cast<double>(elapsedMs) / static_cast<double>(refillMs_));
lastRefillTime_ = now;
}
uint32_t maxTokens_;
uint32_t refillMs_;
double tokens_;
std::chrono::steady_clock::time_point lastRefillTime_;
mutable std::mutex mutex_;
};
std::unique_ptr<SelectionRateLimiter> SelectionRateLimiter::Create(
SelectionRateLimitStrategy strategy, uint32_t maxRequests, uint32_t windowMs)
{
switch (strategy) {
case SelectionRateLimitStrategy::SLIDING_WINDOW:
return std::make_unique<SelectionSlidingWindowLimiter>(maxRequests, windowMs);
case SelectionRateLimitStrategy::TOKEN_BUCKET:
return std::make_unique<SelectionTokenBucketLimiter>(maxRequests, windowMs);
default:
return nullptr;
}
}
template<typename Key, typename Value>
class SelectionLruCache {
public:
static constexpr size_t defaultCacheCapacity_ = 64;
explicit SelectionLruCache(size_t capacity) : capacity_(capacity == 0 ? defaultCacheCapacity_ : capacity) {}
void Put(const Key& key, const Value& value)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = indexMap_.find(key);
if (it != indexMap_.end()) {
it->second->second = value;
items_.splice(items_.begin(), items_, it->second);
return;
}
if (items_.size() >= capacity_) {
indexMap_.erase(items_.back().first);
items_.pop_back();
evictCount_++;
}
items_.push_front({key, value});
indexMap_[key] = items_.begin();
}
std::optional<Value> Get(const Key& key)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = indexMap_.find(key);
if (it == indexMap_.end()) {
missCount_++;
return std::nullopt;
}
items_.splice(items_.begin(), items_, it->second);
hitCount_++;
return it->second->second;
}
bool Erase(const Key& key)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = indexMap_.find(key);
if (it == indexMap_.end()) {
return false;
}
items_.erase(it->second);
indexMap_.erase(it);
return true;
}
void Clear()
{
std::lock_guard<std::mutex> lock(mutex_);
items_.clear();
indexMap_.clear();
}
size_t Size() const
{
std::lock_guard<std::mutex> lock(mutex_);
return items_.size();
}
double HitRate() const
{
std::lock_guard<std::mutex> lock(mutex_);
uint64_t total = hitCount_ + missCount_;
return total == 0 ? 0.0 : static_cast<double>(hitCount_) / static_cast<double>(total);
}
private:
size_t capacity_;
std::list<std::pair<Key, Value>> items_;
std::unordered_map<Key, typename std::list<std::pair<Key, Value>>::iterator> indexMap_;
mutable std::mutex mutex_;
mutable uint64_t hitCount_ = 0;
mutable uint64_t missCount_ = 0;
uint64_t evictCount_ = 0;
};
template<typename State, typename Event>
class SelectionStateMachine {
public:
using Guard = std::function<bool()>;
using Action = std::function<void()>;
struct Transition { State from; Event trigger; State to; Guard guard; Action action; };
explicit SelectionStateMachine(State initial) : currentState_(initial) {}
void AddTransition(State from, Event trigger, State to,
Guard guard = nullptr, Action action = nullptr)
{
transitions_.push_back({from, trigger, to, guard, action});
}
std::optional<State> HandleEvent(Event event)
{
std::lock_guard<std::mutex> lock(mutex_);
for (const auto& t : transitions_) {
if (t.from == currentState_ && t.trigger == event) {
if (t.guard && !t.guard()) {
continue;
}
State prev = currentState_;
currentState_ = t.to;
if (t.action) {
t.action();
}
history_.push_back({prev, event, currentState_});
if (history_.size() > maxHistorySize_) {
history_.pop_front();
}
return currentState_;
}
}
return std::nullopt;
}
State GetCurrentState() const
{
std::lock_guard<std::mutex> lock(mutex_);
return currentState_;
}
void ForceState(State state)
{
std::lock_guard<std::mutex> lock(mutex_);
currentState_ = state;
}
std::vector<std::tuple<State, Event, State>> GetHistory() const
{
std::lock_guard<std::mutex> lock(mutex_);
return {history_.begin(), history_.end()};
}
private:
State currentState_;
std::vector<Transition> transitions_;
std::deque<std::tuple<State, Event, State>> history_;
static constexpr size_t defaultMaxHistorySize_ = 128;
size_t maxHistorySize_ = defaultMaxHistorySize_;
mutable std::mutex mutex_;
};
enum class SelectionCircuitState { CLOSED, OPEN, HALF_OPEN };
class SelectionCircuitBreaker {
public:
struct Config {
static constexpr uint32_t defaultFailureThreshold = 5;
static constexpr uint32_t defaultResetTimeoutMs = 30000;
static constexpr uint32_t defaultHalfOpenMaxRequests = 1;
static constexpr uint32_t defaultSuccessThreshold = 3;
uint32_t failureThreshold = defaultFailureThreshold;
uint32_t resetTimeoutMs = defaultResetTimeoutMs;
uint32_t halfOpenMaxRequests = defaultHalfOpenMaxRequests;
uint32_t successThreshold = defaultSuccessThreshold;
};
explicit SelectionCircuitBreaker(const Config& cfg) : config_(cfg) {}
bool AllowRequest()
{
std::lock_guard<std::mutex> lock(mutex_);
switch (state_) {
case SelectionCircuitState::CLOSED:
return true;
case SelectionCircuitState::OPEN:
if (HasResetTimeoutExpired()) {
state_ = SelectionCircuitState::HALF_OPEN;
halfOpenSuccessCount_ = 0;
halfOpenRequestCount_ = 0;
return true;
}
return false;
case SelectionCircuitState::HALF_OPEN:
if (halfOpenRequestCount_ < config_.halfOpenMaxRequests) {
halfOpenRequestCount_++;
return true;
}
return false;
default:
return false;
}
}
void RecordSuccess()
{
std::lock_guard<std::mutex> lock(mutex_);
consecutiveFailures_ = 0;
if (state_ == SelectionCircuitState::HALF_OPEN) {
halfOpenSuccessCount_++;
if (halfOpenSuccessCount_ >= config_.successThreshold) {
state_ = SelectionCircuitState::CLOSED;
failureCount_ = 0;
}
}
}
void RecordFailure()
{
std::lock_guard<std::mutex> lock(mutex_);
consecutiveFailures_++;
failureCount_++;
if (state_ == SelectionCircuitState::HALF_OPEN) {
TripToOpen();
} else if (state_ == SelectionCircuitState::CLOSED &&
consecutiveFailures_ >= config_.failureThreshold) {
TripToOpen();
}
}
SelectionCircuitState GetState() const
{
std::lock_guard<std::mutex> lock(mutex_);
return state_;
}
void Reset()
{
std::lock_guard<std::mutex> lock(mutex_);
state_ = SelectionCircuitState::CLOSED;
failureCount_ = 0;
consecutiveFailures_ = 0;
}
private:
void TripToOpen()
{
state_ = SelectionCircuitState::OPEN;
openedAt_ = std::chrono::steady_clock::now();
}
bool HasResetTimeoutExpired() const
{
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - openedAt_).count();
return static_cast<uint32_t>(elapsed) >= config_.resetTimeoutMs;
}
Config config_;
SelectionCircuitState state_ = SelectionCircuitState::CLOSED;
uint32_t failureCount_ = 0;
uint32_t consecutiveFailures_ = 0;
uint32_t halfOpenSuccessCount_ = 0;
uint32_t halfOpenRequestCount_ = 0;
std::chrono::steady_clock::time_point openedAt_;
mutable std::mutex mutex_;
};
class SelectionRetryPolicy {
public:
struct Config {
static constexpr uint32_t defaultMaxRetries = 3;
static constexpr uint32_t defaultInitialDelayMs = 100;
static constexpr uint32_t defaultMaxDelayMs = 5000;
static constexpr double defaultBackoffMultiplier = 2.0;
uint32_t maxRetries = defaultMaxRetries;
uint32_t initialDelayMs = defaultInitialDelayMs;
uint32_t maxDelayMs = defaultMaxDelayMs;
double backoffMultiplier = defaultBackoffMultiplier;
};
explicit SelectionRetryPolicy(const Config& cfg) : config_(cfg) {}
uint32_t GetDelayForAttempt(uint32_t attempt) const
{
if (attempt == 0) {
return 0;
}
double delay = static_cast<double>(config_.initialDelayMs);
for (uint32_t i = 1; i < attempt; ++i) {
delay *= config_.backoffMultiplier;
}
return static_cast<uint32_t>(std::min(delay, static_cast<double>(config_.maxDelayMs)));
}
bool ShouldRetry(uint32_t attempt) const
{
return attempt < config_.maxRetries;
}
private:
Config config_;
};
class SelectionContentTrimmer {
public:
static constexpr uint32_t maxSelectionBytes_ = 6000;
static std::string TrimToLimit(const std::string& content, uint32_t maxBytes = maxSelectionBytes_)
{
if (content.size() <= maxBytes) {
return content;
}
return content.substr(0, FindSafeUtf8Boundary(content, maxBytes));
}
static bool IsWithinLimit(const std::string& content, uint32_t maxBytes = maxSelectionBytes_)
{
return content.size() <= maxBytes;
}
static uint32_t FindSafeUtf8Boundary(const std::string& content, uint32_t maxBytes)
{
if (content.empty() || maxBytes == 0) {
return 0;
}
uint32_t len = static_cast<uint32_t>(content.size());
if (maxBytes >= len) {
return len;
}
uint32_t pos = maxBytes;
while (pos > 0 && IsUtf8ContinuationByte(static_cast<uint8_t>(content[pos]))) {
pos--;
}
if (pos == 0) {
return 0;
}
uint32_t expectedLen = Utf8CharExpectedLen(static_cast<uint8_t>(content[pos]));
return (pos + expectedLen > maxBytes) ? pos : maxBytes;
}
static constexpr uint8_t printableCharMin_ = 0x20;
static std::string SanitizeContent(const std::string& content)
{
std::string result;
result.reserve(content.size());
for (unsigned char c : content) {
if (c >= printableCharMin_ || c == '\t' || c == '\n' || c == '\r') {
result.push_back(static_cast<char>(c));
}
}
return result;
}
private:
static constexpr uint32_t utf8OneByteMask_ = 0x80;
static constexpr uint32_t utf8OneByteVal_ = 0x00;
static constexpr uint32_t utf8TwoByteMask_ = 0xE0;
static constexpr uint32_t utf8TwoByteVal_ = 0xC0;
static constexpr uint32_t utf8ThreeByteMask_ = 0xF0;
static constexpr uint32_t utf8ThreeByteVal_ = 0xE0;
static constexpr uint32_t utf8FourByteMask_ = 0xF8;
static constexpr uint32_t utf8FourByteVal_ = 0xF0;
static constexpr uint32_t utf8OneByteLen_ = 1;
static constexpr uint32_t utf8TwoByteLen_ = 2;
static constexpr uint32_t utf8ThreeByteLen_ = 3;
static constexpr uint32_t utf8FourByteLen_ = 4;
static constexpr uint8_t utf8ContinuationMask_ = 0xC0;
static constexpr uint8_t utf8ContinuationVal_ = 0x80;
static bool IsUtf8ContinuationByte(uint8_t byte)
{
return (byte & utf8ContinuationMask_) == utf8ContinuationVal_;
}
static uint32_t Utf8CharExpectedLen(uint8_t first)
{
if ((first & utf8OneByteMask_) == utf8OneByteVal_) {
return utf8OneByteLen_;
}
if ((first & utf8TwoByteMask_) == utf8TwoByteVal_) {
return utf8TwoByteLen_;
}
if ((first & utf8ThreeByteMask_) == utf8ThreeByteVal_) {
return utf8ThreeByteLen_;
}
if ((first & utf8FourByteMask_) == utf8FourByteVal_) {
return utf8FourByteLen_;
}
return utf8OneByteLen_;
}
};
class SelectionMetricsCollector {
public:
enum class MetricType { COUNTER, GAUGE, HISTOGRAM };
struct MetricEntry {
std::string name;
MetricType type;
int64_t intValue = 0;
double doubleValue = 0.0;
std::vector<int64_t> samples;
};
static SelectionMetricsCollector& GetInstance()
{
static SelectionMetricsCollector instance;
return instance;
}
void RegisterCounter(const std::string& name)
{
std::lock_guard<std::mutex> lock(mutex_);
if (metrics_.find(name) == metrics_.end()) {
metrics_[name] = {name, MetricType::COUNTER};
}
}
void RegisterGauge(const std::string& name)
{
std::lock_guard<std::mutex> lock(mutex_);
if (metrics_.find(name) == metrics_.end()) {
metrics_[name] = {name, MetricType::GAUGE};
}
}
void RegisterHistogram(const std::string& name)
{
std::lock_guard<std::mutex> lock(mutex_);
if (metrics_.find(name) == metrics_.end()) {
metrics_[name] = {name, MetricType::HISTOGRAM};
}
}
void IncrementCounter(const std::string& name, int64_t delta = 1)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = metrics_.find(name);
if (it != metrics_.end() && it->second.type == MetricType::COUNTER) {
it->second.intValue += delta;
}
}
void SetGauge(const std::string& name, double value)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = metrics_.find(name);
if (it != metrics_.end() && it->second.type == MetricType::GAUGE) {
it->second.doubleValue = value;
}
}
void RecordHistogramSample(const std::string& name, int64_t sample)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = metrics_.find(name);
if (it != metrics_.end() && it->second.type == MetricType::HISTOGRAM) {
it->second.samples.push_back(sample);
if (it->second.samples.size() > maxHistogramSamples_) {
it->second.samples.erase(it->second.samples.begin());
}
}
}
std::optional<int64_t> GetCounterValue(const std::string& name) const
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = metrics_.find(name);
if (it != metrics_.end() && it->second.type == MetricType::COUNTER) {
return it->second.intValue;
}
return std::nullopt;
}
struct HistogramStats {
int64_t min = 0;
int64_t max = 0;
double mean = 0.0;
double p50 = 0.0;
double p95 = 0.0;
double p99 = 0.0;
};
std::optional<HistogramStats> GetHistogramStats(const std::string& name) const
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = metrics_.find(name);
if (it == metrics_.end() || it->second.type != MetricType::HISTOGRAM || it->second.samples.empty()) {
return std::nullopt;
}
auto sorted = it->second.samples;
std::sort(sorted.begin(), sorted.end());
HistogramStats stats;
stats.min = sorted.front();
stats.max = sorted.back();
int64_t sum = 0;
for (auto s : sorted) {
sum += s;
}
stats.mean = static_cast<double>(sum) / static_cast<double>(sorted.size());
stats.p50 = Percentile(sorted, percentileP50);
stats.p95 = Percentile(sorted, percentileP95);
stats.p99 = Percentile(sorted, percentileP99);
return stats;
}
void ResetAll()
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [name, entry] : metrics_) {
entry.intValue = 0;
entry.doubleValue = 0.0;
entry.samples.clear();
}
}
private:
SelectionMetricsCollector() = default;
SelectionMetricsCollector(const SelectionMetricsCollector&) = delete;
SelectionMetricsCollector& operator=(const SelectionMetricsCollector&) = delete;
static double Percentile(const std::vector<int64_t>& sorted, double p)
{
if (sorted.empty()) {
return 0.0;
}
double idx = p * static_cast<double>(sorted.size() - 1);
size_t lo = static_cast<size_t>(std::floor(idx));
size_t hi = static_cast<size_t>(std::ceil(idx));
if (lo == hi || hi >= sorted.size()) {
return static_cast<double>(sorted[lo]);
}
double frac = idx - static_cast<double>(lo);
return static_cast<double>(sorted[lo]) * (1.0 - frac) + static_cast<double>(sorted[hi]) * frac;
}
static constexpr size_t maxHistogramSamples_ = 1024;
static constexpr double percentileP50 = 0.50;
static constexpr double percentileP95 = 0.95;
static constexpr double percentileP99 = 0.99;
std::unordered_map<std::string, MetricEntry> metrics_;
mutable std::mutex mutex_;
};
class SelectionScopedTimer {
public:
explicit SelectionScopedTimer(const std::string& tag)
: tag_(tag), startTime_(std::chrono::steady_clock::now()) {}
SelectionScopedTimer(const std::string& tag, std::function<void(const std::string&, uint64_t)> cb)
: tag_(tag), callback_(std::move(cb)), startTime_(std::chrono::steady_clock::now()) {}
~SelectionScopedTimer()
{
auto ms = ElapsedMs();
if (callback_) {
callback_(tag_, ms);
} else {
SELECTION_HILOGI("ScopedTimer [%{public}s]: %{public}" PRIu64 " ms", tag_.c_str(), ms);
}
}
uint64_t ElapsedMs() const
{
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - startTime_).count());
}
SelectionScopedTimer(const SelectionScopedTimer&) = delete;
SelectionScopedTimer& operator=(const SelectionScopedTimer&) = delete;
private:
std::string tag_;
std::function<void(const std::string&, uint64_t)> callback_;
std::chrono::steady_clock::time_point startTime_;
};
class SelectionDeferredExecutor {
public:
SelectionDeferredExecutor() = default;
~SelectionDeferredExecutor()
{
for (auto it = actions_.rbegin(); it != actions_.rend(); ++it) {
if (*it) {
(*it)();
}
}
}
void Defer(std::function<void()> action)
{
if (action) {
actions_.push_back(std::move(action));
}
}
void CancelAll()
{
actions_.clear();
}
SelectionDeferredExecutor(const SelectionDeferredExecutor&) = delete;
SelectionDeferredExecutor& operator=(const SelectionDeferredExecutor&) = delete;
private:
std::vector<std::function<void()>> actions_;
};
struct SelectionPluginInfo {
std::string name;
std::string soPath;
uint32_t version = 0;
bool isLoaded = false;
void* handle = nullptr;
};
class SelectionPluginRegistry {
public:
static SelectionPluginRegistry& GetInstance()
{
static SelectionPluginRegistry instance;
return instance;
}
bool RegisterPlugin(const std::string& name, const std::string& soPath, uint32_t version)
{
std::lock_guard<std::mutex> lock(mutex_);
if (plugins_.count(name) > 0) {
return false;
}
SelectionPluginInfo info{name, soPath, version, false, nullptr};
plugins_[name] = std::move(info);
return true;
}
bool UnregisterPlugin(const std::string& name)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = plugins_.find(name);
if (it == plugins_.end() || it->second.isLoaded) {
return false;
}
plugins_.erase(it);
return true;
}
std::optional<SelectionPluginInfo> GetPluginInfo(const std::string& name) const
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = plugins_.find(name);
return it != plugins_.end() ? std::optional{it->second} : std::nullopt;
}
bool MarkLoaded(const std::string& name, void* handle)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = plugins_.find(name);
if (it == plugins_.end()) {
return false;
}
it->second.isLoaded = true;
it->second.handle = handle;
return true;
}
bool MarkUnloaded(const std::string& name)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = plugins_.find(name);
if (it == plugins_.end()) {
return false;
}
it->second.isLoaded = false;
it->second.handle = nullptr;
return true;
}
std::vector<SelectionPluginInfo> GetAllPlugins() const
{
std::lock_guard<std::mutex> lock(mutex_);
std::vector<SelectionPluginInfo> result;
for (const auto& [n, info] : plugins_) {
result.push_back(info);
}
return result;
}
void ClearAll()
{
std::lock_guard<std::mutex> lock(mutex_);
plugins_.clear();
}
private:
SelectionPluginRegistry() = default;
SelectionPluginRegistry(const SelectionPluginRegistry&) = delete;
SelectionPluginRegistry& operator=(const SelectionPluginRegistry&) = delete;
std::unordered_map<std::string, SelectionPluginInfo> plugins_;
mutable std::mutex mutex_;
};
class SelectionUserIdMapper {
public:
static SelectionUserIdMapper& GetInstance()
{
static SelectionUserIdMapper instance;
return instance;
}
void MapUidToUserId(int32_t uid, int32_t userId)
{
std::lock_guard<std::mutex> lock(mutex_);
uidToUserId_[uid] = userId;
userIdToUids_[userId].insert(uid);
}
void UnmapUid(int32_t uid)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = uidToUserId_.find(uid);
if (it == uidToUserId_.end()) {
return;
}
int32_t userId = it->second;
uidToUserId_.erase(it);
auto uit = userIdToUids_.find(userId);
if (uit != userIdToUids_.end()) {
uit->second.erase(uid);
if (uit->second.empty()) {
userIdToUids_.erase(uit);
}
}
}
std::optional<int32_t> GetUserId(int32_t uid) const
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = uidToUserId_.find(uid);
return it != uidToUserId_.end() ? std::optional{it->second} : std::nullopt;
}
std::set<int32_t> GetUidsForUser(int32_t userId) const
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = userIdToUids_.find(userId);
return it != userIdToUids_.end() ? it->second : std::set<int32_t>{};
}
void RemoveUser(int32_t userId)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = userIdToUids_.find(userId);
if (it == userIdToUids_.end()) {
return;
}
for (auto uid : it->second) {
uidToUserId_.erase(uid);
}
userIdToUids_.erase(it);
}
void ClearAll()
{
std::lock_guard<std::mutex> lock(mutex_);
uidToUserId_.clear();
userIdToUids_.clear();
}
private:
SelectionUserIdMapper() = default;
SelectionUserIdMapper(const SelectionUserIdMapper&) = delete;
SelectionUserIdMapper& operator=(const SelectionUserIdMapper&) = delete;
std::unordered_map<int32_t, int32_t> uidToUserId_;
std::unordered_map<int32_t, std::set<int32_t>> userIdToUids_;
mutable std::mutex mutex_;
};
struct SelectionPanelCapability {
static constexpr uint32_t defaultMaxContentSize = 6000;
bool supportsTextSelection = false;
bool supportsImageSelection = false;
uint32_t maxContentSize = defaultMaxContentSize;
};
struct SelectionPanelEntry {
std::string bundleName;
std::string abilityName;
int32_t userId = -1;
SelectionPanelCapability capability;
bool isActive = false;
};
class SelectionExtensionPanelRegistry {
public:
static SelectionExtensionPanelRegistry& GetInstance()
{
static SelectionExtensionPanelRegistry instance;
return instance;
}
bool RegisterPanel(const std::string& bundleName, const std::string& abilityName,
int32_t userId, const SelectionPanelCapability& cap)
{
std::lock_guard<std::mutex> lock(mutex_);
std::string key = MakeKey(bundleName, abilityName, userId);
if (panels_.count(key) > 0) {
return false;
}
SelectionPanelEntry entry{bundleName, abilityName, userId, cap, true};
panels_[key] = std::move(entry);
return true;
}
bool UnregisterPanel(const std::string& bundleName, const std::string& abilityName, int32_t userId)
{
std::lock_guard<std::mutex> lock(mutex_);
return panels_.erase(MakeKey(bundleName, abilityName, userId)) > 0;
}
std::optional<SelectionPanelEntry> GetPanel(
const std::string& bundleName, const std::string& abilityName, int32_t userId) const
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = panels_.find(MakeKey(bundleName, abilityName, userId));
return it != panels_.end() ? std::optional{it->second} : std::nullopt;
}
std::vector<SelectionPanelEntry> GetPanelsForUser(int32_t userId) const
{
std::lock_guard<std::mutex> lock(mutex_);
std::vector<SelectionPanelEntry> result;
for (const auto& [k, v] : panels_) {
if (v.userId == userId) {
result.push_back(v);
}
}
return result;
}
bool SetPanelActive(const std::string& bundleName, const std::string& abilityName,
int32_t userId, bool active)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = panels_.find(MakeKey(bundleName, abilityName, userId));
if (it == panels_.end()) {
return false;
}
it->second.isActive = active;
return true;
}
void ClearAll()
{
std::lock_guard<std::mutex> lock(mutex_);
panels_.clear();
}
private:
SelectionExtensionPanelRegistry() = default;
SelectionExtensionPanelRegistry(const SelectionExtensionPanelRegistry&) = delete;
SelectionExtensionPanelRegistry& operator=(const SelectionExtensionPanelRegistry&) = delete;
static std::string MakeKey(const std::string& bn, const std::string& an, int32_t uid)
{
return bn + "/" + an + "/" + std::to_string(uid);
}
std::unordered_map<std::string, SelectionPanelEntry> panels_;
mutable std::mutex mutex_;
};
}
}