1 /*
2 * Copyright (c) 2024-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "common/rs_background_thread.h"
17 #include "src/gpu/gl/GrGLDefines.h"
18 #include "rs_trace.h"
19 #include "pipeline/rs_uni_render_processor.h"
20 #include "pipeline/rs_uni_render_util.h"
21 #include "pipeline/sk_resource_manager.h"
22 #include "rs_pointer_render_manager.h"
23
24 namespace OHOS {
25 namespace Rosen {
26 namespace {
27 static const std::string DISPLAY = "DisplayNode";
28 static const std::string POINTER = "pointer";
29 static const float RGB = 255.0f;
30 static const float HALF = 0.5f;
31 static const int32_t CALCULATE_MIDDLE = 2;
32 } // namespace
33 static std::unique_ptr<RSPointerRenderManager> g_pointerRenderManagerInstance =
34 std::make_unique<RSPointerRenderManager>();
35
GetInstance()36 RSPointerRenderManager& RSPointerRenderManager::GetInstance()
37 {
38 return *g_pointerRenderManagerInstance;
39 }
40
41 #if defined (RS_ENABLE_VK)
InitInstance(const std::shared_ptr<RSVkImageManager> & vkImageManager)42 void RSPointerRenderManager::InitInstance(const std::shared_ptr<RSVkImageManager>& vkImageManager)
43 {
44 g_pointerRenderManagerInstance->vkImageManager_ = vkImageManager;
45 }
46 #endif
47
48 #if defined (RS_ENABLE_GL) && defined (RS_ENABLE_EGLIMAGE)
InitInstance(const std::shared_ptr<RSEglImageManager> & eglImageManager)49 void RSPointerRenderManager::InitInstance(const std::shared_ptr<RSEglImageManager>& eglImageManager)
50 {
51 g_pointerRenderManagerInstance->eglImageManager_ = eglImageManager;
52 }
53 #endif
54
GetCurrentTime()55 int64_t RSPointerRenderManager::GetCurrentTime()
56 {
57 auto timeNow = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now());
58 return std::chrono::duration_cast<std::chrono::milliseconds>(timeNow.time_since_epoch()).count();
59 }
60
SetPointerColorInversionConfig(float darkBuffer,float brightBuffer,int64_t interval,int32_t rangeSize)61 void RSPointerRenderManager::SetPointerColorInversionConfig(float darkBuffer, float brightBuffer,
62 int64_t interval, int32_t rangeSize)
63 {
64 std::lock_guard<std::mutex> lock(cursorInvertMutex_);
65 darkBuffer_ = darkBuffer;
66 brightBuffer_ = brightBuffer;
67 colorSamplingInterval_ = interval;
68 rangeSize_ = rangeSize;
69 }
70
SetPointerColorInversionEnabled(bool enable)71 void RSPointerRenderManager::SetPointerColorInversionEnabled(bool enable)
72 {
73 std::lock_guard<std::mutex> lock(cursorInvertMutex_);
74 isEnableCursorInversion_ = enable;
75 }
76
RegisterPointerLuminanceChangeCallback(pid_t pid,sptr<RSIPointerLuminanceChangeCallback> callback)77 void RSPointerRenderManager::RegisterPointerLuminanceChangeCallback(pid_t pid,
78 sptr<RSIPointerLuminanceChangeCallback> callback)
79 {
80 std::lock_guard<std::mutex> lock(cursorInvertMutex_);
81 colorChangeListeners_[pid] = callback;
82 }
83
UnRegisterPointerLuminanceChangeCallback(pid_t pid)84 void RSPointerRenderManager::UnRegisterPointerLuminanceChangeCallback(pid_t pid)
85 {
86 std::lock_guard<std::mutex> lock(cursorInvertMutex_);
87 colorChangeListeners_.erase(pid);
88 }
89
ExecutePointerLuminanceChangeCallback(int32_t brightness)90 void RSPointerRenderManager::ExecutePointerLuminanceChangeCallback(int32_t brightness)
91 {
92 std::lock_guard<std::mutex> lock(cursorInvertMutex_);
93 lastColorPickerTime_ = RSPointerRenderManager::GetCurrentTime();
94 for (auto it = colorChangeListeners_.begin(); it != colorChangeListeners_.end(); it++) {
95 if (it->second) {
96 it->second->OnPointerLuminanceChanged(brightness);
97 }
98 }
99 }
100
CallPointerLuminanceChange(int32_t brightness)101 void RSPointerRenderManager::CallPointerLuminanceChange(int32_t brightness)
102 {
103 RS_LOGD("RSPointerRenderManager::CallPointerLuminanceChange luminance_:%{public}d.", luminance_);
104 if (brightnessMode_ == CursorBrightness::NONE) {
105 brightnessMode_ = brightness < static_cast<int32_t>(RGB * HALF) ?
106 CursorBrightness::DARK : CursorBrightness::BRIGHT;
107 ExecutePointerLuminanceChangeCallback(brightness);
108 } else if (brightnessMode_ == CursorBrightness::DARK) {
109 // Dark cursor to light cursor buffer
110 if (brightness > static_cast<int32_t>(RGB * darkBuffer_)) {
111 brightnessMode_ = CursorBrightness::BRIGHT;
112 ExecutePointerLuminanceChangeCallback(brightness);
113 }
114 } else {
115 // light cursor to Dark cursor buffer
116 if (brightness < static_cast<int32_t>(RGB * brightBuffer_)) {
117 brightnessMode_ = CursorBrightness::DARK;
118 ExecutePointerLuminanceChangeCallback(brightness);
119 }
120 }
121 }
122
CheckColorPickerEnabled()123 bool RSPointerRenderManager::CheckColorPickerEnabled()
124 {
125 if (!isEnableCursorInversion_ || taskDoing_) {
126 return false;
127 }
128
129 auto time = RSPointerRenderManager::GetCurrentTime() - lastColorPickerTime_;
130 if (time < colorSamplingInterval_) {
131 return false;
132 }
133
134 bool exists = false;
135 auto& threadParams = RSUniRenderThread::Instance().GetRSRenderThreadParams();
136 if (threadParams == nullptr) {
137 ROSEN_LOGE("RSPointerRenderManager::CheckColorPickerEnabled threadParams == nullptr");
138 return false;
139 }
140 auto& hardwareDrawables = threadParams->GetHardwareEnabledTypeDrawables();
141
142 for (const auto& drawable : hardwareDrawables) {
143 auto surfaceDrawable = std::static_pointer_cast<DrawableV2::RSSurfaceRenderNodeDrawable>(drawable);
144 if (surfaceDrawable != nullptr && surfaceDrawable->IsHardwareEnabledTopSurface()) {
145 exists = true;
146 break;
147 }
148 }
149
150 return exists;
151 }
152
ProcessColorPicker(std::shared_ptr<RSProcessor> processor,std::shared_ptr<Drawing::GPUContext> gpuContext)153 void RSPointerRenderManager::ProcessColorPicker(std::shared_ptr<RSProcessor> processor,
154 std::shared_ptr<Drawing::GPUContext> gpuContext)
155 {
156 if (!CheckColorPickerEnabled()) {
157 ROSEN_LOGD("RSPointerRenderManager::CheckColorPickerEnabled is false!");
158 return;
159 }
160
161 RS_TRACE_BEGIN("RSPointerRenderManager ProcessColorPicker");
162 std::lock_guard<std::mutex> locker(mtx_);
163
164 if (cacheImgForPointer_) {
165 if (!GetIntersectImageBySubset(gpuContext)) {
166 ROSEN_LOGE("RSPointerRenderManager::GetIntersectImageBySubset is false!");
167 return;
168 }
169 } else {
170 if (!CalculateTargetLayer(processor)) { // get the layer intersect with pointer and calculate the rect
171 ROSEN_LOGD("RSPointerRenderManager::CalculateTargetLayer is false!");
172 return;
173 }
174 }
175
176 // post color picker task to background thread
177 RunColorPickerTask();
178 RS_TRACE_END();
179 }
180
GetIntersectImageBySubset(std::shared_ptr<Drawing::GPUContext> gpuContext)181 bool RSPointerRenderManager::GetIntersectImageBySubset(std::shared_ptr<Drawing::GPUContext> gpuContext)
182 {
183 auto& hardwareDrawables =
184 RSUniRenderThread::Instance().GetRSRenderThreadParams()->GetHardwareEnabledTypeDrawables();
185 for (const auto& drawable : hardwareDrawables) {
186 auto surfaceDrawable = std::static_pointer_cast<DrawableV2::RSSurfaceRenderNodeDrawable>(drawable);
187 if (surfaceDrawable == nullptr || !surfaceDrawable->IsHardwareEnabledTopSurface() ||
188 !surfaceDrawable->GetRenderParams()) {
189 continue;
190 }
191 image_ = std::make_shared<Drawing::Image>();
192 RectI pointerRect = surfaceDrawable->GetRenderParams()->GetAbsDrawRect();
193 CalculateColorRange(pointerRect);
194 Drawing::RectI drawingPointerRect = Drawing::RectI(pointerRect.GetLeft(), pointerRect.GetTop(),
195 pointerRect.GetRight(), pointerRect.GetBottom());
196 image_->BuildSubset(cacheImgForPointer_, drawingPointerRect, *gpuContext);
197
198 Drawing::TextureOrigin origin = Drawing::TextureOrigin::BOTTOM_LEFT;
199 backendTexture_ = image_->GetBackendTexture(false, &origin);
200 bitmapFormat_ = Drawing::BitmapFormat{ image_->GetColorType(), image_->GetAlphaType() };
201 return true;
202 }
203 return false;
204 }
205
CalculateTargetLayer(std::shared_ptr<RSProcessor> processor)206 bool RSPointerRenderManager::CalculateTargetLayer(std::shared_ptr<RSProcessor> processor)
207 {
208 auto uniRenderProcessor = std::static_pointer_cast<RSUniRenderProcessor>(processor);
209 if (uniRenderProcessor == nullptr) {
210 ROSEN_LOGE("RSPointerRenderManager::CalculateTargetLayer uniRenderProcessor is null!");
211 return false;
212 }
213 std::vector<LayerInfoPtr> layers = uniRenderProcessor->GetLayers();
214 forceCPU_ = RSBaseRenderEngine::NeedForceCPU(layers);
215
216 std::sort(layers.begin(), layers.end(), [](LayerInfoPtr a, LayerInfoPtr b) {
217 return a->GetZorder() < b->GetZorder();
218 });
219 // get pointer and display node layer
220 bool find = false;
221 RectI pRect;
222 int displayNodeIndex = INT_MAX;
223 for (int i = 0; i < layers.size(); ++i) {
224 std::string name = layers[i]->GetSurface()->GetName();
225 if (name.find(DISPLAY) != std::string::npos) {
226 displayNodeIndex = i;
227 continue;
228 }
229 if (name.find(POINTER) != std::string::npos) {
230 GraphicIRect rect = layers[i]->GetLayerSize();
231 pRect.SetAll(rect.x, rect.y, rect.w, rect.h);
232 find = true;
233 continue;
234 }
235 }
236
237 if (!find || displayNodeIndex == INT_MAX) {
238 ROSEN_LOGD("RSPointerRenderManager::CalculateTargetLayer cannot find pointer or display node.");
239 return false;
240 }
241 CalculateColorRange(pRect);
242 // calculate the max intersection layer and rect
243 GetRectAndTargetLayer(layers, pRect, displayNodeIndex);
244
245 return true;
246 }
247
CalculateColorRange(RectI & pRect)248 void RSPointerRenderManager::CalculateColorRange(RectI& pRect)
249 {
250 if (rangeSize_ > 0) {
251 int left = pRect.GetLeft() + (pRect.GetWidth() - rangeSize_) / CALCULATE_MIDDLE;
252 int top = pRect.GetTop() + (pRect.GetHeight() - rangeSize_) / CALCULATE_MIDDLE;
253 pRect.SetAll(left, top, rangeSize_, rangeSize_);
254 }
255 }
256
GetRectAndTargetLayer(std::vector<LayerInfoPtr> & layers,RectI & pRect,int displayNodeIndex)257 void RSPointerRenderManager::GetRectAndTargetLayer(std::vector<LayerInfoPtr>& layers, RectI& pRect,
258 int displayNodeIndex)
259 {
260 target_ = nullptr;
261 rect_.Clear();
262
263 for (int i = std::max(0, displayNodeIndex - 1); i >= 0; --i) {
264 if (layers[i]->GetSurface()->GetName().find(POINTER) != std::string::npos) {
265 continue;
266 }
267 GraphicIRect layerSize = layers[i]->GetLayerSize();
268 RectI curRect = RectI(layerSize.x, layerSize.y, layerSize.w, layerSize.h);
269 if (!curRect.Intersect(pRect)) {
270 continue;
271 }
272 RectI dstRect = curRect.IntersectRect(pRect);
273 if (dstRect.width_ * dstRect.height_ >= rect_.width_ * rect_.height_) {
274 dstRect = dstRect.Offset(-curRect.GetLeft(), -curRect.GetTop());
275 rect_ = dstRect;
276 target_ = layers[i];
277 }
278 }
279
280 // if not intersect with other layers, calculate the displayNode intersection rect
281 if (target_ == nullptr) {
282 target_ = layers[displayNodeIndex];
283 GraphicIRect layerSize = target_->GetLayerSize();
284 RectI curRect = RectI(layerSize.x, layerSize.y, layerSize.w, layerSize.h);
285 RectI dstRect = curRect.IntersectRect(pRect);
286 rect_ = dstRect.Offset(-curRect.GetLeft(), -curRect.GetTop());
287 }
288 }
289
CalcAverageLuminance(std::shared_ptr<Drawing::Image> image)290 int16_t RSPointerRenderManager::CalcAverageLuminance(std::shared_ptr<Drawing::Image> image)
291 {
292 // create a 1x1 SkPixmap
293 uint32_t pixel[1] = { 0 };
294 Drawing::ImageInfo single_pixel_info(1, 1, Drawing::ColorType::COLORTYPE_RGBA_8888,
295 Drawing::AlphaType::ALPHATYPE_PREMUL);
296 Drawing::Bitmap single_pixel;
297 single_pixel.Build(single_pixel_info, single_pixel_info.GetBytesPerPixel());
298 single_pixel.SetPixels(pixel);
299
300 // resize image to 1x1 to calculate average color
301 // kMedium_SkFilterQuality will do bilerp + mipmaps for down-scaling, we can easily get average color
302 image->ScalePixels(single_pixel,
303 Drawing::SamplingOptions(Drawing::FilterMode::LINEAR, Drawing::MipmapMode::LINEAR));
304 // convert color format and return average luminance
305 auto color = SkColor4f::FromBytes_RGBA(pixel[0]).toSkColor();
306 return Drawing::Color::ColorQuadGetR(color) * 0.2126f + Drawing::Color::ColorQuadGetG(color) * 0.7152f +
307 Drawing::Color::ColorQuadGetB(color) * 0.0722f;
308 }
309
RunColorPickerTaskBackground(const BufferDrawParam & param)310 void RSPointerRenderManager::RunColorPickerTaskBackground(const BufferDrawParam& param)
311 {
312 #ifdef RS_ENABLE_UNI_RENDER
313 std::lock_guard<std::mutex> locker(mtx_);
314 std::shared_ptr<Drawing::Image> image;
315 auto context = RSBackgroundThread::Instance().GetShareGPUContext().get();
316 if (backendTexturePre_.IsValid()) {
317 image = std::make_shared<Drawing::Image>();
318 SharedTextureContext* sharedContext = new SharedTextureContext(image_);
319 bool ret = image->BuildFromTexture(*context, backendTexturePre_.GetTextureInfo(),
320 Drawing::TextureOrigin::BOTTOM_LEFT, bitmapFormat_, nullptr,
321 SKResourceManager::DeleteSharedTextureContext, sharedContext);
322 if (!ret) {
323 RS_LOGE("RSPointerRenderManager::RunColorPickerTaskBackground BuildFromTexture failed.");
324 return;
325 }
326 } else {
327 image = GetIntersectImageByLayer(param);
328 }
329 if (image == nullptr || !image->IsValid(context)) {
330 RS_LOGE("RSPointerRenderManager::RunColorPickerTaskBackground image not valid.");
331 return;
332 }
333
334 luminance_ = CalcAverageLuminance(image);
335
336 CallPointerLuminanceChange(luminance_);
337 #endif
338 }
339
RunColorPickerTask()340 void RSPointerRenderManager::RunColorPickerTask()
341 {
342 if (!image_ && (target_ == nullptr || rect_.IsEmpty())) {
343 ROSEN_LOGE("RSPointerRenderManager::RunColorPickerTask failed for null target or rect is empty!");
344 return;
345 }
346
347 BufferDrawParam param;
348 if (!image_) {
349 param = RSUniRenderUtil::CreateLayerBufferDrawParam(target_, forceCPU_);
350 }
351 RSBackgroundThread::Instance().PostTask([this, param] () {
352 taskDoing_ = true;
353 this->RunColorPickerTaskBackground(param);
354 taskDoing_ = false;
355 backendTexturePre_ = backendTexture_;
356 backendTexture_ = Drawing::BackendTexture(false);
357 image_ = nullptr;
358 });
359 }
360
GetIntersectImageByLayer(const BufferDrawParam & param)361 std::shared_ptr<Drawing::Image> RSPointerRenderManager::GetIntersectImageByLayer(const BufferDrawParam& param)
362 {
363 #ifdef RS_ENABLE_UNI_RENDER
364 Drawing::RectI imgCutRect = Drawing::RectI(rect_.GetLeft(), rect_.GetTop(), rect_.GetRight(), rect_.GetBottom());
365 auto context = RSBackgroundThread::Instance().GetShareGPUContext();
366 if (context == nullptr) {
367 RS_LOGE("RSPointerRenderManager::GetIntersectImageByLayer context is nullptr.");
368 return nullptr;
369 }
370 if (param.buffer == nullptr) {
371 ROSEN_LOGE("RSPointerRenderManager::GetIntersectImageByLayer param buffer is nullptr");
372 return nullptr;
373 }
374 #ifdef RS_ENABLE_VK
375 if (RSSystemProperties::GetGpuApiType() == GpuApiType::VULKAN
376 || RSSystemProperties::GetGpuApiType() == GpuApiType::DDGR) {
377 return GetIntersectImageFromVK(imgCutRect, context, param);
378 }
379 #endif
380 #if defined(RS_ENABLE_GL) && defined(RS_ENABLE_EGLIMAGE)
381 if (RSSystemProperties::GetGpuApiType() == GpuApiType::OPENGL) {
382 return GetIntersectImageFromGL(imgCutRect, context, param);
383 }
384 #endif
385 #endif
386 return nullptr;
387 }
388
389 #ifdef RS_ENABLE_VK
GetIntersectImageFromVK(Drawing::RectI & imgCutRect,std::shared_ptr<Drawing::GPUContext> & context,const BufferDrawParam & param)390 std::shared_ptr<Drawing::Image> RSPointerRenderManager::GetIntersectImageFromVK(Drawing::RectI& imgCutRect,
391 std::shared_ptr<Drawing::GPUContext>& context, const BufferDrawParam& param)
392 {
393 if (vkImageManager_ == nullptr) {
394 RS_LOGE("RSPointerRenderManager::GetIntersectImageFromVK vkImageManager_ == nullptr");
395 return nullptr;
396 }
397 auto imageCache = vkImageManager_->CreateImageCacheFromBuffer(param.buffer, param.acquireFence);
398 if (imageCache == nullptr) {
399 ROSEN_LOGE("RSPointerRenderManager::GetIntersectImageFromVK imageCache == nullptr!");
400 return nullptr;
401 }
402 auto& backendTexture = imageCache->GetBackendTexture();
403 Drawing::BitmapFormat bitmapFormat = RSBaseRenderUtil::GenerateDrawingBitmapFormat(param.buffer);
404
405 std::shared_ptr<Drawing::Image> layerImage = std::make_shared<Drawing::Image>();
406 if (!layerImage->BuildFromTexture(*context, backendTexture.GetTextureInfo(),
407 Drawing::TextureOrigin::TOP_LEFT, bitmapFormat, nullptr,
408 NativeBufferUtils::DeleteVkImage, imageCache->RefCleanupHelper())) {
409 ROSEN_LOGE("RSPointerRenderManager::GetIntersectImageFromVK image BuildFromTexture failed.");
410 return nullptr;
411 }
412
413 std::shared_ptr<Drawing::Image> cutDownImage = std::make_shared<Drawing::Image>();
414 cutDownImage->BuildSubset(layerImage, imgCutRect, *context);
415 return cutDownImage;
416 }
417 #endif
418
419 #if defined (RS_ENABLE_GL) && defined (RS_ENABLE_EGLIMAGE)
GetIntersectImageFromGL(Drawing::RectI & imgCutRect,std::shared_ptr<Drawing::GPUContext> & context,const BufferDrawParam & param)420 std::shared_ptr<Drawing::Image> RSPointerRenderManager::GetIntersectImageFromGL(Drawing::RectI& imgCutRect,
421 std::shared_ptr<Drawing::GPUContext>& context, const BufferDrawParam& param)
422 {
423 if (eglImageManager_ == nullptr) {
424 RS_LOGE("RSPointerRenderManager::GetIntersectImageFromGL eglImageManager_ == nullptr");
425 return nullptr;
426 }
427 auto eglTextureId = eglImageManager_->MapEglImageFromSurfaceBuffer(param.buffer,
428 param.acquireFence, param.threadIndex);
429 if (eglTextureId == 0) {
430 RS_LOGE("RSPointerRenderManager::GetIntersectImageFromGL invalid texture ID");
431 return nullptr;
432 }
433
434 Drawing::BitmapFormat bitmapFormat = RSBaseRenderUtil::GenerateDrawingBitmapFormat(param.buffer);
435 Drawing::TextureInfo externalTextureInfo;
436 externalTextureInfo.SetWidth(param.buffer->GetSurfaceBufferWidth());
437 externalTextureInfo.SetHeight(param.buffer->GetSurfaceBufferHeight());
438 externalTextureInfo.SetIsMipMapped(false);
439 externalTextureInfo.SetTarget(GL_TEXTURE_EXTERNAL_OES);
440 externalTextureInfo.SetID(eglTextureId);
441 auto glType = GR_GL_RGBA8;
442 auto pixelFmt = param.buffer->GetFormat();
443 if (pixelFmt == GRAPHIC_PIXEL_FMT_BGRA_8888) {
444 glType = GR_GL_BGRA8;
445 } else if (pixelFmt == GRAPHIC_PIXEL_FMT_YCBCR_P010 || pixelFmt == GRAPHIC_PIXEL_FMT_YCRCB_P010) {
446 glType = GL_RGB10_A2;
447 }
448 externalTextureInfo.SetFormat(glType);
449
450 std::shared_ptr<Drawing::Image> layerImage = std::make_shared<Drawing::Image>();
451 if (!layerImage->BuildFromTexture(*context, externalTextureInfo,
452 Drawing::TextureOrigin::TOP_LEFT, bitmapFormat, nullptr)) {
453 RS_LOGE("RSPointerRenderManager::GetIntersectImageFromGL image BuildFromTexture failed");
454 return nullptr;
455 }
456
457 std::shared_ptr<Drawing::Image> cutDownImage = std::make_shared<Drawing::Image>();
458 cutDownImage->BuildSubset(layerImage, imgCutRect, *context);
459 Drawing::ImageInfo info = Drawing::ImageInfo(imgCutRect.GetWidth(), imgCutRect.GetHeight(),
460 Drawing::COLORTYPE_RGBA_8888, Drawing::ALPHATYPE_PREMUL);
461
462 std::shared_ptr<Drawing::Surface> surface = Drawing::Surface::MakeRenderTarget(context.get(), false, info);
463 if (surface == nullptr) {
464 RS_LOGE("RSPointerRenderManager::GetIntersectImageFromGL MakeRenderTarget failed.");
465 return nullptr;
466 }
467 auto drawCanvas = std::make_shared<RSPaintFilterCanvas>(surface.get());
468 drawCanvas->DrawImage(*cutDownImage, 0.f, 0.f, Drawing::SamplingOptions());
469 surface->FlushAndSubmit(true);
470 return surface.get()->GetImageSnapshot();
471 }
472 #endif
473 } // namespace Rosen
474 } // namespace OHOS