diff --git a/window_scene/interfaces/include/ws_common.h b/window_scene/interfaces/include/ws_common.h index a29cecb734cb24034aff45f93cab2e3b2495ca30..5df9b2dd5c7409010379461ca4000baa5b87a82f 100644 --- a/window_scene/interfaces/include/ws_common.h +++ b/window_scene/interfaces/include/ws_common.h @@ -350,6 +350,12 @@ struct SessionInfo { bool isAsyncModalBinding_ = false; uint32_t parentWindowType_ = 1; // WINDOW_TYPE_APP_MAIN_WINDOW SessionViewportConfig config_; + + /* + * Multi instance + */ + bool isNewAppInstance_ = false; + std::string appInstanceKey_; }; enum class SessionFlag : uint32_t { diff --git a/window_scene/interfaces/kits/napi/scene_session_manager/js_root_scene_session.cpp b/window_scene/interfaces/kits/napi/scene_session_manager/js_root_scene_session.cpp index 695153657851c334b94009d25d3e4b9ca830c109..da818b84d7d63962cb44e75beab1676a3f434e91 100644 --- a/window_scene/interfaces/kits/napi/scene_session_manager/js_root_scene_session.cpp +++ b/window_scene/interfaces/kits/napi/scene_session_manager/js_root_scene_session.cpp @@ -347,8 +347,8 @@ sptr JsRootSceneSession::GenSceneSession(SessionInfo& info) sceneSession = SceneSessionManager::GetInstance().FindSessionByAffinity( info.sessionAffinity); } else { - sceneSession = SceneSessionManager::GetInstance().GetSceneSessionByName( - info.bundleName_, info.moduleName_, info.abilityName_, info.appIndex_, info.windowType_); + sceneSession = SceneSessionManager::GetInstance().GetSceneSessionByName(info.bundleName_, + info.moduleName_, info.abilityName_, info.appIndex_, info.appInstanceKey_, info.windowType_); } } if (sceneSession == nullptr) { diff --git a/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session.cpp b/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session.cpp index 1b75cc31b7f1fcb6982fb16c33d1011af2a0617b..948c939c2ae2bd8c1cb563174a018a5bb95e9592 100644 --- a/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session.cpp +++ b/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session.cpp @@ -254,6 +254,8 @@ napi_value JsSceneSession::Create(napi_env env, const sptr& sessio CreateJsValue(env, static_cast(session->IsTopmost()))); napi_set_named_property(env, objValue, "subWindowModalType", CreateJsValue(env, static_cast(session->GetSubWindowModalType()))); + napi_set_named_property(env, objValue, "appInstanceKey", + CreateJsValue(env, session->GetSessionInfo().appInstanceKey_)); SetWindowSize(env, objValue, session); const char* moduleName = "JsSceneSession"; @@ -2847,8 +2849,8 @@ sptr JsSceneSession::GenSceneSession(SessionInfo& info) if (SceneSessionManager::GetInstance().CheckCollaboratorType(info.collaboratorType_)) { sceneSession = SceneSessionManager::GetInstance().FindSessionByAffinity(info.sessionAffinity); } else { - sceneSession = SceneSessionManager::GetInstance().GetSceneSessionByName( - info.bundleName_, info.moduleName_, info.abilityName_, info.appIndex_, info.windowType_); + sceneSession = SceneSessionManager::GetInstance().GetSceneSessionByName(info.bundleName_, + info.moduleName_, info.abilityName_, info.appIndex_, info.appInstanceKey_, info.windowType_); } } if (sceneSession == nullptr) { diff --git a/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session_manager.cpp b/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session_manager.cpp index 14841b18eee07dcd980cc5080989a0ce4dc7528f..bd9e54c572945084593f1e69d630452d3b4b5fa6 100644 --- a/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session_manager.cpp +++ b/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session_manager.cpp @@ -199,6 +199,14 @@ napi_value JsSceneSessionManager::Init(napi_env env, napi_value exportObj) JsSceneSessionManager::UpdateAppHookDisplayInfo); BindNativeFunction(env, exportObj, "refreshPcZOrder", moduleName, JsSceneSessionManager::RefreshPcZOrder); + BindNativeFunction(env, exportObj, "getMaxInstanceCount", moduleName, + JsSceneSessionManager::GetMaxInstanceCount); + BindNativeFunction(env, exportObj, "getInstanceCount", moduleName, + JsSceneSessionManager::GetInstanceCount); + BindNativeFunction(env, exportObj, "getLastInstanceKey", moduleName, + JsSceneSessionManager::GetLastInstanceKey); + BindNativeFunction(env, exportObj, "packageRemovedOrChanged", moduleName, + JsSceneSessionManager::PackageRemovedOrChanged); return NapiGetUndefined(env); } @@ -983,6 +991,46 @@ napi_value JsSceneSessionManager::IsScbCoreEnabled(napi_env env, napi_callback_i return (me != nullptr) ? me->OnIsScbCoreEnabled(env, info) : nullptr; } +napi_value JsSceneSessionManager::GetMaxInstanceCount(napi_env env, napi_callback_info info) +{ + JsSceneSessionManager* me = CheckParamsAndGetThis(env, info); + if (me == nullptr) { + TLOGW(WmsLogTag::WMS_LIFE, "me is null"); + return nullptr; + } + return me->OnGetMaxInstanceCount(env, info); +} + +napi_value JsSceneSessionManager::GetInstanceCount(napi_env env, napi_callback_info info) +{ + JsSceneSessionManager* me = CheckParamsAndGetThis(env, info); + if (me == nullptr) { + TLOGW(WmsLogTag::WMS_LIFE, "me is null"); + return nullptr; + } + return me->OnGetInstanceCount(env, info); +} + +napi_value JsSceneSessionManager::GetLastInstanceKey(napi_env env, napi_callback_info info) +{ + JsSceneSessionManager* me = CheckParamsAndGetThis(env, info); + if (me == nullptr) { + TLOGW(WmsLogTag::WMS_LIFE, "me is null"); + return nullptr; + } + return me->OnGetLastInstanceKey(env, info); +} + +napi_value JsSceneSessionManager::PackageRemovedOrChanged(napi_env env, napi_callback_info info) +{ + JsSceneSessionManager* me = CheckParamsAndGetThis(env, info); + if (me == nullptr) { + TLOGW(WmsLogTag::WMS_LIFE, "me is null"); + return nullptr; + } + return me->OnPackageRemovedOrChanged(env, info); +} + napi_value JsSceneSessionManager::RefreshPcZOrder(napi_env env, napi_callback_info info) { TLOGD(WmsLogTag::WMS_LAYOUT, "[NAPI]"); @@ -3184,6 +3232,98 @@ napi_value JsSceneSessionManager::OnRefreshPcZOrder(napi_env env, napi_callback_ return NapiGetUndefined(env); } +napi_value JsSceneSessionManager::OnGetMaxInstanceCount(napi_env env, napi_callback_info info) +{ + size_t argc = 4; + napi_value argv[4] = {nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + if (argc != ARGC_ONE) { + TLOGE(WmsLogTag::WMS_LIFE, "[NAPI]Argc is invalid: %{public}zu", argc); + napi_throw(env, CreateJsError(env, static_cast(WSErrorCode::WS_ERROR_INVALID_PARAM), + "Input parameter is missing or invalid")); + return NapiGetUndefined(env); + } + std::string bundleName; + if (!ConvertFromJsValue(env, argv[0], bundleName)) { + TLOGE(WmsLogTag::WMS_LIFE, "[NAPI]Failed to convert parameter to bundleName"); + napi_throw(env, CreateJsError(env, static_cast(WSErrorCode::WS_ERROR_INVALID_PARAM), + "Input parameter is missing or invalid")); + return NapiGetUndefined(env); + } + napi_value result = nullptr; + napi_create_int32(env, SceneSessionManager::GetInstance().GetMaxInstanceCount(bundleName), &result); + return result; +} + +napi_value JsSceneSessionManager::OnGetInstanceCount(napi_env env, napi_callback_info info) +{ + size_t argc = 4; + napi_value argv[4] = {nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + if (argc != ARGC_ONE) { + TLOGE(WmsLogTag::WMS_LIFE, "[NAPI]Argc is invalid: %{public}zu", argc); + napi_throw(env, CreateJsError(env, static_cast(WSErrorCode::WS_ERROR_INVALID_PARAM), + "Input parameter is missing or invalid")); + return NapiGetUndefined(env); + } + std::string bundleName; + if (!ConvertFromJsValue(env, argv[0], bundleName)) { + TLOGE(WmsLogTag::WMS_LIFE, "[NAPI]Failed to convert parameter to bundleName"); + napi_throw(env, CreateJsError(env, static_cast(WSErrorCode::WS_ERROR_INVALID_PARAM), + "Input parameter is missing or invalid")); + return NapiGetUndefined(env); + } + napi_value result = nullptr; + napi_create_int32(env, SceneSessionManager::GetInstance().GetInstanceCount(bundleName), &result); + return result; +} + +napi_value JsSceneSessionManager::OnGetLastInstanceKey(napi_env env, napi_callback_info info) +{ + size_t argc = 4; + napi_value argv[4] = {nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + if (argc != ARGC_ONE) { + TLOGE(WmsLogTag::WMS_LIFE, "[NAPI]Argc is invalid: %{public}zu", argc); + napi_throw(env, CreateJsError(env, static_cast(WSErrorCode::WS_ERROR_INVALID_PARAM), + "Input parameter is missing or invalid")); + return NapiGetUndefined(env); + } + std::string bundleName; + if (!ConvertFromJsValue(env, argv[0], bundleName)) { + TLOGE(WmsLogTag::WMS_LIFE, "[NAPI]Failed to convert parameter to bundleName"); + napi_throw(env, CreateJsError(env, static_cast(WSErrorCode::WS_ERROR_INVALID_PARAM), + "Input parameter is missing or invalid")); + return NapiGetUndefined(env); + } + std::string instanceKey = SceneSessionManager::GetInstance().GetLastInstanceKey(bundleName); + napi_value result = nullptr; + napi_create_string_utf8(env, instanceKey.c_str(), instanceKey.length(), &result); + return result; +} + +napi_value JsSceneSessionManager::OnPackageRemovedOrChanged(napi_env env, napi_callback_info info) +{ + size_t argc = 4; + napi_value argv[4] = {nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + if (argc != ARGC_ONE) { + TLOGE(WmsLogTag::WMS_LIFE, "[NAPI]Argc is invalid: %{public}zu", argc); + napi_throw(env, CreateJsError(env, static_cast(WSErrorCode::WS_ERROR_INVALID_PARAM), + "Input parameter is missing or invalid")); + return NapiGetUndefined(env); + } + std::string bundleName; + if (!ConvertFromJsValue(env, argv[0], bundleName)) { + TLOGE(WmsLogTag::WMS_LIFE, "[NAPI]Failed to convert parameter to bundleName"); + napi_throw(env, CreateJsError(env, static_cast(WSErrorCode::WS_ERROR_INVALID_PARAM), + "Input parameter is missing or invalid")); + return NapiGetUndefined(env); + } + SceneSessionManager::GetInstance().PackageRemovedOrChanged(bundleName); + return NapiGetUndefined(env); +} + void JsSceneSessionManager::OnCloseTargetFloatWindow(const std::string& bundleName) { TLOGD(WmsLogTag::WMS_MULTI_WINDOW, "[NAPI]in"); diff --git a/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session_manager.h b/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session_manager.h index 0b7c8ac8f75215ce1f1efa473dc69eac4686275e..cd5a946a7ec7c3847cabdd8d3688bf0223329e19 100644 --- a/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session_manager.h +++ b/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_session_manager.h @@ -112,6 +112,13 @@ public: static napi_value IsScbCoreEnabled(napi_env env, napi_callback_info info); static napi_value RefreshPcZOrder(napi_env env, napi_callback_info info); + /* + * Multi instance + */ + static napi_value GetMaxInstanceCount(napi_env env, napi_callback_info info); + static napi_value GetInstanceCount(napi_env env, napi_callback_info info); + static napi_value GetLastInstanceKey(napi_env env, napi_callback_info info); + static napi_value PackageRemovedOrChanged(napi_env env, napi_callback_info info); private: napi_value OnRegisterCallback(napi_env env, napi_callback_info info); napi_value OnGetRootSceneSession(napi_env env, napi_callback_info info); @@ -174,6 +181,14 @@ private: napi_value OnIsScbCoreEnabled(napi_env env, napi_callback_info info); napi_value OnRefreshPcZOrder(napi_env env, napi_callback_info info); + /* + * multi instance + */ + napi_value OnGetMaxInstanceCount(napi_env env, napi_callback_info info); + napi_value OnGetInstanceCount(napi_env env, napi_callback_info info); + napi_value OnGetLastInstanceKey(napi_env env, napi_callback_info info); + napi_value OnPackageRemovedOrChanged(napi_env env, napi_callback_info info); + void OnRootSceneBackEvent(); void OnStatusBarEnabledUpdate(bool enable, const std::string& bundleName); void OnGestureNavigationEnabledUpdate(bool enable, const std::string& bundleName); diff --git a/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_utils.cpp b/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_utils.cpp index 076a3bfa4aa699d918628563e39541e17d166d9b..c3d9e6be79611054f9463969fbd03f3fcd390a3a 100644 --- a/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_utils.cpp +++ b/window_scene/interfaces/kits/napi/scene_session_manager/js_scene_utils.cpp @@ -298,6 +298,32 @@ bool IsJsFullScreenStartUndefined(napi_env env, napi_value jsFullscreenStart, Se return true; } +bool IsJsIsNewAppInstanceUndefined(napi_env env, napi_value jsIsNewAppInstance, SessionInfo& sessionInfo) +{ + if (GetType(env, jsIsNewAppInstance) != napi_undefined) { + bool isNewAppInstance = false; + if (!ConvertFromJsValue(env, jsIsNewAppInstance, isNewAppInstance)) { + TLOGI(WmsLogTag::WMS_LIFE, "Failed to convert parameter to isNewAppInstance"); + return false; + } + sessionInfo.isNewAppInstance_ = isNewAppInstance; + } + return true; +} + +bool IsJsInstanceKeyUndefined(napi_env env, napi_value jsInstanceKey, SessionInfo& sessionInfo) +{ + if (GetType(env, jsInstanceKey) != napi_undefined) { + std::string instanceKey; + if (!ConvertFromJsValue(env, jsInstanceKey, instanceKey)) { + TLOGI(WmsLogTag::WMS_LIFE, "Failed to convert parameter to instanceKey"); + return false; + } + sessionInfo.appInstanceKey_ = instanceKey; + } + return true; +} + bool ConvertSessionInfoName(napi_env env, napi_value jsObject, SessionInfo& sessionInfo) { napi_value jsBundleName = nullptr; @@ -316,6 +342,10 @@ bool ConvertSessionInfoName(napi_env env, napi_value jsObject, SessionInfo& sess napi_get_named_property(env, jsObject, "windowInputType", &jsWindowInputType); napi_value jsFullScreenStart = nullptr; napi_get_named_property(env, jsObject, "fullScreenStart", &jsFullScreenStart); + napi_value jsIsNewAppInstance = nullptr; + napi_get_named_property(env, jsObject, "isNewAppInstance", &jsIsNewAppInstance); + napi_value jsInstanceKey = nullptr; + napi_get_named_property(env, jsObject, "instanceKey", &jsInstanceKey); if (!IsJsBundleNameUndefind(env, jsBundleName, sessionInfo)) { return false; } @@ -337,7 +367,9 @@ bool ConvertSessionInfoName(napi_env env, napi_value jsObject, SessionInfo& sess if (!IsJsWindowInputTypeUndefind(env, jsWindowInputType, sessionInfo)) { return false; } - if (!IsJsFullScreenStartUndefined(env, jsFullScreenStart, sessionInfo)) { + if (!IsJsFullScreenStartUndefined(env, jsFullScreenStart, sessionInfo) || + !IsJsIsNewAppInstanceUndefined(env, jsIsNewAppInstance, sessionInfo) || + !IsJsInstanceKeyUndefined(env, jsInstanceKey, sessionInfo)) { return false; } return true; diff --git a/window_scene/session/BUILD.gn b/window_scene/session/BUILD.gn index c921224d20bc38de3b11f17a5fd4cf65bf50ed60..6ccaf05ae63f538b2dfc709dbf44676fc40c648a 100644 --- a/window_scene/session/BUILD.gn +++ b/window_scene/session/BUILD.gn @@ -50,6 +50,7 @@ ohos_shared_library("scene_session") { "host/src/keyboard_session.cpp", "host/src/main_session.cpp", "host/src/move_drag_controller.cpp", + "host/src/multi_instance_manager.cpp", "host/src/root_scene_session.cpp", "host/src/scb_system_session.cpp", "host/src/scene_persistence.cpp", @@ -79,6 +80,8 @@ ohos_shared_library("scene_session") { external_deps = [ "ability_runtime:ability_start_setting", "ability_runtime:process_options", + "bundle_framework:appexecfwk_base", + "bundle_framework:appexecfwk_core", "c_utils:utils", "eventhandler:libeventhandler", "ffrt:libffrt", diff --git a/window_scene/session/host/include/multi_instance_manager.h b/window_scene/session/host/include/multi_instance_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..e15360e316c034c1ce521b85ccd269fcf7c85a5a --- /dev/null +++ b/window_scene/session/host/include/multi_instance_manager.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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 OHOS_ROSEN_WINDOW_SCENE_MULTI_INSTANCE_MANAGER_H +#define OHOS_ROSEN_WINDOW_SCENE_MULTI_INSTANCE_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include "wm_common.h" +#include "session/host/include/scene_session.h" + +namespace OHOS::AppExecFwk { +class IBundleMgr; +struct ApplicationInfo; +} // namespace OHOS::AppExecFwk + +namespace OHOS::Rosen { +class MultiInstanceManager { +public: + static MultiInstanceManager& GetInstance(); + void Init(const sptr& bundleMgr); + void SetCurrentUserId(int32_t userId); + uint32_t GetMaxInstanceCount(const std::string& bundleName); + uint32_t GetInstanceCount(const std::string& bundleName); + std::string GetLastInstanceKey(const std::string& bundleName); + std::string CreateNewInstanceKey(const std::string& bundleName); + bool IsValidInstanceKey(const std::string& bundleName, const std::string& instanceKey); + void RemoveAppInfo(const std::string& bundleName); + void IncreaseInstanceKeyRefCount(const sptr& sceneSession); + void DecreaseInstanceKeyRefCount(const sptr& sceneSession); + void FillInstanceKeyIfNeed(const sptr& sceneSession); +private: + uint32_t findMinimumAvailableInstanceId(const std::vector& instanceIdList); + bool RemoveInstanceKey(const std::string& bundleName, const std::string& instanceKey); + std::map> bundleInstanceIdListMap_; + std::map appInfoMap_; + std::map bundleInstanceSessionCountMap_; + sptr bundleMgr_; + std::string uiType_; + int32_t userId_; + std::shared_mutex mutex_; + std::shared_mutex appInfoMutex_; +}; +} // namespace OHOS::Rosen + +#endif // OHOS_ROSEN_WINDOW_SCENE_MULTI_INSTANCE_MANAGER_H \ No newline at end of file diff --git a/window_scene/session/host/include/scene_session.h b/window_scene/session/host/include/scene_session.h index ac98915b6d5e202951309ef8d372f7510027aebb..094c28351931dbb059c6ecd22bbe2fae536c8039 100644 --- a/window_scene/session/host/include/scene_session.h +++ b/window_scene/session/host/include/scene_session.h @@ -563,6 +563,11 @@ private: WSPropertyChangeAction action); void NotifyPrivacyModeChange(); + /* + * Multi instance + */ + bool CheckInstanceKey(const sptr abilitySessionInfo, SessionInfo& info); + /* * PiP Window */ diff --git a/window_scene/session/host/include/session.h b/window_scene/session/host/include/session.h index 656527ea2edc9bd95ef723765be22cd4d5fe8689..27c65c29015e1fea5b682d758f3b217cd38bc9aa 100644 --- a/window_scene/session/host/include/session.h +++ b/window_scene/session/host/include/session.h @@ -490,6 +490,7 @@ public: static void SetScbCoreEnabled(bool enabled); bool IsVisible() const; virtual bool IsNeedSyncScenePanelGlobalPosition() { return true; } + void SetAppInstanceKey(const std::string& appInstanceKey); protected: class SessionLifeCycleTask : public virtual RefBase { diff --git a/window_scene/session/host/include/zidl/session_proxy.h b/window_scene/session/host/include/zidl/session_proxy.h index b059b91919d7eb91ca0569f5413c6fca7da3b1ba..b378cf21d3ea0ef6342415bceae24367b55caf07 100644 --- a/window_scene/session/host/include/zidl/session_proxy.h +++ b/window_scene/session/host/include/zidl/session_proxy.h @@ -22,6 +22,7 @@ #include "ws_common.h" namespace OHOS::Rosen { +enum class SessionInterfaceCode; class SessionProxy : public IRemoteProxy { public: explicit SessionProxy(const sptr& impl) : IRemoteProxy(impl) {} @@ -97,6 +98,7 @@ public: private: static inline BrokerDelegator delegator_; + WSError SendRequest(SessionInterfaceCode code, MessageParcel& data, MessageParcel& reply, MessageOption& option); }; } // namespace OHOS::Rosen diff --git a/window_scene/session/host/src/multi_instance_manager.cpp b/window_scene/session/host/src/multi_instance_manager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c92327a6b38e66eb174f952d4a8bedbc1ff5e510 --- /dev/null +++ b/window_scene/session/host/src/multi_instance_manager.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2024 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 "session/host/include/multi_instance_manager.h" +#include +#include "interfaces/include/ws_common.h" +#include "window_manager_hilog.h" +#include "window_helper.h" + +namespace OHOS::Rosen { +namespace { + constexpr uint32_t MAX_INSTANCE_COUNT = 50; + const std::string APP_INSTANCE_KEY_PREFIX = "app_instance_"; +} + +MultiInstanceManager& MultiInstanceManager::GetInstance() +{ + static MultiInstanceManager instance; + return instance; +} + +void MultiInstanceManager::Init(const sptr& bundleMgr) +{ + bundleMgr_ = bundleMgr; +} + +void MultiInstanceManager::SetCurrentUserId(int32_t userId) +{ + userId_ = userId; +} + +uint32_t MultiInstanceManager::GetMaxInstanceCount(const std::string& bundleName) +{ + std::unique_lock lock(appInfoMutex_); + AppExecFwk::ApplicationInfo appInfo; + auto iter = appInfoMap_.find(bundleName); + if (iter != appInfoMap_.end()) { + appInfo = iter->second; + } else { + if (bundleMgr_ && !bundleMgr_->GetApplicationInfo(bundleName, + AppExecFwk::ApplicationFlag::GET_BASIC_APPLICATION_INFO, userId_, appInfo)) { + TLOGE(WmsLogTag::WMS_LIFE, "Get application info fail, bundleName:%{public}s", bundleName.c_str()); + return 0; + } + appInfoMap_[bundleName] = appInfo; + TLOGI(WmsLogTag::WMS_LIFE, "bundleName:%{public}s multiAppModeType:%{public}d maxCount:%{public}d", + bundleName.c_str(), static_cast(appInfo.multiAppMode.multiAppModeType), + appInfo.multiAppMode.maxCount); + } + if (appInfo.multiAppMode.multiAppModeType == AppExecFwk::MultiAppModeType::MULTI_INSTANCE) { + return appInfo.multiAppMode.maxCount; + } + return 0; +} + +uint32_t MultiInstanceManager::GetInstanceCount(const std::string& bundleName) +{ + std::shared_lock lock(mutex_); + auto iter = bundleInstanceIdListMap_.find(bundleName); + if (iter == bundleInstanceIdListMap_.end()) { + return 0; + } else { + return iter->second.size(); + } +} + +std::string MultiInstanceManager::GetLastInstanceKey(const std::string& bundleName) +{ + std::shared_lock lock(mutex_); + auto iter = bundleInstanceIdListMap_.find(bundleName); + if (iter == bundleInstanceIdListMap_.end() || iter->second.size() == 0) { + return ""; + } else { + return APP_INSTANCE_KEY_PREFIX + std::to_string(iter->second.back()); + } +} + +std::string MultiInstanceManager::CreateNewInstanceKey(const std::string& bundleName) +{ + std::unique_lock lock(mutex_); + auto iter = bundleInstanceIdListMap_.find(bundleName); + if (iter == bundleInstanceIdListMap_.end()) { + uint32_t instanceId = 0; + bundleInstanceIdListMap_.emplace(bundleName, std::vector{ instanceId }); + return APP_INSTANCE_KEY_PREFIX + std::to_string(instanceId); + } else { + auto& instanceIdList = iter->second; + uint32_t instanceId = findMinimumAvailableInstanceId(instanceIdList); + instanceIdList.push_back(instanceId); + return APP_INSTANCE_KEY_PREFIX + std::to_string(instanceId); + } +} + +bool MultiInstanceManager::IsValidInstanceKey(const std::string& bundleName, const std::string& instanceKey) +{ + std::shared_lock lock(mutex_); + auto iter = bundleInstanceIdListMap_.find(bundleName); + if (iter == bundleInstanceIdListMap_.end()) { + return false; + } + const auto& instanceIdList = iter->second; + for (auto instanceId : instanceIdList) { + if (APP_INSTANCE_KEY_PREFIX + std::to_string(instanceId) == instanceKey) { + return true; + } + } + return false; +} + +bool MultiInstanceManager::RemoveInstanceKey(const std::string& bundleName, const std::string& instanceKey) +{ + std::unique_lock lock(mutex_); + auto iter = bundleInstanceIdListMap_.find(bundleName); + if (iter == bundleInstanceIdListMap_.end()) { + return false; + } + auto& instanceIdList = iter->second; + for (auto it = instanceIdList.begin(); it != instanceIdList.end(); ++it) { + if (APP_INSTANCE_KEY_PREFIX + std::to_string(*it) == instanceKey) { + instanceIdList.erase(it); + return true; + } + } + return false; +} + +uint32_t MultiInstanceManager::findMinimumAvailableInstanceId(const std::vector& instanceIdList) +{ + for (uint32_t i = 0; i < MAX_INSTANCE_COUNT; i++) { + if (std::find(instanceIdList.begin(), instanceIdList.end(), i) == instanceIdList.end()) { + return i; + } + } + TLOGE(WmsLogTag::DEFAULT, "Not found available instanceId"); + return 0; +} + +void MultiInstanceManager::RemoveAppInfo(const std::string& bundleName) +{ + std::unique_lock lock(appInfoMutex_); + appInfoMap_.erase(bundleName); +} + +void MultiInstanceManager::IncreaseInstanceKeyRefCount(const sptr& sceneSession) +{ + if (!sceneSession) { + TLOGE(WmsLogTag::DEFAULT, "sceneSession is nullptr"); + return; + } + if (!WindowHelper::IsMainWindow(sceneSession->GetWindowType())) { + TLOGE(WmsLogTag::DEFAULT, "sceneSession is not main window"); + return; + } + const auto& instanceKey = sceneSession->GetSessionInfo().appInstanceKey_; + if (instanceKey.empty()) { + TLOGD(WmsLogTag::DEFAULT, "instanceKey is empty"); + return; + } + const auto& bundleName = sceneSession->GetSessionInfo().bundleName_; + const auto bundleInstanceKey = bundleName + instanceKey; + auto iter = bundleInstanceSessionCountMap_.find(bundleInstanceKey); + if (iter == bundleInstanceSessionCountMap_.end()) { + TLOGD(WmsLogTag::DEFAULT, "bundleInstanceKey not exist."); + bundleInstanceSessionCountMap_.emplace(bundleInstanceKey, 1); + } else { + ++(iter->second); + } +} + +void MultiInstanceManager::DecreaseInstanceKeyRefCount(const sptr& sceneSession) +{ + if (!sceneSession) { + TLOGE(WmsLogTag::DEFAULT, "sceneSession is nullptr"); + return; + } + if (!WindowHelper::IsMainWindow(sceneSession->GetWindowType())) { + TLOGE(WmsLogTag::DEFAULT, "sceneSession is not main window"); + return; + } + const auto& instanceKey = sceneSession->GetSessionInfo().appInstanceKey_; + if (instanceKey.empty()) { + TLOGD(WmsLogTag::DEFAULT, "instanceKey is empty"); + return; + } + const auto& bundleName = sceneSession->GetSessionInfo().bundleName_; + const auto bundleInstanceKey = bundleName + instanceKey; + auto iter = bundleInstanceSessionCountMap_.find(bundleInstanceKey); + if (iter == bundleInstanceSessionCountMap_.end()) { + TLOGD(WmsLogTag::DEFAULT, "bundleInstanceKey not exist."); + return; + } + if (--(iter->second) <= 0) { + bundleInstanceSessionCountMap_.erase(bundleInstanceKey); + RemoveInstanceKey(bundleName, instanceKey); + } +} + +void MultiInstanceManager::FillInstanceKeyIfNeed(const sptr& sceneSession) +{ + if (!sceneSession) { + TLOGE(WmsLogTag::DEFAULT, "sceneSession is nullptr"); + return; + } + const auto& bundleName = sceneSession->GetSessionInfo().bundleName_; + uint32_t maxInstanceCount = GetMaxInstanceCount(bundleName); + if (maxInstanceCount <= 0) { + TLOGD(WmsLogTag::DEFAULT, "maxInstanceCount is not valid"); + return; + } + const auto& sessionInfo = sceneSession->GetSessionInfo(); + if (sessionInfo.appInstanceKey_.empty()) { + uint32_t instanceCount = GetInstanceCount(bundleName); + if (sessionInfo.isNewAppInstance_ && instanceCount < maxInstanceCount) { + sceneSession->SetAppInstanceKey(CreateNewInstanceKey(bundleName)); + } else { + sceneSession->SetAppInstanceKey(GetLastInstanceKey(bundleName)); + } + } +} +} // namespace OHOS::Rosen diff --git a/window_scene/session/host/src/scene_session.cpp b/window_scene/session/host/src/scene_session.cpp index 37d0ddf51e88721b1e12f1554b760ce5c47d5a74..a88ab2e40276c876b8f83db97a028286b8e7703d 100644 --- a/window_scene/session/host/src/scene_session.cpp +++ b/window_scene/session/host/src/scene_session.cpp @@ -52,6 +52,7 @@ #include "screen.h" #include "singleton_container.h" #include "fold_screen_state_internel.h" +#include "session/host/include/multi_instance_manager.h" #ifdef POWER_MANAGER_ENABLE #include @@ -3155,6 +3156,7 @@ static SessionInfo MakeSessionInfoDuringPendingActivation(const sptrisAtomicService; info.isBackTransition_ = abilitySessionInfo->isBackTransition; info.needClearInNotShowRecent_ = abilitySessionInfo->needClearInNotShowRecent; + info.appInstanceKey_ = abilitySessionInfo->instanceKey; if (info.want != nullptr) { info.windowMode = info.want->GetIntParam(AAFwk::Want::PARAM_RESV_WINDOW_MODE, 0); @@ -3169,10 +3171,10 @@ static SessionInfo MakeSessionInfoDuringPendingActivation(const sptr ab } session->sessionInfo_.startMethod = StartMethod::START_CALL; SessionInfo info = MakeSessionInfoDuringPendingActivation(abilitySessionInfo, session->GetPersistentId()); + if (session->systemConfig_.IsPcWindow()) { + int32_t maxInstanceCount = MultiInstanceManager::GetInstance().GetMaxInstanceCount(info.bundleName_); + TLOGI(WmsLogTag::WMS_LIFE, "id:%{public}d maxInstanceCount:%{public}d", + session->GetPersistentId(), maxInstanceCount); + if (maxInstanceCount > 0 && !session->CheckInstanceKey(abilitySessionInfo, info)) { + TLOGI(WmsLogTag::WMS_LIFE, "instanceKey is invalid, id:%{public}d instanceKey:%{public}s", + session->GetPersistentId(), info.appInstanceKey_.c_str()); + return WSError::WS_ERROR_INVALID_PARAM; + } + } session->HandleCastScreenConnection(info, session); if (session->pendingSessionActivationFunc_) { session->pendingSessionActivationFunc_(info); @@ -4839,4 +4851,19 @@ void SceneSession::SetCustomDecorHeight(int32_t height) } customDecorHeight_ = height; } + +bool SceneSession::CheckInstanceKey(const sptr abilitySessionInfo, SessionInfo& info) +{ + if (info.appInstanceKey_.empty()) { + if (abilitySessionInfo->persistentId != 0) { + TLOGD(WmsLogTag::WMS_LIFE, "empty instance key, persistentId:%{public}d", + abilitySessionInfo->persistentId); + return false; + } + } else if (!MultiInstanceManager::GetInstance().IsValidInstanceKey(info.bundleName_, info.appInstanceKey_)) { + TLOGD(WmsLogTag::WMS_LIFE, "invalid instancekey:%{public}s", info.appInstanceKey_.c_str()); + return false; + } + return true; +} } // namespace OHOS::Rosen diff --git a/window_scene/session/host/src/session.cpp b/window_scene/session/host/src/session.cpp index 2d869c4b7b11b5e66dd3f181e8d6ab2f1d40503f..f790e4dda99cb59a971df8d29e3738dfc458377a 100644 --- a/window_scene/session/host/src/session.cpp +++ b/window_scene/session/host/src/session.cpp @@ -291,6 +291,11 @@ void Session::SetScreenId(uint64_t screenId) } } +void Session::SetAppInstanceKey(const std::string& appInstanceKey) +{ + sessionInfo_.appInstanceKey_ = appInstanceKey; +} + const SessionInfo& Session::GetSessionInfo() const { return sessionInfo_; diff --git a/window_scene/session/host/src/zidl/session_proxy.cpp b/window_scene/session/host/src/zidl/session_proxy.cpp index 9038e623dc8449cd1dbd1ddacc5eb138b2277296..6946a4d55715693c80a0c02b5d7674426f7de2f7 100644 --- a/window_scene/session/host/src/zidl/session_proxy.cpp +++ b/window_scene/session/host/src/zidl/session_proxy.cpp @@ -429,18 +429,11 @@ WSError SessionProxy::PendingSessionActivation(sptr abilityS return WSError::WS_ERROR_IPC_FAILED; } } - sptr remote = Remote(); - if (remote == nullptr) { - WLOGFE("remote is null"); - return WSError::WS_ERROR_IPC_FAILED; - } - if (remote->SendRequest(static_cast(SessionInterfaceCode::TRANS_ID_ACTIVE_PENDING_SESSION), - data, reply, option) != ERR_NONE) { - WLOGFE("SendRequest failed"); + if (!data.WriteString(abilitySessionInfo->instanceKey)) { + TLOGE(WmsLogTag::WMS_LIFE, "Write instanceKey failed"); return WSError::WS_ERROR_IPC_FAILED; } - int32_t ret = reply.ReadInt32(); - return static_cast(ret); + return SendRequest(SessionInterfaceCode::TRANS_ID_ACTIVE_PENDING_SESSION, data, reply, option); } WSError SessionProxy::TerminateSession(const sptr abilitySessionInfo) @@ -1857,4 +1850,19 @@ void SessionProxy::NotifyExtensionEventAsync(uint32_t notifyEvent) return; } } + +WSError SessionProxy::SendRequest(SessionInterfaceCode code, MessageParcel& data, MessageParcel& reply, + MessageOption& option) +{ + sptr remote = Remote(); + if (remote == nullptr) { + TLOGE(WmsLogTag::DEFAULT, "remote is null, code:%{public}d", static_cast(code)); + return WSError::WS_ERROR_IPC_FAILED; + } + if (remote->SendRequest(static_cast(code), data, reply, option) != ERR_NONE) { + TLOGE(WmsLogTag::DEFAULT, "SendRequest failed, code:%{public}d", static_cast(code)); + return WSError::WS_ERROR_IPC_FAILED; + } + return static_cast(reply.ReadInt32()); +} } // namespace OHOS::Rosen diff --git a/window_scene/session/host/src/zidl/session_stub.cpp b/window_scene/session/host/src/zidl/session_stub.cpp index da74262f6683722052bbe58eb0c4b7020e27d566..720e6d646c82f919038a69a051f02268ffc8d07a 100644 --- a/window_scene/session/host/src/zidl/session_stub.cpp +++ b/window_scene/session/host/src/zidl/session_stub.cpp @@ -524,17 +524,11 @@ int SessionStub::HandlePendingSessionActivation(MessageParcel& data, MessageParc } auto processOptions = data.ReadParcelable(); abilitySessionInfo->processOptions.reset(processOptions); - if (!data.ReadBool(abilitySessionInfo->canStartAbilityFromBackground)) { - TLOGE(WmsLogTag::WMS_LIFE, "Read canStartAbilityFromBackground failed."); - return ERR_INVALID_VALUE; - } - if (!data.ReadBool(abilitySessionInfo->isAtomicService)) { - TLOGE(WmsLogTag::WMS_LIFE, "Read isAtomicService failed."); - return ERR_INVALID_VALUE; - } - if (!data.ReadBool(abilitySessionInfo->isBackTransition) || + if (!data.ReadBool(abilitySessionInfo->canStartAbilityFromBackground) || + !data.ReadBool(abilitySessionInfo->isAtomicService) || + !data.ReadBool(abilitySessionInfo->isBackTransition) || !data.ReadBool(abilitySessionInfo->needClearInNotShowRecent)) { - TLOGE(WmsLogTag::WMS_LIFE, "Read isBackTransition or needClearInNotShowRecent failed."); + TLOGE(WmsLogTag::WMS_LIFE, "Read some bool failed."); return ERR_INVALID_VALUE; } bool hasCallerToken = false; @@ -554,6 +548,10 @@ int SessionStub::HandlePendingSessionActivation(MessageParcel& data, MessageParc auto abilityStartSetting = data.ReadParcelable(); abilitySessionInfo->startSetting.reset(abilityStartSetting); } + if (!data.ReadString(abilitySessionInfo->instanceKey)) { + TLOGE(WmsLogTag::WMS_LIFE, "Read instanceKey failed."); + return ERR_INVALID_VALUE; + } WSError errCode = PendingSessionActivation(abilitySessionInfo); reply.WriteUint32(static_cast(errCode)); return ERR_NONE; diff --git a/window_scene/session_manager/include/scene_session_manager.h b/window_scene/session_manager/include/scene_session_manager.h index 85b04dc975b9209577e4e0a798714199689593ed..8cb52653357bfb950732f7ebb74d1bc7628591b5 100644 --- a/window_scene/session_manager/include/scene_session_manager.h +++ b/window_scene/session_manager/include/scene_session_manager.h @@ -155,7 +155,7 @@ public: void PostFlushWindowInfoTask(FlushWindowInfoTask &&task, const std::string taskName, const int delayTime); sptr GetSceneSessionByName(const std::string& bundleName, const std::string& moduleName, - const std::string& abilityName, const int32_t appIndex, + const std::string& abilityName, const int32_t appIndex, const std::string instanceKey = "", const uint32_t windowType = static_cast(WindowType::WINDOW_TYPE_APP_MAIN_WINDOW)); sptr GetSceneSessionByType(WindowType type); @@ -428,6 +428,13 @@ public: WMError SetSnapshotSkipByUserIdAndBundleNameList(const int32_t userId, const std::vector& bundleNameList) override; + /* + * Multi instance + */ + int32_t GetMaxInstanceCount(const std::string& bundleName); + int32_t GetInstanceCount(const std::string& bundleName); + std::string GetLastInstanceKey(const std::string& bundleName); + void PackageRemovedOrChanged(const std::string& bundleName); protected: SceneSessionManager(); virtual ~SceneSessionManager(); diff --git a/window_scene/session_manager/src/scene_session_manager.cpp b/window_scene/session_manager/src/scene_session_manager.cpp index c0dfa46f099a15176cf0dc1d2d4b9f8d475135fd..da0ea3e6782be27afd24691d857e7d1960815d5d 100644 --- a/window_scene/session_manager/src/scene_session_manager.cpp +++ b/window_scene/session_manager/src/scene_session_manager.cpp @@ -67,6 +67,7 @@ #include "anomaly_detection.h" #include "hidump_controller.h" #include "window_pid_visibility_info.h" +#include "session/host/include/multi_instance_manager.h" #ifdef MEMMGR_WINDOW_ENABLE #include "mem_mgr_client.h" @@ -275,6 +276,7 @@ void SceneSessionManager::Init() TLOGI(WmsLogTag::WMS_EVENT, "register WindowStateError callback with ret: %{public}d", retCode); scbDumpSubscriber_ = ScbDumpSubscriber::Subscribe(); + MultiInstanceManager::GetInstance().Init(bundleMgr_); } void SceneSessionManager::InitScheduleUtils() @@ -761,6 +763,7 @@ void SceneSessionManager::ClearUnrecoveredSessions(const std::vector& r std::unique_lock lock(sceneSessionMapMutex_); visibleWindowCountMap_.erase(sceneSession->GetCallingPid()); sceneSessionMap_.erase(persistentId); + MultiInstanceManager::GetInstance().DecreaseInstanceKeyRefCount(sceneSession); } } } @@ -1222,7 +1225,8 @@ sptr SceneSessionManager::GetSceneSession(int32_t persistentId) } sptr SceneSessionManager::GetSceneSessionByName(const std::string& bundleName, - const std::string& moduleName, const std::string& abilityName, const int32_t appIndex, const uint32_t windowType) + const std::string& moduleName, const std::string& abilityName, const int32_t appIndex, + const std::string instanceKey, const uint32_t windowType) { std::shared_lock lock(sceneSessionMapMutex_); for (const auto &item : sceneSessionMap_) { @@ -1231,6 +1235,7 @@ sptr SceneSessionManager::GetSceneSessionByName(const std::string& sceneSession->GetSessionInfo().moduleName_ == moduleName && sceneSession->GetSessionInfo().abilityName_ == abilityName && sceneSession->GetSessionInfo().appIndex_ == appIndex && + sceneSession->GetSessionInfo().appInstanceKey_ == instanceKey && sceneSession->GetSessionInfo().windowType_ == windowType) { return sceneSession; } @@ -1516,8 +1521,8 @@ sptr SceneSessionManager::RequestSceneSession(const SessionInfo& s "abilityName: %{public}s, appIndex: %{public}d", sessionInfo.bundleName_.c_str(), sessionInfo.moduleName_.c_str(), sessionInfo.abilityName_.c_str(), sessionInfo.appIndex_); - auto sceneSession = GetSceneSessionByName(sessionInfo.bundleName_, - sessionInfo.moduleName_, sessionInfo.abilityName_, sessionInfo.appIndex_, sessionInfo.windowType_); + auto sceneSession = GetSceneSessionByName(sessionInfo.bundleName_, sessionInfo.moduleName_, + sessionInfo.abilityName_, sessionInfo.appIndex_, sessionInfo.appInstanceKey_, sessionInfo.windowType_); bool isSingleStart = sceneSession && sceneSession->GetAbilityInfo() && sceneSession->GetAbilityInfo()->launchMode == AppExecFwk::LaunchMode::SINGLETON; if (isSingleStart) { @@ -1541,6 +1546,9 @@ sptr SceneSessionManager::RequestSceneSession(const SessionInfo& s TLOGE(WmsLogTag::WMS_LIFE, "sceneSession is nullptr!"); return sceneSession; } + if (systemConfig_.IsPcWindow()) { + MultiInstanceManager::GetInstance().FillInstanceKeyIfNeed(sceneSession); + } InitSceneSession(sceneSession, sessionInfo, property); if (CheckCollaboratorType(sceneSession->GetCollaboratorType())) { TLOGNI(WmsLogTag::WMS_LIFE, "%{public}s: ancoSceneState: %{public}d", @@ -1560,6 +1568,7 @@ sptr SceneSessionManager::RequestSceneSession(const SessionInfo& s std::unique_lock lock(sceneSessionMapMutex_); sceneSessionMap_.insert({ sceneSession->GetPersistentId(), sceneSession }); } + MultiInstanceManager::GetInstance().IncreaseInstanceKeyRefCount(sceneSession); PerformRegisterInRequestSceneSession(sceneSession); NotifySessionUpdate(sessionInfo, ActionType::SINGLE_START); TLOGI(WmsLogTag::WMS_LIFE, "RequestSceneSession id: %{public}d, type: %{public}d", @@ -1722,6 +1731,7 @@ sptr SceneSessionManager::SetAbilitySessionInfo(const sptrwant.SetParam(AAFwk::Want::PARAM_RESV_DISPLAY_ID, static_cast(sessionProperty->GetDisplayId())); } + abilitySessionInfo->instanceKey = sessionInfo.appInstanceKey_; return abilitySessionInfo; } @@ -2131,6 +2141,7 @@ void SceneSessionManager::EraseSceneSessionMapById(int32_t persistentId) sceneSessionMap_.erase(persistentId); systemTopSceneSessionMap_.erase(persistentId); nonSystemFloatSceneSessionMap_.erase(persistentId); + MultiInstanceManager::GetInstance().DecreaseInstanceKeyRefCount(sceneSession); } WSError SceneSessionManager::RequestSceneSessionDestruction(const sptr& sceneSession, @@ -3154,6 +3165,7 @@ WSError SceneSessionManager::InitUserInfo(int32_t userId, std::string& fileDir) } currentUserId_ = userId; SceneInputManager::GetInstance().SetCurrentUserId(currentUserId_); + MultiInstanceManager::GetInstance().SetCurrentUserId(currentUserId_); RegisterSecSurfaceInfoListener(); return WSError::WS_OK; }; @@ -3224,6 +3236,7 @@ void SceneSessionManager::NotifySwitchingUser(const bool isUserActive) SceneInputManager::GetInstance().SetUserBackground(!isUserActive); if (isUserActive) { // switch to current user SceneInputManager::GetInstance().SetCurrentUserId(currentUserId_); + MultiInstanceManager::GetInstance().SetCurrentUserId(currentUserId_); // notify screenSessionManager to recover current user ScreenSessionManagerClient::GetInstance().SwitchingCurrentUser(); FlushWindowInfoToMMI(true); @@ -10850,4 +10863,23 @@ void SceneSessionManager::RemoveProcessWatermarkPid(int32_t pid) } } +int32_t SceneSessionManager::GetMaxInstanceCount(const std::string& bundleName) +{ + return MultiInstanceManager::GetInstance().GetMaxInstanceCount(bundleName); +} + +int32_t SceneSessionManager::GetInstanceCount(const std::string& bundleName) +{ + return MultiInstanceManager::GetInstance().GetInstanceCount(bundleName); +} + +std::string SceneSessionManager::GetLastInstanceKey(const std::string& bundleName) +{ + return MultiInstanceManager::GetInstance().GetLastInstanceKey(bundleName); +} + +void SceneSessionManager::PackageRemovedOrChanged(const std::string& bundleName) +{ + return MultiInstanceManager::GetInstance().RemoveAppInfo(bundleName); +} } // namespace OHOS::Rosen