From 0d3b3e81390bdff67f260596253c9de16c84e9ca Mon Sep 17 00:00:00 2001 From: qiuguohua Date: Mon, 18 Nov 2024 14:46:27 +0800 Subject: [PATCH] Fix some bugs in the ark engine Fix some issues with jsvm --- cocos/rendering/legacy/index.jsb.ts | 4 +- .../cocos/audio/android/AudioEngine-inl.cpp | 2 +- .../audio/android/AudioMixerController.cpp | 15 +- .../audio/android/AudioPlayerProvider.cpp | 4 +- native/cocos/audio/android/cutils/log.h | 2 +- .../cocos/bindings/jswrapper/jsvm/Object.cpp | 61 +++-- .../cocos/bindings/jswrapper/napi/Object.cpp | 8 - .../bindings/manual/jsb_cocos_manual.cpp | 8 +- .../platform/openharmony/napi/NapiHelper.cpp | 1 + platforms/native/engine/jsb-fs-utils.js | 29 ++- .../entry/src/main/ets/cocos/game.ts | 4 +- .../cocos/oh-adapter/sys-ability-polyfill.js | 221 ++++++++++++++++++ 12 files changed, 292 insertions(+), 67 deletions(-) create mode 100644 templates/openharmony/entry/src/main/ets/cocos/oh-adapter/sys-ability-polyfill.js diff --git a/cocos/rendering/legacy/index.jsb.ts b/cocos/rendering/legacy/index.jsb.ts index 13e6f932f87..5419a9ad779 100644 --- a/cocos/rendering/legacy/index.jsb.ts +++ b/cocos/rendering/legacy/index.jsb.ts @@ -53,7 +53,9 @@ import { RenderTexture } from '../../asset/assets/render-texture'; export function createDefaultPipeline (): ForwardPipeline { const rppl = new ForwardPipeline(); - //rppl.initialize({ flows: [] }); + if (!window.oh) { + rppl.initialize({ flows: [] }); + } return rppl; } export const ForwardPipeline: typeof NrForwardPipeline = nr.ForwardPipeline; diff --git a/native/cocos/audio/android/AudioEngine-inl.cpp b/native/cocos/audio/android/AudioEngine-inl.cpp index 52a6c5ddbfc..2a2d8b27bdc 100644 --- a/native/cocos/audio/android/AudioEngine-inl.cpp +++ b/native/cocos/audio/android/AudioEngine-inl.cpp @@ -208,7 +208,7 @@ bool AudioEngineImpl::init() { _audioPlayerProvider = ccnew AudioPlayerProvider(_engineEngine, _outputMixObject, outputSampleRate, bufferSizeInFrames, fdGetter, &gCallerThreadUtils); #elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY - _audioPlayerProvider = new AudioPlayerProvider(_engineEngine, outputSampleRate, fdGetter, &gCallerThreadUtils); + _audioPlayerProvider = ccnew AudioPlayerProvider(_engineEngine, outputSampleRate, fdGetter, &gCallerThreadUtils); #endif ret = true; } while (false); diff --git a/native/cocos/audio/android/AudioMixerController.cpp b/native/cocos/audio/android/AudioMixerController.cpp index d54634ac417..2c11fc2849c 100644 --- a/native/cocos/audio/android/AudioMixerController.cpp +++ b/native/cocos/audio/android/AudioMixerController.cpp @@ -38,7 +38,7 @@ AudioMixerController::AudioMixerController(int bufferSizeInFrames, int sampleRat : _bufferSizeInFrames(bufferSizeInFrames), _sampleRate(sampleRate), _channelCount(channelCount), _mixer(nullptr), _isPaused(false), _isMixingFrame(false) { ALOGV("In the constructor of AudioMixerController!"); - _mixingBuffer.size = (size_t)bufferSizeInFrames * 2 * channelCount; + _mixingBuffer.size = static_cast(bufferSizeInFrames * 2 * channelCount); // Don't use posix_memalign since it was added from API 16, it will crash on Android 2.3 // Therefore, for a workaround, we uses memalign here. _mixingBuffer.buf = memalign(32, _mixingBuffer.size); @@ -82,7 +82,7 @@ void AudioMixerController::updateBufferSize(int bufferSize) { } bool AudioMixerController::init(int bufferSizeInFrames) { - _mixingBuffer.size = (size_t)bufferSizeInFrames * 2 * _channelCount; + _mixingBuffer.size = static_cast(bufferSizeInFrames * 2 * _channelCount); memset(_mixingBuffer.buf, 0, _mixingBuffer.size); _bufferSizeInFrames = bufferSizeInFrames; _mixer = ccnew AudioMixer(_bufferSizeInFrames, _sampleRate); @@ -113,8 +113,9 @@ static void removeItemFromVector(ccstd::vector &v, T item) { } void AudioMixerController::initTrack(Track *track, ccstd::vector &tracksToRemove) { - if (track->isInitialized()) + if (track->isInitialized()) { return; + } uint32_t channelMask = audio_channel_out_mask_from_count(2); int32_t name = _mixer->getTrackName(channelMask, AUDIO_FORMAT_PCM_16_BIT, @@ -131,22 +132,22 @@ void AudioMixerController::initTrack(Track *track, ccstd::vector &track name, AudioMixer::TRACK, AudioMixer::MIXER_FORMAT, - (void *)(uintptr_t)AUDIO_FORMAT_PCM_16_BIT); + reinterpret_cast(static_cast(AUDIO_FORMAT_PCM_16_BIT))); _mixer->setParameter( name, AudioMixer::TRACK, AudioMixer::FORMAT, - (void *)(uintptr_t)AUDIO_FORMAT_PCM_16_BIT); + reinterpret_cast(static_cast(AUDIO_FORMAT_PCM_16_BIT))); _mixer->setParameter( name, AudioMixer::TRACK, AudioMixer::MIXER_CHANNEL_MASK, - (void *)(uintptr_t)channelMask); + reinterpret_cast(static_cast(channelMask))); _mixer->setParameter( name, AudioMixer::TRACK, AudioMixer::CHANNEL_MASK, - (void *)(uintptr_t)channelMask); + reinterpret_cast(static_cast(channelMask))); track->setName(name); _mixer->enable(name); diff --git a/native/cocos/audio/android/AudioPlayerProvider.cpp b/native/cocos/audio/android/AudioPlayerProvider.cpp index 3f5f05737bf..1e929ca0313 100644 --- a/native/cocos/audio/android/AudioPlayerProvider.cpp +++ b/native/cocos/audio/android/AudioPlayerProvider.cpp @@ -113,8 +113,8 @@ AudioPlayerProvider::AudioPlayerProvider(SLEngineItf engineItf, int deviceSample _callerThreadUtils(callerThreadUtils), _pcmAudioService(nullptr), _mixController(nullptr), _threadPool(LegacyThreadPool::newCachedThreadPool(1, 8, 5, 2, 2)) { - _mixController = new AudioMixerController(_deviceSampleRate, 2); - _pcmAudioService = new PcmAudioService(); + _mixController = ccnew AudioMixerController(_deviceSampleRate, 2); + _pcmAudioService = ccnew PcmAudioService(); _pcmAudioService->init(_mixController, 2, deviceSampleRate, &_bufferSizeInFrames); _mixController->init(_bufferSizeInFrames); ALOG_ASSERT(callerThreadUtils != nullptr, "Caller thread utils parameter should not be nullptr!"); diff --git a/native/cocos/audio/android/cutils/log.h b/native/cocos/audio/android/cutils/log.h index 32e2013aab2..fa32d12febd 100644 --- a/native/cocos/audio/android/cutils/log.h +++ b/native/cocos/audio/android/cutils/log.h @@ -428,7 +428,7 @@ extern "C" { #define LOG_ALWAYS_FATAL(...) \ (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__))) #elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY - #define LOG_ALWAYS_FATAL(...) ((void) OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__)) + #define LOG_ALWAYS_FATAL(...) ((void) OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__)) #endif #endif diff --git a/native/cocos/bindings/jswrapper/jsvm/Object.cpp b/native/cocos/bindings/jswrapper/jsvm/Object.cpp index 58193f34572..02560a20aa1 100644 --- a/native/cocos/bindings/jswrapper/jsvm/Object.cpp +++ b/native/cocos/bindings/jswrapper/jsvm/Object.cpp @@ -277,10 +277,6 @@ Object* Object::createTypedArray(Object::TypedArrayType type, const void* data, return nullptr; } - if (type == TypedArrayType::UINT8_CLAMPED) { - SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); - return nullptr; - } JSVM_TypedarrayType jsvmType; JSVM_Value outputBuffer; void* outputPtr = nullptr; @@ -298,6 +294,10 @@ Object* Object::createTypedArray(Object::TypedArrayType type, const void* data, jsvmType = JSVM_UINT8_ARRAY; sizeOfEle = 1; break; + case TypedArrayType::UINT8_CLAMPED: + jsvmType = JSVM_UINT8_CLAMPED_ARRAY; + sizeOfEle = 1; + break; case TypedArrayType::INT16: jsvmType = JSVM_INT16_ARRAY; sizeOfEle = 2; @@ -364,10 +364,6 @@ Object* Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *ob return nullptr; } - if (type == TypedArrayType::UINT8_CLAMPED) { - SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); - return nullptr; - } assert(obj->isArrayBuffer()); JSVM_Status status; @@ -425,30 +421,23 @@ Object* Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *ob } Object* Object::createExternalArrayBufferObject(void* contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void* freeUserData) { - // JSVM_Status status; - // JSVM_Value result; - // if (freeFunc) { - // struct ExternalArrayBufferCallbackParams* param = new (struct ExternalArrayBufferCallbackParams); - // param->func = freeFunc; - // param->contents = contents; - // param->byteLength = byteLength; - // param->userData = freeUserData; - // NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_external_arraybuffer( - // ScriptEngine::getEnv(), contents, byteLength, [](napi_env env, void* finalize_data, void* finalize_hint) { - // if (finalize_hint) { - // struct ExternalArrayBufferCallbackParams* param = reinterpret_cast(finalize_hint); - // param->func(param->contents, param->byteLength, param->userData); - // delete param; - // } - // }, - // reinterpret_cast(param), &result)); - // } else { - // NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_external_arraybuffer(ScriptEngine::getEnv(), contents, byteLength, nullptr, freeUserData, &result)); - // } - - // Object* obj = Object::_createJSObject(ScriptEngine::getEnv(), result, nullptr); - // return obj; - return nullptr; + JSVM_Status status; + JSVM_Value result; + if (freeFunc) { + struct ExternalArrayBufferCallbackParams* param = new (struct ExternalArrayBufferCallbackParams); + param->func = freeFunc; + param->contents = contents; + param->byteLength = byteLength; + param->userData = freeUserData; + NODE_API_CALL(status, ScriptEngine::getEnv(), OH_JSVM_CreateArraybuffer(ScriptEngine::getEnv(), byteLength, &contents, &result)); + param->func(param->contents, param->byteLength, param->userData); + delete param; + } else { + NODE_API_CALL(status, ScriptEngine::getEnv(), OH_JSVM_CreateArraybuffer(ScriptEngine::getEnv(), byteLength, &contents, &result)); + } + + Object* obj = Object::_createJSObject(ScriptEngine::getEnv(), result, nullptr); + return obj; } @@ -718,8 +707,12 @@ void Object::weakCallback(JSVM_Env env, void* nativeObject, void* finalizeHint / if (nativeObject == nullptr) { return; } - void *rawPtr = reinterpret_cast(nativeObject)->_privateData; - Object* seObj = reinterpret_cast(nativeObject); + void *rawPtr = reinterpret_cast(finalizeHint)->_privateData; + Object* seObj = reinterpret_cast(finalizeHint); + Object* rawPtrObj = reinterpret_cast(rawPtr); + if(rawPtrObj->getRefCount() == 0) { + return; + } if (seObj->_onCleaingPrivateData) { //called by cleanPrivateData, not release seObj; return; } diff --git a/native/cocos/bindings/jswrapper/napi/Object.cpp b/native/cocos/bindings/jswrapper/napi/Object.cpp index 713664920b6..732d1403570 100644 --- a/native/cocos/bindings/jswrapper/napi/Object.cpp +++ b/native/cocos/bindings/jswrapper/napi/Object.cpp @@ -208,10 +208,6 @@ Object* Object::createTypedArray(Object::TypedArrayType type, const void* data, return nullptr; } - if (type == TypedArrayType::UINT8_CLAMPED) { - SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); - return nullptr; - } napi_typedarray_type napiType; napi_value outputBuffer; void* outputPtr = nullptr; @@ -291,10 +287,6 @@ Object* Object::createTypedArrayWithBuffer(TypedArrayType type, const Object* ob return nullptr; } - if (type == TypedArrayType::UINT8_CLAMPED) { - SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); - return nullptr; - } CC_ASSERT(obj->isArrayBuffer()); napi_status status; diff --git a/native/cocos/bindings/manual/jsb_cocos_manual.cpp b/native/cocos/bindings/manual/jsb_cocos_manual.cpp index ec38981e789..21b8116f78c 100644 --- a/native/cocos/bindings/manual/jsb_cocos_manual.cpp +++ b/native/cocos/bindings/manual/jsb_cocos_manual.cpp @@ -738,19 +738,21 @@ static bool js_readFile_doJob(const ccstd::string &fullPath, typename ReadFileDo return false; } - // TODO(cjh): OpenHarmony NAPI support -#if SCRIPT_ENGINE_TYPE != SCRIPT_ENGINE_NAPI + if constexpr (std::is_same_v && isJson) { +// TODO(cjh): OpenHarmony NAPI support +#if SCRIPT_ENGINE_TYPE != SCRIPT_ENGINE_NAPI auto u16str = std::make_shared(); if (!cc::StringUtils::UTF8ToUTF16(*content, *u16str)) { CC_LOG_ERROR("UTF8ToUTF16 failed, file: %s", fullPath.c_str()); return false; } outValue = u16str; +#endif } else { outValue = content; } -#endif + return true; } diff --git a/native/cocos/platform/openharmony/napi/NapiHelper.cpp b/native/cocos/platform/openharmony/napi/NapiHelper.cpp index de94e64de13..bd55b6c8fd8 100644 --- a/native/cocos/platform/openharmony/napi/NapiHelper.cpp +++ b/native/cocos/platform/openharmony/napi/NapiHelper.cpp @@ -52,6 +52,7 @@ enum ContextType { ENGINE_UTILS, EDITBOX_UTILS, WEBVIEW_UTILS, + SYSTEM_UTILS, DISPLAY_UTILS, UV_ASYNC_SEND, VIDEO_UTILS diff --git a/platforms/native/engine/jsb-fs-utils.js b/platforms/native/engine/jsb-fs-utils.js index cfd32680925..549c839d6ee 100644 --- a/platforms/native/engine/jsb-fs-utils.js +++ b/platforms/native/engine/jsb-fs-utils.js @@ -176,13 +176,28 @@ const fsUtils = { }, readJson (filePath, onComplete) { - fs.readJsonFile(filePath, (err, jsonObj) => { - if (err) { - cc.warn(`Read json failed: path: ${filePath} message: ${err}`); - err = new Error(err); - } - onComplete && onComplete(err, jsonObj); - }); + if (window.oh && window.scriptEngineType === 'napi') { + fsUtils.readFile(filePath, 'utf8', (err, text) => { + let out = null; + if (!err) { + try { + out = JSON.parse(text); + } catch (e) { + cc.warn(`Read json failed: path: ${filePath} message: ${e.message}`); + err = new Error(e.message); + } + } + onComplete && onComplete(err, out); + }); + } else { + fs.readJsonFile(filePath, (err, jsonObj) => { + if (err) { + cc.warn(`Read json failed: path: ${filePath} message: ${err}`); + err = new Error(err); + } + onComplete && onComplete(err, jsonObj); + }); + } }, readJsonSync (path) { diff --git a/templates/openharmony/entry/src/main/ets/cocos/game.ts b/templates/openharmony/entry/src/main/ets/cocos/game.ts index a65faf66eb4..0101dd4e84e 100644 --- a/templates/openharmony/entry/src/main/ets/cocos/game.ts +++ b/templates/openharmony/entry/src/main/ets/cocos/game.ts @@ -76,8 +76,7 @@ export function launchEngine (): Promise { window.global = window; // @ts-ignore window.oh = window.oh || {}; - return import('./jsb-adapter/sys-ability-polyfill.js').then(({ systemReady }) => { - return systemReady().then(() => { + return import('./oh-adapter/sys-ability-polyfill.js').then(() => { // @ts-ignore window.oh.loadModule = loadModule; return import('./jsb-adapter/web-adapter.js').then(() => { @@ -110,7 +109,6 @@ export function launchEngine (): Promise { }); }); }); - }); }).catch((e: any) => { console.error('imported failed', e.message, e.stack) }); diff --git a/templates/openharmony/entry/src/main/ets/cocos/oh-adapter/sys-ability-polyfill.js b/templates/openharmony/entry/src/main/ets/cocos/oh-adapter/sys-ability-polyfill.js new file mode 100644 index 00000000000..4d252561406 --- /dev/null +++ b/templates/openharmony/entry/src/main/ets/cocos/oh-adapter/sys-ability-polyfill.js @@ -0,0 +1,221 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +import display from '@ohos.display'; +import I18n from '@ohos.i18n'; +import deviceInfo from '@ohos.deviceInfo'; +import batteryInfo from '@ohos.batteryInfo'; +import sensor from '@ohos.sensor'; +import connection from '@ohos.net.connection' +import vibrator from '@ohos.vibrator'; +import process from '@ohos.process'; +import { ContextType } from "../../common/Constants" +import cocos from "libcocos.so"; + +const displayUtils = cocos.getContext(ContextType.DISPLAY_UTILS); + +let pro = new process.ProcessManager(); +let cutout = { + left: 0, + top: 0, + width: 0, + height: 0 +}; + +globalThis.getSystemLanguage = function () { + return I18n.System.getSystemLanguage(); +} + +globalThis.getOSFullName = function () { + return deviceInfo.osFullName; +} + +globalThis.getDeviceModel = function () { + return deviceInfo.productModel; +} + +globalThis.getBatteryLevel = function () { + return batteryInfo.batterySOC; +} + +globalThis.getDPI = function () { + var displayClass = display.getDefaultDisplaySync(); + return displayClass.densityDPI; +} + +globalThis.getPixelRation = function () { + var displayClass = display.getDefaultDisplaySync(); + return displayClass.densityPixels; +} + +let onDisplayChange = (data) => { + // Monitor changes in screen orientation. + displayUtils.onDisplayChange(globalThis.getDeviceOrientation()); + + // update screen cutout info + globalThis.initScreenInfo(); +} + +try { + display.on("change", onDisplayChange); +} catch (exception) { + console.log('Failed to register callback. Code: ' + JSON.stringify(exception)); +} + +globalThis.getDeviceOrientation = function () { + var displayClass = display.getDefaultDisplaySync(); + return displayClass.rotation; +} + +function radiansToDegrees(radians) { + var pi = Math.PI; + return radians * (180/pi); +} + +let sDeviceMotionValues = []; +try { + sensor.on(sensor.SensorId.ACCELEROMETER, function (data) { + sDeviceMotionValues[0] = data.x; + sDeviceMotionValues[1] = data.y; + sDeviceMotionValues[2] = -data.z; + }, + { interval: 200000000 } + ); +} catch (err) { + sDeviceMotionValues[0] = 0; + sDeviceMotionValues[1] = 0; + sDeviceMotionValues[2] = 0; +} + +try { + sensor.on(sensor.SensorId.LINEAR_ACCELEROMETER, function(data){ + sDeviceMotionValues[3] = data.x; + sDeviceMotionValues[4] = data.y; + sDeviceMotionValues[5] = data.z; + }, + {interval: 200000000} + ); +} catch (err) { + sDeviceMotionValues[3] = 0; + sDeviceMotionValues[4] = 0; + sDeviceMotionValues[5] = 0; +} +try { + sensor.on(sensor.SensorId.GYROSCOPE, function(data){ + sDeviceMotionValues[6] = radiansToDegrees(data.x); + sDeviceMotionValues[7] = radiansToDegrees(data.y); + sDeviceMotionValues[8] = radiansToDegrees(data.z); + }, + {interval: 200000000} + ); +} catch (err) { + sDeviceMotionValues[6] = 0; + sDeviceMotionValues[7] = 0; + sDeviceMotionValues[8] = 0; +} + +globalThis.getDeviceMotionValue = function () { + return sDeviceMotionValues; +} + + +globalThis.getNetworkType = function () { + let netHandle = connection.getDefaultNetSync(); + if(netHandle && netHandle.netId != 0) { + let result = connection.getNetCapabilitiesSync(netHandle); + if (result && result.bearerTypes) { + return result.bearerTypes[0]; + } + } + return -1; +} + +globalThis.vibrate = function (duration) { + console.log('begin to vibrate, duration is.' + duration); + try { + vibrator.startVibration({ + type: 'time', + duration: duration * 1000 + }, { + id: 0, + usage: 'alarm' + }, (error) => { + if (error) { + console.error('vibrate fail, error.code: ' + error.code + 'error.message: ', + error.message); + return error.code; + } + console.log('Vibration start sucessful.'); + return 0; + }); + } catch (err) { + console.error('errCode: ' + err.code + ' ,msg: ' + err.message); + } +} + +globalThis.terminateProcess = function () { + pro.exit(0); +} + +globalThis.initScreenInfo = function () { + display.getDefaultDisplaySync().getCutoutInfo().then((data) => { + if (data.boundingRects.length == 0) { + return; + } + const rc = data.boundingRects[0]; + cutout.left = rc.left; + cutout.top = rc.top; + cutout.width = rc.width; + cutout.height = rc.height; + }).catch((err) => { + console.log("get cutout info error!"); + }); +}; +globalThis.initScreenInfo(); + +globalThis.getCutoutWidth = function () { + if(!cutout.width) { + return 0; + } + + let disPlayWidth = display.getDefaultDisplaySync().width; + if(cutout.left + cutout.width > disPlayWidth - cutout.left) { + return disPlayWidth - cutout.left; + } + return cutout.left + cutout.width; +} + +globalThis.getCutoutHeight = function () { + if(!cutout.height) { + return 0; + } + + let orientation = globalThis.getDeviceOrientation(); + if (orientation == display.Orientation.PORTRAIT) { + return cutout.top + cutout.height; + } else if(orientation == display.Orientation.PORTRAIT_INVERTED) { + let displayHeight = display.getDefaultDisplaySync().height; + return displayHeight - cutout.top; + } + return 0; +}