diff --git a/frameworks/kits/ability/ability_runtime/BUILD.gn b/frameworks/kits/ability/ability_runtime/BUILD.gn index fad4b1454bc7c1b43d88d7929e97462e886f75c0..15a8e584e82cd88ff79acb1902756074561890f0 100644 --- a/frameworks/kits/ability/ability_runtime/BUILD.gn +++ b/frameworks/kits/ability/ability_runtime/BUILD.gn @@ -52,6 +52,7 @@ ohos_shared_library("ability_context_native") { "ability_runtime:ability_manager", "ability_runtime:app_manager", "ability_runtime:runtime", + "access_token:libaccesstoken_sdk", "bundle_framework:appexecfwk_base", "bytrace_standard:bytrace_core", "faultloggerd:lib_dfx_dump_catcher", diff --git a/frameworks/kits/ability/ability_runtime/src/ability_context_impl.cpp b/frameworks/kits/ability/ability_runtime/src/ability_context_impl.cpp index ae9d87680bf3c4f123f177b65a8b811c792e14c7..8e0281880c3597c681374677b7abba10064fec41 100644 --- a/frameworks/kits/ability/ability_runtime/src/ability_context_impl.cpp +++ b/frameworks/kits/ability/ability_runtime/src/ability_context_impl.cpp @@ -18,9 +18,15 @@ #include #include "ability_manager_client.h" +#include "accesstoken_kit.h" #include "bytrace.h" #include "connection_manager.h" #include "hilog_wrapper.h" +#include "permission_list_state.h" + +using OHOS::Security::AccessToken::AccessTokenKit; +using OHOS::Security::AccessToken::PermissionListState; +using OHOS::Security::AccessToken::TypePermissionOper; namespace OHOS { namespace AbilityRuntime { @@ -28,6 +34,7 @@ const size_t AbilityContext::CONTEXT_TYPE_ID(std::hash {} ("Ability const std::string GRANT_ABILITY_BUNDLE_NAME = "com.ohos.permissionmanager"; const std::string GRANT_ABILITY_ABILITY_NAME = "com.ohos.permissionmanager.GrantAbility"; const std::string PERMISSION_KEY = "ohos.user.grant.permission"; +const std::string STATE_KEY = "ohos.user.grant.permission.state"; std::string AbilityContextImpl::GetBundleCodeDir() { @@ -315,22 +322,61 @@ void AbilityContextImpl::RequestPermissionsFromUser(const std::vectorStartAbility(want, token_, requestCode); - HILOG_INFO("%{public}s. End calling StartAbility. ret=%{public}d", __func__, err); + + std::vector permList; + for (auto permission : permissions) { + HILOG_DEBUG("%{public}s. permission: %{public}s.", __func__, permission.c_str()); + PermissionListState permState; + permState.permissionName = permission; + permState.state = -1; + permList.emplace_back(permState); + } + HILOG_DEBUG("%{public}s. permList size: %{public}zu, permissions size: %{public}zu.", + __func__, permList.size(), permissions.size()); + + auto ret = AccessTokenKit::GetSelfPermissionsState(permList); + if (permList.size() != permissions.size()) { + HILOG_ERROR("%{public}s. Returned permList size: %{public}zu.", __func__, permList.size()); + return; + } + + std::vector permissionsState; + for (auto permState : permList) { + HILOG_DEBUG("%{public}s. permissions: %{public}s. permissionsState: %{public}u", + __func__, permState.permissionName.c_str(), permState.state); + permissionsState.emplace_back(permState.state); + } + HILOG_DEBUG("%{public}s. permissions size: %{public}zu. permissionsState size: %{public}zu.", + __func__, permissions.size(), permissionsState.size()); + + if (ret == TypePermissionOper::DYNAMIC_OPER) { + AAFwk::Want want; + want.SetElementName(GRANT_ABILITY_BUNDLE_NAME, GRANT_ABILITY_ABILITY_NAME); + want.SetParam(PERMISSION_KEY, permissions); + want.SetParam(STATE_KEY, permissionsState); + permissionRequestCallbacks_.insert(make_pair(requestCode, std::move(task))); + HILOG_DEBUG("%{public}s. Start calling StartAbility.", __func__); + ErrCode err = AAFwk::AbilityManagerClient::GetInstance()->StartAbility(want, token_, requestCode); + HILOG_DEBUG("%{public}s. End calling StartAbility. ret=%{public}d", __func__, err); + } else { + HILOG_DEBUG("%{public}s. No dynamic popup required.", __func__); + if (task) { + task(permissions, permissionsState); + } + } } void AbilityContextImpl::OnRequestPermissionsFromUserResult( - int requestCode, const std::vector &permissions, const std::vector &grantResults) + int requestCode, const std::vector &permissions, const std::vector &permissionsState) { HILOG_DEBUG("%{public}s. Start calling OnRequestPermissionsFromUserResult.", __func__); - permissionRequestCallbacks_[requestCode](permissions, grantResults); - permissionRequestCallbacks_.erase(requestCode); - HILOG_INFO("%{public}s. End calling OnRequestPermissionsFromUserResult.", __func__); + auto iter = permissionRequestCallbacks_.find(requestCode); + if (iter != permissionRequestCallbacks_.end() && iter->second) { + auto task = iter->second; + task(permissions, permissionsState); + permissionRequestCallbacks_.erase(iter); + HILOG_DEBUG("%{public}s. End calling OnRequestPermissionsFromUserResult.", __func__); + } } ErrCode AbilityContextImpl::RestoreWindowStage(NativeEngine& engine, NativeValue* contentStorage) diff --git a/frameworks/kits/ability/native/include/ability_context.h b/frameworks/kits/ability/native/include/ability_context.h index c305fc1d84c7f76ba908d0e0fdbcdc5e4f491719..38aa7ed5112e6fa5352946bb86cc4920dc587aab 100644 --- a/frameworks/kits/ability/native/include/ability_context.h +++ b/frameworks/kits/ability/native/include/ability_context.h @@ -415,10 +415,12 @@ public: * the Ability.onRequestPermissionsFromUserResult(int, String[], int[]) method will be called back. * * @param permissions Indicates the list of permissions to be requested. This parameter cannot be null. + * @param permissionsState Indicates the list of permissions' state to be requested. This parameter cannot be null. * @param requestCode Indicates the request code to be passed to the Ability.onRequestPermissionsFromUserResult(int, * String[], int[]) callback method. This code cannot be a negative number. */ - virtual void RequestPermissionsFromUser(std::vector &permissions, int requestCode) override; + virtual void RequestPermissionsFromUser(std::vector &permissions, std::vector &permissionsState, + int requestCode) override; /** * @brief Deletes the specified private file associated with the application. diff --git a/frameworks/kits/ability/native/include/ability_process.h b/frameworks/kits/ability/native/include/ability_process.h index 0cdd83475fb3a179810996cb3ffa013479d810da..840075a6eb952b1dc65910e40df1e1736f0c12c5 100644 --- a/frameworks/kits/ability/native/include/ability_process.h +++ b/frameworks/kits/ability/native/include/ability_process.h @@ -39,6 +39,10 @@ public: void OnRequestPermissionsFromUserResult(Ability *ability, int requestCode, const std::vector &permissions, const std::vector &grantResults); +private: + bool CaullFunc(int requestCode, const std::vector &permissions, + const std::vector &permissionsState, CallbackInfo &callbackInfo); + private: static std::mutex mutex_; static std::shared_ptr instance_; diff --git a/frameworks/kits/ability/native/src/ability_context.cpp b/frameworks/kits/ability/native/src/ability_context.cpp index 0dd3ac43101147f7ff28396441b0b65c8529f039..cacf97cbf5af106c51d8e220257cac3e0c7f7ec3 100644 --- a/frameworks/kits/ability/native/src/ability_context.cpp +++ b/frameworks/kits/ability/native/src/ability_context.cpp @@ -32,6 +32,7 @@ int AbilityContext::ABILITY_CONTEXT_DEFAULT_REQUEST_CODE(0); const std::string GRANT_ABILITY_BUNDLE_NAME = "com.ohos.permissionmanager"; const std::string GRANT_ABILITY_ABILITY_NAME = "com.ohos.permissionmanager.GrantAbility"; const std::string PERMISSION_KEY = "ohos.user.grant.permission"; +const std::string STATE_KEY = "ohos.user.grant.permission.state"; ErrCode AbilityContext::StartAbility(const AAFwk::Want &want, int requestCode) { @@ -407,7 +408,8 @@ void AbilityContext::GetPermissionDes(const std::string &permissionName, std::st HILOG_DEBUG("%{public}s end GetPermissionDef.", __func__); } -void AbilityContext::RequestPermissionsFromUser(std::vector &permissions, int requestCode) +void AbilityContext::RequestPermissionsFromUser(std::vector &permissions, + std::vector &permissionsState, int requestCode) { HILOG_INFO("%{public}s begin.", __func__); if (permissions.size() == 0) { @@ -423,6 +425,7 @@ void AbilityContext::RequestPermissionsFromUser(std::vector &permis AAFwk::Want want; want.SetElementName(GRANT_ABILITY_BUNDLE_NAME, GRANT_ABILITY_ABILITY_NAME); want.SetParam(PERMISSION_KEY, permissions); + want.SetParam(STATE_KEY, permissionsState); StartAbility(want, requestCode); HILOG_INFO("%{public}s end.", __func__); } diff --git a/frameworks/kits/ability/native/src/ability_process.cpp b/frameworks/kits/ability/native/src/ability_process.cpp index 141730a193a02bcc4053b29856feb26aa5178d8b..1256f21ee8e37c046de6707b0fffebcd84943404 100644 --- a/frameworks/kits/ability/native/src/ability_process.cpp +++ b/frameworks/kits/ability/native/src/ability_process.cpp @@ -17,7 +17,14 @@ #include +#include "accesstoken_kit.h" #include "hilog_wrapper.h" +#include "permission_list_state.h" + +using OHOS::Security::AccessToken::AccessTokenKit; +using OHOS::Security::AccessToken::PermissionListState; +using OHOS::Security::AccessToken::TypePermissionOper; + namespace OHOS { namespace AppExecFwk { static void *g_handle = nullptr; @@ -165,7 +172,39 @@ void AbilityProcess::RequestPermissionsFromUser( return; } - ability->RequestPermissionsFromUser(param.permission_list, param.requestCode); + std::vector permList; + for (auto permission : param.permission_list) { + HILOG_DEBUG("%{public}s. permission: %{public}s.", __func__, permission.c_str()); + PermissionListState permState; + permState.permissionName = permission; + permState.state = -1; + permList.emplace_back(permState); + } + HILOG_DEBUG("%{public}s. permList size: %{public}zu, permissions size: %{public}zu.", + __func__, permList.size(), param.permission_list.size()); + + auto ret = AccessTokenKit::GetSelfPermissionsState(permList); + if (permList.size() != param.permission_list.size()) { + HILOG_ERROR("%{public}s. Returned permList size: %{public}zu.", __func__, permList.size()); + return; + } + + std::vector permissionsState; + for (auto permState : permList) { + HILOG_DEBUG("%{public}s. permissions: %{public}s. permissionsState: %{public}u", + __func__, permState.permissionName.c_str(), permState.state); + permissionsState.emplace_back(permState.state); + } + HILOG_DEBUG("%{public}s. permissions size: %{public}zu. permissionsState size: %{public}zu", + __func__, param.permission_list.size(), permissionsState.size()); + + if (ret != TypePermissionOper::DYNAMIC_OPER) { + HILOG_DEBUG("%{public}s. No dynamic popup required.", __func__); + (void)CaullFunc(param.requestCode, param.permission_list, permissionsState, callbackInfo); + return; + } + + ability->RequestPermissionsFromUser(param.permission_list, permissionsState, param.requestCode); { std::lock_guard lock_l(mutex_); @@ -173,23 +212,20 @@ void AbilityProcess::RequestPermissionsFromUser( auto it = abilityRequestPermissionsForUserMap_.find(ability); if (it == abilityRequestPermissionsForUserMap_.end()) { HILOG_INFO("AbilityProcess::RequestPermissionsFromUser ability: %{public}p is not in the " - "abilityRequestPermissionsForUserMap_", - ability); + "abilityRequestPermissionsForUserMap_", ability); } else { HILOG_INFO("AbilityProcess::RequestPermissionsFromUser ability: %{public}p is in the " - "abilityRequestPermissionsForUserMap_", - ability); + "abilityRequestPermissionsForUserMap_", ability); map = it->second; } map[param.requestCode] = callbackInfo; abilityRequestPermissionsForUserMap_[ability] = map; } - HILOG_INFO("AbilityProcess::RequestPermissionsFromUser end"); } void AbilityProcess::OnRequestPermissionsFromUserResult(Ability *ability, int requestCode, - const std::vector &permissions, const std::vector &grantResults) + const std::vector &permissions, const std::vector &permissionsState) { HILOG_INFO("AbilityProcess::OnRequestPermissionsFromUserResult begin"); if (ability == nullptr) { @@ -215,16 +251,27 @@ void AbilityProcess::OnRequestPermissionsFromUserResult(Ability *ability, int re return; } CallbackInfo callbackInfo = callback->second; + if (!CaullFunc(requestCode, permissions, permissionsState, callbackInfo)) { + HILOG_ERROR("AbilityProcess::OnRequestPermissionsFromUserResult call function failed."); + return; + } + map.erase(requestCode); + + abilityRequestPermissionsForUserMap_[ability] = map; + HILOG_INFO("AbilityProcess::OnRequestPermissionsFromUserResult end"); +} + +bool AbilityProcess::CaullFunc(int requestCode, const std::vector &permissions, + const std::vector &permissionsState, CallbackInfo &callbackInfo) +{ #ifdef SUPPORT_GRAPHICS // start open featureability lib if (g_handle == nullptr) { g_handle = dlopen(SHARED_LIBRARY_FEATURE_ABILITY, RTLD_LAZY); if (g_handle == nullptr) { HILOG_ERROR("%{public}s, dlopen failed %{public}s. %{public}s", - __func__, - SHARED_LIBRARY_FEATURE_ABILITY, - dlerror()); - return; + __func__, SHARED_LIBRARY_FEATURE_ABILITY, dlerror()); + return false; } } #endif @@ -233,18 +280,13 @@ void AbilityProcess::OnRequestPermissionsFromUserResult(Ability *ability, int re dlsym(g_handle, FUNC_CALL_ON_REQUEST_PERMISSIONS_FROM_USERRESULT)); if (func == nullptr) { HILOG_ERROR("%{public}s, dlsym failed %{public}s. %{public}s", - __func__, - FUNC_CALL_ON_REQUEST_PERMISSIONS_FROM_USERRESULT, - dlerror()); + __func__, FUNC_CALL_ON_REQUEST_PERMISSIONS_FROM_USERRESULT, dlerror()); dlclose(g_handle); g_handle = nullptr; - return; + return false; } - func(requestCode, permissions, grantResults, callbackInfo); - map.erase(requestCode); - - abilityRequestPermissionsForUserMap_[ability] = map; - HILOG_INFO("AbilityProcess::OnRequestPermissionsFromUserResult end"); + func(requestCode, permissions, permissionsState, callbackInfo); + return true; } } // namespace AppExecFwk } // namespace OHOS \ No newline at end of file diff --git a/frameworks/kits/ability/native/test/unittest/ability_permission_test.cpp b/frameworks/kits/ability/native/test/unittest/ability_permission_test.cpp index d79244ecffa736b7f2a16311f780e4bd7bea65cf..449d15e6bb2059c35917d921474251a14198d22e 100644 --- a/frameworks/kits/ability/native/test/unittest/ability_permission_test.cpp +++ b/frameworks/kits/ability/native/test/unittest/ability_permission_test.cpp @@ -140,11 +140,12 @@ HWTEST_F( permissions.emplace_back("permission_1"); permissions.emplace_back("permission_2"); permissions.emplace_back("permission_3"); + std::vector permissionsState(permissions.size(), -1); std::shared_ptr deal = std::make_shared(); deal->SetApplicationInfo(appInfo); context_->AttachBaseContext(deal); - context_->RequestPermissionsFromUser(permissions, 1004); + context_->RequestPermissionsFromUser(permissions, permissionsState, 1004); } } // namespace AppExecFwk } // namespace OHOS \ No newline at end of file diff --git a/frameworks/kits/appkit/native/app/include/context.h b/frameworks/kits/appkit/native/app/include/context.h index 5f9ad4bd4c629409d480c598fec7c2cd17bf3b2d..16234e37a917d2ee70ac56ea77a8a637cc809225 100644 --- a/frameworks/kits/appkit/native/app/include/context.h +++ b/frameworks/kits/appkit/native/app/include/context.h @@ -268,8 +268,8 @@ public: /** * @brief Checks whether the current process has the given permission. - * You need to call requestPermissionsFromUser(java.lang.std::string[],int) to request a permission only - * if the current process does not have the specific permission. + * You need to call requestPermissionsFromUser(std::vector,std::vector, int) to request + * a permission only if the current process does not have the specific permission. * * @param permission Indicates the permission to check. This parameter cannot be null. * @@ -419,11 +419,13 @@ public: * the Ability.onRequestPermissionsFromUserResult(int, String[], int[]) method will be called back. * * @param permissions Indicates the list of permissions to be requested. This parameter cannot be null. + * @param permissionsState Indicates the list of permissions' state to be requested. This parameter cannot be null. * @param requestCode Indicates the request code to be passed to the Ability.onRequestPermissionsFromUserResult(int, * String[], int[]) callback method. This code cannot be a negative number. * */ - virtual void RequestPermissionsFromUser(std::vector &permissions, int requestCode) = 0; + virtual void RequestPermissionsFromUser(std::vector &permissions, std::vector &permissionsState, + int requestCode) = 0; /** * @brief Starts a new ability with special ability start setting. diff --git a/frameworks/kits/appkit/native/app/include/context_container.h b/frameworks/kits/appkit/native/app/include/context_container.h index 833e8743b81d2372fc2f36ee73429f03efc0ffe7..cb9753c2fd52ad301cc13fdc186f7ef013beaa44 100644 --- a/frameworks/kits/appkit/native/app/include/context_container.h +++ b/frameworks/kits/appkit/native/app/include/context_container.h @@ -307,11 +307,13 @@ public: * the Ability.onRequestPermissionsFromUserResult(int, String[], int[]) method will be called back. * * @param permissions Indicates the list of permissions to be requested. This parameter cannot be null. + * @param permissionsState Indicates the list of permissions' state to be requested. This parameter cannot be null. * @param requestCode Indicates the request code to be passed to the Ability.onRequestPermissionsFromUserResult(int, * String[], int[]) callback method. This code cannot be a negative number. * */ - void RequestPermissionsFromUser(std::vector &permissions, int requestCode) override; + void RequestPermissionsFromUser(std::vector &permissions, std::vector &permissionsState, + int requestCode) override; /** * @brief Creates a Context object for an application with the given bundle name. diff --git a/frameworks/kits/appkit/native/app/include/context_deal.h b/frameworks/kits/appkit/native/app/include/context_deal.h index 77152de25ee286bfae7d9624e4d57f922c093dce..516a71fb10804a0ed438ea25fc981941bb029e4c 100644 --- a/frameworks/kits/appkit/native/app/include/context_deal.h +++ b/frameworks/kits/appkit/native/app/include/context_deal.h @@ -263,8 +263,8 @@ public: /** * @brief Checks whether the current process has the given permission. - * You need to call requestPermissionsFromUser(java.lang.std::string[],int) to request a permission only - * if the current process does not have the specific permission. + * You need to call requestPermissionsFromUser(std::vector,std::vector, int) to request + * a permission only if the current process does not have the specific permission. * * @param permission Indicates the permission to check. This parameter cannot be null. * @@ -407,11 +407,12 @@ public: * the Ability.onRequestPermissionsFromUserResult(int, String[], int[]) method will be called back. * * @param permissions Indicates the list of permissions to be requested. This parameter cannot be null. + * @param permissionsState Indicates the list of permissions' state to be requested. This parameter cannot be null. * @param requestCode Indicates the request code to be passed to the Ability.onRequestPermissionsFromUserResult(int, * String[], int[]) callback method. This code cannot be a negative number. - * */ - void RequestPermissionsFromUser(std::vector &permissions, int requestCode) override; + void RequestPermissionsFromUser(std::vector &permissions, std::vector &permissionsState, + int requestCode) override; /** * @brief Starts a new ability with special ability start setting. diff --git a/frameworks/kits/appkit/native/app/src/context_container.cpp b/frameworks/kits/appkit/native/app/src/context_container.cpp index 58ef2de10ad51253986cb479b9e6f1180700eec6..ae9b0102303626cc0565ddab676b4cde2d8b3966 100644 --- a/frameworks/kits/appkit/native/app/src/context_container.cpp +++ b/frameworks/kits/appkit/native/app/src/context_container.cpp @@ -337,8 +337,8 @@ std::string ContextContainer::GetNoBackupFilesDir() /** * @brief Checks whether the current process has the given permission. - * You need to call requestPermissionsFromUser(java.lang.std::string[],int) to request a permission only - * if the current process does not have the specific permission. + * You need to call requestPermissionsFromUser(std::vector,std::vector, int) to request a permission + * only if the current process does not have the specific permission. * * @param permission Indicates the permission to check. This parameter cannot be null. * @@ -536,14 +536,16 @@ std::string ContextContainer::GetProcessName() * the Ability.onRequestPermissionsFromUserResult(int, String[], int[]) method will be called back. * * @param permissions Indicates the list of permissions to be requested. This parameter cannot be null. + * @param permissionsState Indicates the list of permissions' state to be requested. This parameter cannot be null. * @param requestCode Indicates the request code to be passed to the Ability.onRequestPermissionsFromUserResult(int, * String[], int[]) callback method. This code cannot be a negative number. * */ -void ContextContainer::RequestPermissionsFromUser(std::vector &permissions, int requestCode) +void ContextContainer::RequestPermissionsFromUser(std::vector &permissions, + std::vector &permissionsState, int requestCode) { if (baseContext_ != nullptr) { - baseContext_->RequestPermissionsFromUser(permissions, requestCode); + baseContext_->RequestPermissionsFromUser(permissions, permissionsState, requestCode); } else { HILOG_ERROR("ContextContainer::RequestPermissionsFromUser baseContext_ is nullptr"); } diff --git a/frameworks/kits/appkit/native/app/src/context_deal.cpp b/frameworks/kits/appkit/native/app/src/context_deal.cpp index 71da6bb9f621a8227746eac927a7a2dd23d3ef50..de52519738396b3f7677b90c1c75a4337753906a 100644 --- a/frameworks/kits/appkit/native/app/src/context_deal.cpp +++ b/frameworks/kits/appkit/native/app/src/context_deal.cpp @@ -450,8 +450,8 @@ std::string ContextDeal::GetNoBackupFilesDir() /** * @brief Checks whether the current process has the given permission. - * You need to call requestPermissionsFromUser(java.lang.std::string[],int) to request a permission only - * if the current process does not have the specific permission. + * You need to call requestPermissionsFromUser(std::vector,std::vector, int) to request a permission + * only if the current process does not have the specific permission. * * @param permission Indicates the permission to check. This parameter cannot be null. * @@ -735,12 +735,13 @@ std::string ContextDeal::GetCallingBundle() * the Ability.onRequestPermissionsFromUserResult(int, String[], int[]) method will be called back. * * @param permissions Indicates the list of permissions to be requested. This parameter cannot be null. + * @param permissionsState Indicates the list of permissions' state to be requested. This parameter cannot be null. * @param requestCode Indicates the request code to be passed to the Ability.onRequestPermissionsFromUserResult(int, * String[], int[]) callback method. This code cannot be a negative number. * */ -void ContextDeal::RequestPermissionsFromUser(std::vector &permissions, int requestCode) -{} +void ContextDeal::RequestPermissionsFromUser(std::vector &permissions, std::vector &permissionsState, + int requestCode) {} /** * @brief Starts a new ability with special ability start setting. diff --git a/services/test/mock/include/mock_context.h b/services/test/mock/include/mock_context.h index 0cf1c77870358c474fa2cbabba9ae1d6a04da02b..1fa288daea5b5a5e3f4e56c6fc19d46bc9a4d77a 100644 --- a/services/test/mock/include/mock_context.h +++ b/services/test/mock/include/mock_context.h @@ -62,7 +62,8 @@ public: virtual std::shared_ptr GetHapModuleInfo() = 0; virtual std::string GetProcessName() = 0; virtual std::string GetCallingBundle() = 0; - virtual void RequestPermissionsFromUser(std::vector &permissions, int requestCode) = 0; + virtual void RequestPermissionsFromUser(std::vector &permissions, std::vector &permissionsState, + int requestCode) = 0; virtual void StartAbility(const Want &want, int requestCode, const AbilityStartSetting &abilityStartSetting) = 0; virtual bool ConnectAbility(const Want &want, const sptr &conn) = 0; virtual void DisconnectAbility(const sptr &conn) = 0;