From abf9cdebaa56a6e9601b6281ed357f5126383412 Mon Sep 17 00:00:00 2001 From: fengyang Date: Mon, 9 Jun 2025 22:06:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E5=A4=A7?= =?UTF-8?q?=E5=B0=8F=E6=89=93=E7=82=B9;=E5=A2=9E=E5=8A=A0=E7=A9=BA?= =?UTF-8?q?=E9=97=B4=E5=A4=A7=E5=B0=8F=E8=B6=85=E9=99=90=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=B8=85=E7=90=86=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: fengyang --- BUILD.gn | 2 + .../include/bundle_active_config_reader.h | 3 + .../common/include/bundle_active_constant.h | 1 + services/common/include/bundle_active_core.h | 6 +- .../include/bundle_active_event_reporter.h | 52 ++++++ .../include/bundle_active_usage_database.h | 1 + .../src/bundle_active_config_reader.cpp | 36 +++- services/common/src/bundle_active_core.cpp | 53 ++++++ .../src/bundle_active_event_reporter.cpp | 95 ++++++++++ .../src/bundle_active_usage_database.cpp | 25 +++ .../include/bundle_active_report_handler.h | 1 + .../include/bundle_active_user_service.h | 1 + .../src/bundle_active_user_service.cpp | 8 + .../deviceUsage/deviceUsageConfig.xml | 7 + test/resource/deviceUsage/ohos_test.xml | 32 ++++ .../device_usage_statistics_config.json | 3 +- .../device_usage_statistics_config_test1.json | 8 + .../device_usage_statistics_config_test2.json | 7 + test/unittest/BUILD.gn | 54 ++++++ .../bundle_active_event_reporter_test.cpp | 101 ++++++++++ test/unittest/bundle_active_total_test.cpp | 2 + .../bundle_active_usage_database_test.cpp | 87 +++++++++ .../device_usage_statistics_service_test.cpp | 175 ++++++++++++++++++ utils/include/bundle_active_util.h | 6 + utils/src/bundle_active_util.cpp | 104 +++++++++++ 25 files changed, 866 insertions(+), 4 deletions(-) create mode 100644 services/common/include/bundle_active_event_reporter.h create mode 100644 services/common/src/bundle_active_event_reporter.cpp create mode 100644 test/resource/deviceUsage/ohos_test.xml create mode 100644 test/resource/deviceUsage/parseconfig/device_usage_statistics_config_test1.json create mode 100644 test/resource/deviceUsage/parseconfig/device_usage_statistics_config_test2.json create mode 100644 test/unittest/bundle_active_event_reporter_test.cpp diff --git a/BUILD.gn b/BUILD.gn index 884e744..9615773 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -233,6 +233,7 @@ ohos_shared_library("usagestatservice") { "services/common/src/bundle_active_continuous_task_observer.cpp", "services/common/src/bundle_active_core.cpp", "services/common/src/bundle_active_debug_mode.cpp", + "services/common/src/bundle_active_event_reporter.cpp", "services/common/src/bundle_active_open_callback.cpp", "services/common/src/bundle_active_power_state_callback_service.cpp", "services/common/src/bundle_active_service.cpp", @@ -314,6 +315,7 @@ ohos_static_library("usagestatservice_static") { "services/common/src/bundle_active_continuous_task_observer.cpp", "services/common/src/bundle_active_core.cpp", "services/common/src/bundle_active_debug_mode.cpp", + "services/common/src/bundle_active_event_reporter.cpp", "services/common/src/bundle_active_open_callback.cpp", "services/common/src/bundle_active_power_state_callback_service.cpp", "services/common/src/bundle_active_service.cpp", diff --git a/services/common/include/bundle_active_config_reader.h b/services/common/include/bundle_active_config_reader.h index a320202..23dc226 100644 --- a/services/common/include/bundle_active_config_reader.h +++ b/services/common/include/bundle_active_config_reader.h @@ -29,11 +29,14 @@ class BundleActiveConfigReader { public: void LoadConfig(); AppUsePeriodicallyConfig GetApplicationUsePeriodicallyConfig(); + uint64_t GetMaxDataSize(); private: void LoadApplicationUsePeriodically(const char* path); bool GetJsonFromFile(const char* filePath, cJSON *&root); bool ConvertFullPath(const std::string& partialPath, std::string& fullPath); + void LoadMaxDataSize(const char *filePath); AppUsePeriodicallyConfig appUsePeriodicallyConfig_; + uint64_t maxDataSize_ = 0; }; } // namespace DeviceUsageStats diff --git a/services/common/include/bundle_active_constant.h b/services/common/include/bundle_active_constant.h index 6276a4d..b26848d 100644 --- a/services/common/include/bundle_active_constant.h +++ b/services/common/include/bundle_active_constant.h @@ -80,6 +80,7 @@ const int32_t FORM_LAST_TIME_COLUMN_INDEX = 7; const int32_t FORM_UID_COLUMN_INDEX = 8; const int32_t QUERY_CONDITION_VALID = 0; const int32_t QUERY_CONDITION_INVALID = -1; +const int32_t DEFAULT_DELETE_EVENT_DALIYS = 0; const uint32_t BUNDLE_ACTIVE_DB_NAME_MAX_LENGTH = 100; const int64_t TWO_SECONDS = 2 * 1000LL; const int64_t TEN_MINUTES = 10 * 60 * 1000LL; diff --git a/services/common/include/bundle_active_core.h b/services/common/include/bundle_active_core.h index 3c5f46a..64bbe3d 100644 --- a/services/common/include/bundle_active_core.h +++ b/services/common/include/bundle_active_core.h @@ -249,11 +249,15 @@ private: void OnObserverDiedInner(const wptr &remote); void AddbundleUninstalledUid(const int32_t uid); void DelayRemoveBundleUninstalledUid(const int32_t uid); + bool IsUserSpaceMemoryLimit(); + void ProcessDataSize(); + bool IsFolderSizeLimit(); + void DeleteExcessiveTableData(); int64_t flushInterval_; static const int64_t TIME_CHANGE_THRESHOLD_MILLIS = TEN_MINUTES; const int32_t DEFAULT_USER_ID = -1; std::map visibleActivities_; - + double percentUserSpaceLimit_ = 0; int64_t systemTimeShot_; int64_t realTimeShot_; ffrt::mutex mutex_; diff --git a/services/common/include/bundle_active_event_reporter.h b/services/common/include/bundle_active_event_reporter.h new file mode 100644 index 0000000..75217d9 --- /dev/null +++ b/services/common/include/bundle_active_event_reporter.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025 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. + */ + +#ifndef BUNDLE_ACTIVE_EVENT_REPORTER_H +#define BUNDLE_ACTIVE_EVENT_REPORTER_H + +#include +#include +#include +#include + +#include "ffrt.h" +#include "single_instance.h" + +namespace OHOS { +namespace DeviceUsageStats { +class BundleActiveEventReporter { + DECLARE_SINGLE_INSTANCE(BundleActiveEventReporter); +public: + + /** + * @brief report file size event,the files must in path '/data'. + * + */ + void ReportFileSizeEvent(); +private: + void SubmitDelayTask(int64_t delayTime); + void ReportFileSizeDaily(); + void ReportFileSizeInner(); + + std::unordered_set allFilePaths_; + ffrt::mutex mutex_; + bool isTaskSubmit_ = false; + std::string fileSizeRecorderName_; + int64_t lastReportTime_ = -1; +}; +} // namespace DeviceUsageStats +} // namespace OHOS + +#endif // BUNDLE_ACTIVE_EVENT_REPORTER_H \ No newline at end of file diff --git a/services/common/include/bundle_active_usage_database.h b/services/common/include/bundle_active_usage_database.h index b143971..b0f429f 100644 --- a/services/common/include/bundle_active_usage_database.h +++ b/services/common/include/bundle_active_usage_database.h @@ -80,6 +80,7 @@ public: std::map& eventStats, int32_t userId); void QueryNotificationEventStats(int32_t eventId, int64_t beginTime, int64_t endTime, std::map& notificationEventStats, int32_t userId); + void DeleteExcessiveEventTableData(int32_t deleteDays); private: void CheckDatabaseVersion(); diff --git a/services/common/src/bundle_active_config_reader.cpp b/services/common/src/bundle_active_config_reader.cpp index fe13125..16777ef 100644 --- a/services/common/src/bundle_active_config_reader.cpp +++ b/services/common/src/bundle_active_config_reader.cpp @@ -26,10 +26,12 @@ const static char* APPLICATION_USE_PERIODICALLY_KEY = "application_use_periodica const static char* MIN_USE_TIMES = "MinUseTimes"; const static char* MAX_USE_TIMES = "MaxUseTimes"; const static char* MIN_USE_DAYS = "MinUseDays"; +const static char* MAX_DATA_SIZE = "MaxDataSize"; const int32_t DEFAULT_MIN_USE_TIMES = 1; const int32_t DEFAULT_MAX_USE_TIMES = 10; const int32_t DEFAULT_MIN_USE_DAYS = 3; const int32_t MAX_BUFFER = 2048; +const uint64_t DEFAULT_MAX_DATA_SIZE = 5 * 1024 * 1024; void BundleActiveConfigReader::LoadConfig() @@ -42,10 +44,12 @@ void BundleActiveConfigReader::LoadConfig() } for (const auto& filePath : cfgFiles->paths) { LoadApplicationUsePeriodically(filePath); + LoadMaxDataSize(filePath); } BUNDLE_ACTIVE_LOGI("appUsePeriodicallyConfig minUseTimes:%{public}d, maxUseTimes:%{public}d," - "minUseDays:%{public}d", appUsePeriodicallyConfig_.minUseTimes, - appUsePeriodicallyConfig_.maxUseTimes, appUsePeriodicallyConfig_.minUseDays); + "minUseDays:%{public}d maxDataSize:%{public}lu", appUsePeriodicallyConfig_.minUseTimes, + appUsePeriodicallyConfig_.maxUseTimes, appUsePeriodicallyConfig_.minUseDays, + static_cast(maxDataSize_)); FreeCfgFiles(cfgFiles); }; @@ -132,5 +136,33 @@ AppUsePeriodicallyConfig BundleActiveConfigReader::GetApplicationUsePeriodically return appUsePeriodicallyConfig_; }; +void BundleActiveConfigReader::LoadMaxDataSize(const char *filePath) +{ + if (!filePath) { + return; + } + cJSON *root = nullptr; + if (!GetJsonFromFile(filePath, root) || !root) { + BUNDLE_ACTIVE_LOGE("file is empty %{private}s", filePath); + return; + } + cJSON *maxDataSizeItem = cJSON_GetObjectItem(root, MAX_DATA_SIZE); + if (!maxDataSizeItem || !cJSON_IsNumber(maxDataSizeItem)) { + BUNDLE_ACTIVE_LOGE("not have max data size key"); + cJSON_Delete(root); + return; + } + maxDataSize_ = static_cast(maxDataSizeItem->valueint); + cJSON_Delete(root); +}; + +uint64_t BundleActiveConfigReader::GetMaxDataSize() +{ + if (maxDataSize_ == 0) { + return DEFAULT_MAX_DATA_SIZE; + } + return maxDataSize_; +}; + } // namespace DeviceUsageStats } // namespace OHOS \ No newline at end of file diff --git a/services/common/src/bundle_active_core.cpp b/services/common/src/bundle_active_core.cpp index b04f42a..e31432e 100644 --- a/services/common/src/bundle_active_core.cpp +++ b/services/common/src/bundle_active_core.cpp @@ -28,6 +28,7 @@ #include "bundle_constants.h" #include "hisysevent.h" #include "bundle_active_report_controller.h" +#include "bundle_active_event_reporter.h" namespace OHOS { namespace DeviceUsageStats { @@ -36,7 +37,11 @@ const int32_t DEFAULT_OS_ACCOUNT_ID = 0; // 0 is the default id when there is no #endif // OS_ACCOUNT_PART_ENABLED constexpr int32_t BUNDLE_UNINSTALL_DELAY_TIME = 5 * 1000 * 1000; constexpr int32_t MIN_USER_ID = -1; +constexpr int32_t MAX_DELETE_EVENT_DALIYS = 6; +constexpr double DEFAULT_PERCENT_USER_SPACE_LIMIT = 0.1; static constexpr char RSS[] = "RSS"; +static constexpr char FILEMANAGEMENT[] = "FILEMANAGEMENT"; +static constexpr char DATA_PATH[] = "/data"; BundleActiveReportHandlerObject::BundleActiveReportHandlerObject() { @@ -71,6 +76,7 @@ BundleActiveCore::BundleActiveCore() flushInterval_ = THIRTY_MINUTE; debugCore_ = false; } + percentUserSpaceLimit_ = DEFAULT_PERCENT_USER_SPACE_LIMIT; } BundleActiveCore::~BundleActiveCore() @@ -254,6 +260,7 @@ void BundleActiveCore::Init() BUNDLE_ACTIVE_LOGD("system time shot is %{public}lld", (long long)systemTimeShot_); bundleActiveConfigReader_ = std::make_shared(); bundleActiveConfigReader_->LoadConfig(); + BundleActiveEventReporter::GetInstance().ReportFileSizeEvent(); } void BundleActiveCore::InitBundleGroupController() @@ -397,6 +404,10 @@ void BundleActiveCore::RestoreAllData() void BundleActiveCore::RestoreToDatabase(const int32_t userId) { + if (IsUserSpaceMemoryLimit()) { + BUNDLE_ACTIVE_LOGE("The available memory in user space is too low and is not on the disk"); + return; + } BUNDLE_ACTIVE_LOGD("RestoreToDatabase called"); BundleActiveEvent event; event.eventId_ = BundleActiveEvent::FLUSH; @@ -407,6 +418,13 @@ void BundleActiveCore::RestoreToDatabase(const int32_t userId) it->second->ReportEvent(event); } RestoreToDatabaseLocked(userId); + std::shared_ptr bundleActiveCore = shared_from_this(); + ffrt::submit([bundleActiveCore]() { + if (bundleActiveCore == nullptr) { + return; + } + bundleActiveCore->ProcessDataSize(); + }); } void BundleActiveCore::RestoreToDatabaseLocked(const int32_t userId) @@ -973,6 +991,41 @@ void BundleActiveCore::OnObserverDiedInner(const wptr &remote) } recipientMap_.erase(objectProxy); } + +bool BundleActiveCore::IsUserSpaceMemoryLimit() +{ + return BundleActiveUtil::GetPercentOfAvailableUserSpace(DATA_PATH) <= percentUserSpaceLimit_; +} + +void BundleActiveCore::ProcessDataSize() +{ + if (!bundleActiveConfigReader_ || !IsFolderSizeLimit()) { + return; + } + DeleteExcessiveTableData(); +} + +bool BundleActiveCore::IsFolderSizeLimit() +{ + return BundleActiveUtil::GetFolderOrFileSize(BUNDLE_ACTIVE_DATABASE_DIR) >= + bundleActiveConfigReader_->GetMaxDataSize(); +} + +void BundleActiveCore::DeleteExcessiveTableData() +{ + std::lock_guard lock(mutex_); + auto it = userStatServices_.find(currentUsedUser_); + if (it == userStatServices_.end()) { + BUNDLE_ACTIVE_LOGE("currentUsedUser_ is not exit, delete exceeive table data failed"); + return; + } + for (int32_t deleteDays = DEFAULT_DELETE_EVENT_DALIYS; deleteDays <= MAX_DELETE_EVENT_DALIYS; deleteDays++) { + it->second->DeleteExcessiveEventTableData(deleteDays); + if (!IsFolderSizeLimit()) { + return; + } + } +} } // namespace DeviceUsageStats } // namespace OHOS diff --git a/services/common/src/bundle_active_event_reporter.cpp b/services/common/src/bundle_active_event_reporter.cpp new file mode 100644 index 0000000..63853d6 --- /dev/null +++ b/services/common/src/bundle_active_event_reporter.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025 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 "bundle_active_event_reporter.h" +#include "file_ex.h" +#include "hisysevent.h" +#include "bundle_active_log.h" +#include "bundle_active_util.h" +#include "bundle_active_constant.h" + + +namespace OHOS { +namespace DeviceUsageStats { +namespace { +static const int64_t FIRST_REPORT_TIME = 10 * 1000 * 1000; +static const int64_t ONE_DAY_MICRO_SECOND = static_cast(24) * 60 * 60 * 1000 * 1000; +static const char* DATA_FILE_PATH = "/data"; +static const char* FILE_SIZE_REPORTER_RECORDER = "/data/service/el1/public/bundle_usage/file_size_report_time"; +} + +IMPLEMENT_SINGLE_INSTANCE(BundleActiveEventReporter); + +void BundleActiveEventReporter::ReportFileSizeEvent() +{ + std::lock_guard autoLock(mutex_); + if (!isTaskSubmit_) { + fileSizeRecorderName_ = std::string(FILE_SIZE_REPORTER_RECORDER); + SubmitDelayTask(FIRST_REPORT_TIME); + isTaskSubmit_ = true; + } +} + +void BundleActiveEventReporter::SubmitDelayTask(int64_t delayTime) +{ + ffrt::submit([]() { + BundleActiveEventReporter::GetInstance().ReportFileSizeDaily(); + }, ffrt::task_attr().delay(delayTime)); +} + +void BundleActiveEventReporter::ReportFileSizeDaily() +{ + std::string lastReportTime; + LoadStringFromFile(fileSizeRecorderName_, lastReportTime); + if (lastReportTime.empty()) { + ReportFileSizeInner(); + } else { + int64_t lastReportTimeValue = BundleActiveUtil::StringToInt64(lastReportTime); + if (lastReportTimeValue <= 0) { + ReportFileSizeInner(); + } else { + int64_t nowTime = BundleActiveUtil::GetSteadyTime(); + if (nowTime - lastReportTimeValue < ONE_DAY_MICRO_SECOND) { + int64_t nextReportTime = ONE_DAY_MICRO_SECOND - (nowTime - lastReportTimeValue); + SubmitDelayTask(nextReportTime); + } else { + ReportFileSizeInner(); + } + } + } +} + +void BundleActiveEventReporter::ReportFileSizeInner() +{ + BUNDLE_ACTIVE_LOGI("ReportFileSizeInner call"); + std::lock_guard autoLock(mutex_); + std::vector filesPath; + std::vector filesPathSize; + filesPath.emplace_back(BUNDLE_ACTIVE_DATABASE_DIR); + filesPathSize.emplace_back(BundleActiveUtil::GetFolderOrFileSize(BUNDLE_ACTIVE_DATABASE_DIR)); + double remainSize = BundleActiveUtil::GetDeviceValidSize(DATA_FILE_PATH); + HiSysEventWrite(HiviewDFX::HiSysEvent::Domain::FILEMANAGEMENT, "USER_DATA_SIZE", + HiviewDFX::HiSysEvent::EventType::STATISTIC, + "COMPONENT_NAME", "device_usage_statistics", + "PARTITION_NAME", DATA_FILE_PATH, + "REMAIN_PARTITION_SIZE", remainSize, + "FILE_OR_FOLDER_PATH", filesPath, + "FILE_OR_FOLDER_SIZE", filesPathSize); + int64_t nowMicroTime = BundleActiveUtil::GetNowMicroTime(); + SaveStringToFile(fileSizeRecorderName_, std::to_string(nowMicroTime)); + SubmitDelayTask(ONE_DAY_MICRO_SECOND); +} +} // namespace ResourceSchedule +} // namespace OHOS \ No newline at end of file diff --git a/services/common/src/bundle_active_usage_database.cpp b/services/common/src/bundle_active_usage_database.cpp index e8c4e77..68950e1 100644 --- a/services/common/src/bundle_active_usage_database.cpp +++ b/services/common/src/bundle_active_usage_database.cpp @@ -1904,5 +1904,30 @@ int32_t BundleActiveUsageDatabase::JudgeQueryCondition(const int64_t beginTime, } return QUERY_CONDITION_VALID; } + +void BundleActiveUsageDatabase::DeleteExcessiveEventTableData(int32_t deleteDays) +{ + lock_guard lock(databaseMutex_); + // 删除多余event表数据 + if ((eventTableName_ == UNKNOWN_TABLE_NAME) || (eventBeginTime_ == EVENT_BEGIN_TIME_INITIAL_VALUE)) { + return; + } + int64_t eventTableTime = ParseStartTime(eventTableName_); + int64_t deleteTimePoint = eventBeginTime_ - (SIX_DAY_IN_MILLIS_MAX - deleteDays * ONE_DAY_TIME) - eventTableTime; + if (deleteTimePoint <= 0) { + return; + } + shared_ptr rdbStore = GetBundleActiveRdbStore(EVENT_DATABASE_INDEX); + if (rdbStore == nullptr) { + BUNDLE_ACTIVE_LOGE("delete excessive tableData fail, rdbStore is nullptr"); + return; + } + string deleteEventDataSql = "delete from " + eventTableName_ + " where timeStamp <= " + + to_string(deleteTimePoint); + int32_t deleteResult = rdbStore->ExecuteSql(deleteEventDataSql); + if (deleteResult != NativeRdb::E_OK) { + BUNDLE_ACTIVE_LOGE("delete event data failed, rdb error number: %{public}d", deleteResult); + } +} } // namespace DeviceUsageStats } // namespace OHOS diff --git a/services/packageusage/include/bundle_active_report_handler.h b/services/packageusage/include/bundle_active_report_handler.h index 0956833..bae6532 100644 --- a/services/packageusage/include/bundle_active_report_handler.h +++ b/services/packageusage/include/bundle_active_report_handler.h @@ -42,6 +42,7 @@ public: bool HasEvent(const int32_t& eventId); void Init(const std::shared_ptr& bundleActiveCore); void DeInit(); + static const int32_t MSG_REPORT_EVENT; static const int32_t MSG_FLUSH_TO_DISK; static const int32_t MSG_REMOVE_USER; diff --git a/services/packageusage/include/bundle_active_user_service.h b/services/packageusage/include/bundle_active_user_service.h index 299f1c4..7fe18d9 100644 --- a/services/packageusage/include/bundle_active_user_service.h +++ b/services/packageusage/include/bundle_active_user_service.h @@ -77,6 +77,7 @@ public: std::vector& eventStats, int32_t userId); void LoadActiveStats(const int64_t timeStamp, const bool& force, const bool& timeChanged); void LoadModuleAndFormStats(); + void DeleteExcessiveEventTableData(int32_t deleteDays); private: static const int64_t ONE_SECOND_MILLISECONDS = 1000; diff --git a/services/packageusage/src/bundle_active_user_service.cpp b/services/packageusage/src/bundle_active_user_service.cpp index 672549c..2333d01 100644 --- a/services/packageusage/src/bundle_active_user_service.cpp +++ b/services/packageusage/src/bundle_active_user_service.cpp @@ -746,5 +746,13 @@ std::shared_ptr BundleActiveUserService::GetOrCreateMo } return moduleRecords_[combinedInfo]; } + +void BundleActiveUserService::DeleteExcessiveEventTableData(int32_t deleteDays) +{ + if (deleteDays < 0) { + return; + } + database_.DeleteExcessiveEventTableData(deleteDays); +} } // namespace DeviceUsageStats } // namespace OHOS \ No newline at end of file diff --git a/test/resource/deviceUsage/deviceUsageConfig.xml b/test/resource/deviceUsage/deviceUsageConfig.xml index 2dacadb..b24a502 100644 --- a/test/resource/deviceUsage/deviceUsageConfig.xml +++ b/test/resource/deviceUsage/deviceUsageConfig.xml @@ -20,6 +20,13 @@