diff --git a/.github/workflows/native-compile-platforms.yml b/.github/workflows/native-compile-platforms.yml index eaf7a4c84bc..d9ac157c3d2 100644 --- a/.github/workflows/native-compile-platforms.yml +++ b/.github/workflows/native-compile-platforms.yml @@ -327,7 +327,7 @@ jobs: NATIVE_ROOT=$GITHUB_WORKSPACE/native echo "Compiling Openharmony ... " - cd $GITHUB_WORKSPACE/templates/openharmony + cd $GITHUB_WORKSPACE/templates/harmonyos-next echo "message(STATUS \"hook before \${CC_TARGET_NAME}\")" >> Pre-Sample.cmake echo "message(STATUS \"hook after \${CC_TARGET_NAME}\")" >> Post-Sample.cmake @@ -350,7 +350,7 @@ jobs: mkdir -p build-oh/assets - ASSET_DIR=$GITHUB_WORKSPACE/templates/openharmony/build-oh + ASSET_DIR=$GITHUB_WORKSPACE/templates/harmonyos-next/build-oh echo "=============== HACK ./entry/build-profile.json5 ===============" sed -i "s@[^-]DRES_DIR[^=]@$ASSET_DIR@g" ./entry/build-profile.json5 diff --git a/@types/jsb.d.ts b/@types/jsb.d.ts index d53fb8658a2..418b3ec8394 100644 --- a/@types/jsb.d.ts +++ b/@types/jsb.d.ts @@ -357,6 +357,485 @@ declare namespace jsb { readonly thermalStatusNormalized: number; onThermalStatusChanged?: (previousStatus: number, newStatus: number, statusMin: number, statusMax: number) => void; } | undefined; + + /** + * @en Params containing the response code and the debug message from In-app Billing API response. + * @zh 包含应用内结算 API 响应代码和调试信息的参数 + */ + export interface BillingResult { + /** + * @en Debug message returned in In-app Billing API calls. + * @zh 应用内结算 API 调用中返回的调试消息。 + */ + readonly debugMessage: string; + /** + * @en Code returned in In-app Billing API calls. + * @zh 应用内结算 API 调用中返回的响应代码。 + */ + readonly responseCode: string; + readonly toStr: string; + } + + /** + * @en Represents the offer details to buy an one-time purchase product. + * @zh 代表一次性购买产品的报价详情。 + */ + export interface OneTimePurchaseOfferDetails { + /** + * @en The price for the payment in micro-units, where 1,000,000 micro-units equal one unit of the currency. + * @zh 以微单位返回支付价格,其中 1,000,000 个微单位等于 1 个货币单位。 + */ + readonly priceAmountMicros: number; + /** + * @en Formatted price for the payment, including its currency sign. + * @zh 支付的格式化价格,包括其货币单位。 + */ + readonly formattedPrice: string; + /** + * @en ISO 4217 currency code for price. + * @zh 价格的 ISO 4217 货币代码。 + */ + readonly priceCurrencyCode: string; + } + + /** + * @en Represents additional details of an installment subscription plan. + * @zh 表示分期付款订阅计划的附加详细信息。 + */ + export interface InstallmentPlanDetails { + /** + * @en Committed payments count after a user signs up for this subscription plan. + * @zh 用户注册此订阅计划后承诺的付款数量。 + */ + readonly installmentPlanCommitmentPaymentsCount: number; + /** + * @en Subsequent committed payments count after this subscription plan renews. + * @zh 此订阅计划续订后的后续承诺付款数量。 + */ + readonly subsequentInstallmentPlanCommitmentPaymentsCount: number; + } + + /** + * @en Represents a pricing phase, describing how a user pays at a point in time. + * @zh 表示定价阶段,描述用户在某个时间点如何付款。 + */ + export interface PricingPhase { + /** + * @en Number of cycles for which the billing period is applied. + * @zh 计费周期适用的周期数。 + */ + readonly billingCycleCount: number; + /** + * @en The price for the payment cycle in micro-units, where 1,000,000 micro-units equal one unit of the currency. + * @zh 微单位付款周期的价格,其中 1,000,000 个微单位等于 1 个货币单位。 + */ + readonly priceAmountMicros: number; + /** + * @en RecurrenceMode for the pricing phase. + * @zh 定价阶段的RecurrenceMode。 + */ + readonly recurrenceMode: number; + /** + * @en Billing period for which the given price applies, specified in ISO 8601 format. + * @zh 给定价格适用的计费期,以 ISO 8601 格式指定。 + */ + readonly billingPeriod: string; + /** + * @en Formatted price for the payment cycle, including its currency sign. + * @zh 付款周期的格式化价格,包括其货币符号。 + */ + readonly formattedPrice: string; + /** + * @en Returns ISO 4217 currency code for price. + * @zh 返回价格的 ISO 4217 货币代码。 + */ + readonly priceCurrencyCode: string; + } + + /** + * @en Represents the available purchase plans to buy a subscription product. + * @zh 代表一次性购买产品的报价详情。 + */ + export interface SubscriptionOfferDetails { + /** + * @en The base plan id associated with the subscription product. + * @zh 与订阅产品相关的基本计划 ID。 + */ + readonly basePlanId: string; + /** + * @en The offer id associated with the subscription product. + * @zh 与订阅产品相关的优惠 ID。 + */ + readonly offerId: string; + /** + * @en The offer tags associated with this Subscription Offer. + * @zh 与此订阅优惠相关的优惠标签。 + */ + readonly offerTags: string[]; + /** + * @en The offer token required to pass in launchBillingFlow to purchase the subscription product with these pricing phases. + * @zh 在 launchBillingFlow 中传递以使用这些定价阶段购买订阅产品所需的优惠令牌。 + */ + readonly offerToken: string; + /** + * @en The pricing phases for the subscription product. + * @zh 订阅产品的定价区间。 + */ + readonly pricingPhaseList: PricingPhase[]; + /** + * @en The additional details of an installment plan. + * @zh 分期付款计划的附加详细信息。 + */ + readonly installmentPlanDetails: InstallmentPlanDetails; + } + /** + * @en Represents the details of a one time or subscription product. + * @zh 代表一次性或订阅产品的详细信息。 + */ + export interface ProductDetails { + /** + * @en Hash code + * @zh hash值 + */ + readonly hashCode: number; + /** + * @en The description of the product. + * @zh 产品的描述。 + */ + readonly description: string; + /** + * @en The name of the product being sold. + * @zh 所售产品的名称。 + */ + readonly name: string; + /** + * @en The product's Id. + * @zh 产品的 ID。 + */ + readonly productId: string; + /** + * @en The ProductType of the product. + * @zh ProductType产品的。 + */ + readonly productType: string; + /** + * @en The title of the product being sold. + * @zh 所售产品的标题。 + */ + readonly title: string; + /** + * @en To string + * @zh 转换成字符串 + */ + readonly toStr: string; + /** + * @en The offer details of an one-time purchase product. + * @zh 代表一次性购买产品的报价详情。 + */ + readonly oneTimePurchaseOfferDetails: OneTimePurchaseOfferDetails; + /** + * @en A list containing all available offers to purchase a subscription product. + * @zh 返回包含购买订阅产品的所有可用优惠的列表。 + */ + readonly subscriptionOfferDetails: SubscriptionOfferDetails[]; + } + + /** + * @en Account identifiers that were specified when the purchase was made. + * @zh 购买时指定的帐户标识符。 + */ + export interface AccountIdentifiers { + /** + * @en The obfuscated account id specified in setObfuscatedAccountId. + * @zh 在setObfuscatedAccountId中设置的混淆账户id + */ + readonly obfuscatedAccountId: string; + /** + * @en The obfuscated profile id specified in setObfuscatedProfileId. + * @zh 在setObfuscatedProfileId中设置的混淆profile id + */ + readonly obfuscatedProfileId: string; + } + + /** + * @en Represents a pending change/update to the existing purchase. + * @zh 表示对现有购买的待定更改/更新。 + */ + export interface PendingPurchaseUpdate { + /** + * @en A token that uniquely identifies this pending transaction. + * @zh 唯一标识此待处理交易的令牌。 + */ + readonly purchaseToken: string; + /** + * @en The product ids. + * @zh 产品 ID。 + */ + readonly products: string[]; + } + + /** + * @en Represents an in-app billing purchase. + * @zh 代表应用内billing购买。 + */ + export interface Purchase { + /** + * @en One of PurchaseState indicating the state of the purchase. + * @zh PurchaseState表示购买状态的其中一个值。 + */ + readonly purchaseState: number; + /** + * @en The time the product was purchased, in milliseconds since the epoch (Jan 1, 1970). + * @zh 产品购买的时间,以纪元(1970 年 1 月 1 日)以来的毫秒数表示。 + */ + readonly purchaseTime: number; + /** + * @en Indicates whether the purchase has been acknowledged. + * @zh 表示是否已确认购买。 + */ + readonly isAcknowledged: number; + /** + * @en Indicates whether the subscription renews automatically. + * @zh 指示订阅是否自动续订。 + */ + readonly isAutoRenewing: number; + /** + * @en Hash code + * @zh hash值 + */ + readonly hashCode: number; + + /** + * @en The quantity of the purchased product. + * @zh 购买产品的数量。 + */ + readonly quantity: number; + /** + * @en The payload specified when the purchase was acknowledged or consumed. + * @zh 确认或消费购买时指定的有效负载。 + */ + readonly developerPayload: string; + /** + * @en Returns a unique order identifier for the transaction. + * @zh 交易的唯一订单标识符。 + */ + readonly orderId: string; + /** + * @en Returns a String in JSON format that contains details about the purchase order. + * @zh 包含有关采购订单详细信息的 JSON 格式的字符串。 + */ + readonly originalJson: string; + /** + * @en The application package from which the purchase originated. + * @zh 购买来源的应用程序包。 + */ + readonly packageName: string; + /** + * @en A token that uniquely identifies a purchase for a given item and user pair. + * @zh 唯一标识给定商品和用户对的购买的令牌。 + */ + readonly purchaseToken: string; + /** + * @en String containing the signature of the purchase data that was signed with the private key of the developer. + * @zh 包含使用开发者私钥签名的购买数据签名的字符串。 + */ + readonly signature: string; + /** + * @en To string + * @zh 转换成字符串 + */ + readonly toStr: string; + + /** + * @en Returns account identifiers that were provided when the purchase was made. + * @zh 返回购买时提供的帐户标识符。 + */ + readonly accountIdentifiers: AccountIdentifiers; + /** + * @en The PendingPurchaseUpdate for an uncommitted transaction. + * @zh 返回PendingPurchaseUpdate未提交的事务。 + */ + readonly pendingPurchaseUpdate: PendingPurchaseUpdate; + /** + * @en the product Ids. + * @zh 产品 ID。 + */ + readonly products: string[]; + } + + export interface BillingConfig { + /** + * @en The customer's country code. + * @zh 客户的国家代码。 + */ + readonly countryCode: string; + } + + /** + * @en The details used to report transactions made via alternative billing without user choice to use Google Play Billing. + * @zh 用于报告用户未选择使用 Google Play Billing方式而通过替代Billing方式进行的交易的详细信息。 + */ + export interface AlternativeBillingOnlyReportingDetails { + /** + * @en An external transaction token that can be used to report a transaction made via alternative billing + * without user choice to use Google Play billing. + * @zh 返回一个外部交易令牌,该令牌可用于报告通过替代付款方式进行的交易,而无需用户选择使用 Google Play 付款方式。 + */ + readonly externalTransactionToken: string; + } + + /** + * @en The details used to report transactions made via external offer. + * @zh 用于报告通过外部报价进行的交易的详细信息。 + */ + export interface ExternalOfferReportingDetails { + /** + * @en An external transaction token that can be used to report a transaction made via external offer. + * @zh 可用于报告通过外部报价进行的交易的外部交易令牌。 + */ + readonly externalTransactionToken: string; + } + + /** + * @en Results related to in-app messaging. + * @zh 与应用程序内消息相关的结果。 + */ + export interface InAppMessageResult { + /** + * @en Response code for the in-app messaging API call. + * @zh 应用内消息传递 API 调用的响应代码。 + */ + readonly responseCode: number; + /** + * @en Token that identifies the purchase to be acknowledged, if any. + * @zh 返回标识需要确认的购买的令牌。 + */ + readonly purchaseToken: string; + } + + /** + * @en Main interface for communication between the Google Play library and user application code. + * @zh 产品 ID。 + */ + const googleBilling: { + /** + * @en Starts up BillingClient setup process asynchronously. + * @zh 异步启动 BillingClient 设置过程。 + */ + startConnection(): void; + + /** + * @en Closes the connection and releases all held resources such as service connections. + * @zh 关闭连接并释放所有持有的资源,例如服务连接。 + */ + endConnection(): void; + + /** + * @en Get the current billing client connection state. + * @zh 获取当前计费客户端连接状态。 + */ + getConnectionState(): number; + + /** + * @en Checks if the client is currently connected to the service, so that requests to other methods will succeed. + Returns true if the client is currently connected to the service, false otherwise. + * @zh 检查客户端当前是否连接到服务,以便对其他方法的请求能够成功。 + 如果客户端当前已连接到服务,则返回 true,否则返回 false。 + */ + isReady(): boolean; + + /** + * @en Performs a network query the details of products available for sale in your app. + * @zh 执行网络查询您的应用中可供销售的产品的详细信息。 + */ + queryProductDetailsParams(productId: string[], productType: string): void; + /** + * @en Initiates the billing flow for an in-app purchase or subscription. + * @zh 启动应用内购买或订阅的计费流程。 + */ + launchBillingFlow(productDetails: ProductDetails[], selectedOfferToken: string | null): void; + /** + * @en Consumes a given in-app product. + * @zh 消费指定的应用内产品。 + */ + consumePurchases(purchases: Purchase[]): void; + /** + * @en Returns purchases details for currently owned items bought within your app. + * @zh 返回您应用内当前拥有的购买商品的购买详情。 + */ + queryPurchasesAsync(productType: string): void; + /** + * @en Acknowledges in-app purchases.. + * @zh 确认应用内购买。 + */ + acknowledgePurchase(purchases: Purchase[]): void; + /** + * @en Gets the billing config, which stores configuration used to perform billing operations. + * @zh 获取计费配置,其中存储用于执行计费操作的配置。 + */ + getBillingConfigAsync(): void; + + /** + * @en Creates alternative billing only purchase details that can be used to report a transaction made via alternative billing without user choice to use Google Play billing. + * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play Billing。 + */ + createAlternativeBillingOnlyReportingDetailsAsync(): void; + /** + * @en Checks the availability of offering alternative billing without user choice to use Google Play Billing. + * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play Billing方式。 + */ + isAlternativeBillingOnlyAvailableAsync(): void; + + /** + * @en Creates purchase details that can be used to report a transaction made via external offer. + * @zh 创建可用于报告通过外部报价进行的交易的购买详情。 + */ + createExternalOfferReportingDetailsAsync(): void; + /** + * @en Checks the availability of providing external offer. + * @zh 检查提供外部报价的可用性。 + */ + isExternalOfferAvailableAsync(): void; + + /** + * @en Checks if the specified feature or capability is supported by the Play Store. + * @zh 检查 Play Store 是否支持指定的功能。 + */ + isFeatureSupported(feature: string): BillingResult; + + /** + * @en Shows the alternative billing only information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示仅显示备用计费信息对话框。 + */ + showAlternativeBillingOnlyInformationDialog(): BillingResult; + + /** + * @en Shows the external offer information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示外部优惠信息对话框。 + */ + showExternalOfferInformationDialog(): BillingResult; + + /** + * @en Overlays billing related messages on top of the calling app. + * @zh 在调用应用程序上叠加与计费相关的消息。 + */ + showInAppMessages(): BillingResult; + } | undefined; + export let onBillingSetupFinished: (result: BillingResult) => void | undefined; + export let onBillingServiceDisconnected: () => void | undefined; + export let onProductDetailsResponse: (result: BillingResult, productDetailsList: ProductDetails[]) => void | undefined; + export let onPurchasesUpdated: (result: BillingResult, purchaseList: Purchase[]) => void | undefined; + export let onConsumeResponse: (result: BillingResult, purchaseToken: string) => void | undefined; + export let onAcknowledgePurchaseResponse: (result: BillingResult) => void | undefined; + export let onQueryPurchasesResponse: (result: BillingResult, purchaseList: Purchase[]) => void | undefined; + export let onBillingConfigResponse: (result: BillingResult, config: BillingConfig) => void | undefined; + export let onAlternativeBillingOnlyTokenResponse: (result: BillingResult, details: AlternativeBillingOnlyReportingDetails) => void | undefined; + export let onExternalOfferReportingDetailsResponse: (result: BillingResult, details: ExternalOfferReportingDetails) => void | undefined; + export let onAlternativeBillingOnlyAvailabilityResponse: (result: BillingResult) => void | undefined; + export let onExternalOfferAvailabilityResponse: (result: BillingResult) => void | undefined; + export let onAlternativeBillingOnlyInformationDialogResponse: (result: BillingResult) => void | undefined; + export let onExternalOfferInformationDialogResponse: (result: BillingResult) => void | undefined; + export let onInAppMessageResponse: (result: InAppMessageResult) => void | undefined; } declare namespace ns { diff --git a/cc.config.json b/cc.config.json index 4eae710d317..378729639b5 100644 --- a/cc.config.json +++ b/cc.config.json @@ -159,6 +159,9 @@ "modules": ["tiled-map"], "dependentModules": ["2d"] }, + "vendor": { + "modules": ["vendor-google"] + }, "spine": { "modules": ["spine"], "dependentAssets": [ diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index 1b1ff633089..ec3b93cfc11 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -23,7 +23,6 @@ */ /* eslint-disable @typescript-eslint/no-namespace */ - import type { Color, Vec2 } from '../core'; export * from 'internal:native'; @@ -1436,7 +1435,9 @@ export declare namespace native { */ const adpf: { /** - * @en Provides an estimate of how much thermal headroom the device currently has before hitting severe throttling. The value range is a non-negative float, where 0.0 represents a fixed distance from overheating, 1.0 indicates the device will be severely throttled, and values greater than 1.0 may imply even heavier throttling. + * @en Provides an estimate of how much thermal headroom the device currently has before hitting severe throttling. + * The value range is a non-negative float, where 0.0 represents a fixed distance from overheating, 1.0 indicates + * the device will be severely throttled, and values greater than 1.0 may imply even heavier throttling. * @zh 提供设备在达到严重节流之前当前有多少热余量的估计值。值的范围是非负浮点数,其中0.0表示距离过热的固定距离,1.0表示设备将被严重限制,而大于1.0的值可能表示更重的限制。 * @see https://developer.android.com/ndk/reference/group/thermal#group___thermal_1ga1055f6c8d5910a1904162bea75807314 */ diff --git a/editor/engine-features/render-config.json b/editor/engine-features/render-config.json index b03314b40c1..11fb2949889 100644 --- a/editor/engine-features/render-config.json +++ b/editor/engine-features/render-config.json @@ -312,6 +312,15 @@ "2d" ] }, + "vendor": { + "default": false, + "label": "i18n:ENGINE.features.vendor.label", + "description": "i18n:ENGINE.features.vendor.description", + "enginePlugin": false, + "isNativeModule": true, + "cmakeConfig": "USE_VENDOR", + "hidden": true + }, "spine": { "default": true, "label": "i18n:ENGINE.features.spine.label", diff --git a/editor/engine-features/schema.json b/editor/engine-features/schema.json index 2d3b81a5a37..b27c1ae43c1 100644 --- a/editor/engine-features/schema.json +++ b/editor/engine-features/schema.json @@ -179,6 +179,9 @@ "tiled-map": { "$ref": "#/definitions/IFeatureItem" }, + "vendor": { + "$ref": "#/definitions/IFeatureItem" + }, "tween": { "$ref": "#/definitions/IFeatureItem" }, @@ -365,6 +368,9 @@ "tiled-map": { "$ref": "#/definitions/BaseItem" }, + "vendor": { + "$ref": "#/definitions/BaseItem" + }, "tween": { "$ref": "#/definitions/BaseItem" }, diff --git a/editor/i18n/en/localization.js b/editor/i18n/en/localization.js index 3800451c7d9..bfbb09859e1 100755 --- a/editor/i18n/en/localization.js +++ b/editor/i18n/en/localization.js @@ -1043,6 +1043,10 @@ module.exports = link(mixin({ label: "Tiled Map", description: "Tiled map support.", }, + vendor: { + label: "Vendor", + description: "Vendor support.", + }, spine: { label: "Spine Animation", description: "Spine Animation support.", diff --git a/editor/i18n/zh/localization.js b/editor/i18n/zh/localization.js index 7fd7f56d43f..92675fffe63 100755 --- a/editor/i18n/zh/localization.js +++ b/editor/i18n/zh/localization.js @@ -1021,6 +1021,10 @@ module.exports = link(mixin({ label: "Tiled 地图", description: "Tiled 地图支持。", }, + vendor: { + label: "Vendor", + description: "Vendor 支持.", + }, spine: { label: "Spine 动画", description: "Spine 动画支持。", diff --git a/exports/vendor-google.ts b/exports/vendor-google.ts new file mode 100644 index 00000000000..e6ca3a50a09 --- /dev/null +++ b/exports/vendor-google.ts @@ -0,0 +1,26 @@ +/* + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + https://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. + */ + +export * from '../vendor/google'; diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index 7449558b3c5..2cbbd620e44 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -47,7 +47,8 @@ cc_set_if_undefined(USE_GEOMETRY_RENDERER ON) cc_set_if_undefined(USE_WEBP ON) cc_set_if_undefined(NET_MODE 0) # 0 is client cc_set_if_undefined(USE_REMOTE_LOG OFF) - +cc_set_if_undefined(USE_ADPF OFF) +cc_set_if_undefined(USE_GOOGLE_BILLING OFF) if(ANDROID AND NOT DEFINED USE_CCACHE) if("$ENV{COCOS_USE_CCACHE}" STREQUAL "1") @@ -565,8 +566,6 @@ elseif(ANDROID) cocos/platform/android/AndroidPlatform.cpp cocos/platform/android/AndroidPlatform.h cocos/platform/android/AndroidKeyCodes.cpp - cocos/platform/android/adpf_manager.h - cocos/platform/android/adpf_manager.cpp ) elseif(OPENHARMONY) cocos_source_files( @@ -601,6 +600,13 @@ elseif(IOS) ) endif() +if(USE_ADPF) + cocos_source_files( + cocos/platform/android/adpf_manager.h + cocos/platform/android/adpf_manager.cpp + ) +endif() + ############ platform : Abstract interface cocos_source_files( cocos/platform/interfaces/OSInterface.h @@ -754,6 +760,15 @@ elseif(ANDROID OR OHOS) endif() endif() +if(USE_GOOGLE_BILLING) + cocos_source_files( + vendor/google/billing/GoogleBilling.cpp + vendor/google/billing/GoogleBilling.h + vendor/google/billing/GoogleBillingHelper.cpp + vendor/google/billing/GoogleBillingHelper.h + ) +endif() + if(ANDROID) cocos_source_files( cocos/platform/android/modules/Screen.cpp @@ -761,6 +776,7 @@ if(ANDROID) cocos/platform/android/modules/System.cpp cocos/platform/android/modules/System.h ) + elseif(OPENHARMONY) # Because there will be a definition of OHOS in harmonyos, it is necessary to define the macro of OPENHARMONY. elseif(OHOS) @@ -1178,6 +1194,10 @@ elseif(LINUX) ) endif() +if(USE_GOOGLE_BILLING) + list(APPEND CC_JNI_SRC_FILES ${CWD}/cocos/platform/java/jni/JniGoogleBillingHandler.cpp) +endif() + ##### renderer cocos_source_files( cocos/renderer/core/PassUtils.h @@ -2559,7 +2579,6 @@ cocos_source_files( cocos/bindings/manual/jsb_xmlhttprequest.h cocos/bindings/manual/jsb_pipeline_manual.h cocos/bindings/manual/jsb_pipeline_manual.cpp - cocos/bindings/manual/jsb_adpf.cpp ) if(USE_AUDIO) cocos_source_files( @@ -2583,6 +2602,19 @@ if(USE_WEBSOCKET_SERVER) ) endif() +if(USE_ADPF) + cocos_source_files( + cocos/bindings/manual/jsb_adpf.cpp + ) +endif() + +if(USE_GOOGLE_BILLING) + cocos_source_files( + cocos/bindings/manual/jsb_google_billing.cpp + ) + +endif() + if(ANDROID) cocos_source_files( cocos/bindings/manual/jsb_platform_android.cpp @@ -3114,6 +3146,8 @@ function(cc_apply_definations target) $<$:SCRIPT_ENGINE_TYPE=5> $<$:SCRIPT_ENGINE_TYPE=6> $<$,$>:CC_DEBUG=1> + $,CC_USE_APDF=1,CC_USE_APDF=0> + $,CC_USE_GOOGLE_BILLING=1,CC_USE_GOOGLE_BILLING=0> ) endfunction() diff --git a/native/cocos/bindings/manual/jsb_global.cpp b/native/cocos/bindings/manual/jsb_global.cpp index 1895e868e94..41685fb68eb 100644 --- a/native/cocos/bindings/manual/jsb_global.cpp +++ b/native/cocos/bindings/manual/jsb_global.cpp @@ -1630,7 +1630,9 @@ bool jsb_register_global_variables(se::Object *global) { // NOLINT jsb_register_TextEncoder(global); jsb_register_TextDecoder(global); +#if CC_USE_ADPF jsb_register_ADPF(__jsbObj); +#endif se::ScriptEngine::getInstance()->clearException(); diff --git a/native/cocos/bindings/manual/jsb_google_billing.cpp b/native/cocos/bindings/manual/jsb_google_billing.cpp new file mode 100644 index 00000000000..9321269fbb3 --- /dev/null +++ b/native/cocos/bindings/manual/jsb_google_billing.cpp @@ -0,0 +1,1497 @@ +/**************************************************************************** + Copyright (c) 2024 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 documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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. +*****************************************************************************/ + +#include "application/ApplicationManager.h" +#include "bindings/jswrapper/SeApi.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include "bindings/jswrapper/SeApi.h" + #include "bindings/manual/jsb_conversions.h" + #include "bindings/manual/jsb_global.h" + #include "vendor/google/billing/GoogleBilling.h" +JSB_REGISTER_OBJECT_TYPE(cc::BillingResult); +JSB_REGISTER_OBJECT_TYPE(cc::OneTimePurchaseOfferDetails); +JSB_REGISTER_OBJECT_TYPE(cc::InstallmentPlanDetails); +JSB_REGISTER_OBJECT_TYPE(cc::PricingPhase); +JSB_REGISTER_OBJECT_TYPE(cc::SubscriptionOfferDetails); +JSB_REGISTER_OBJECT_TYPE(cc::ProductDetails); +JSB_REGISTER_OBJECT_TYPE(cc::AccountIdentifiers); +JSB_REGISTER_OBJECT_TYPE(cc::PendingPurchaseUpdate); +JSB_REGISTER_OBJECT_TYPE(cc::Purchase); +JSB_REGISTER_OBJECT_TYPE(cc::BillingConfig); +JSB_REGISTER_OBJECT_TYPE(cc::GoogleBilling); + +#endif + +#if CC_PLATFORM == CC_PLATFORM_ANDROID +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_BillingResult) +static bool js_cc_BillingResult_debugMessage_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::BillingResult* arg1 = (cc::BillingResult*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->debugMessage; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_BillingResult_debugMessage_get) + +static bool js_cc_BillingResult_responseCode_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::BillingResult* arg1 = (cc::BillingResult*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->responseCode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + return true; +} +SE_BIND_PROP_GET(js_cc_BillingResult_responseCode_get) + +static bool js_cc_BillingResult_toString_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::BillingResult* arg1 = (cc::BillingResult*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->toString; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + return true; +} +SE_BIND_PROP_GET(js_cc_BillingResult_toString_get) + +static bool js_delete_cc_BillingResult(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_BillingResult) + +bool js_register_cc_BillingResult(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("BillingResult", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("debugMessage", _SE(js_cc_BillingResult_debugMessage_get), nullptr); + cls->defineProperty("responseCode", _SE(js_cc_BillingResult_responseCode_get), nullptr); + cls->defineProperty("toStr", _SE(js_cc_BillingResult_toString_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_BillingResult)); + + cls->install(); + JSBClassType::registerClass(cls); + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_OneTimePurchaseOfferDetails) + +static bool js_cc_OneTimePurchaseOfferDetails_formattedPrice_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::OneTimePurchaseOfferDetails* arg1 = (cc::OneTimePurchaseOfferDetails*)NULL; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= nativevalue_to_se(arg1->formattedPrice, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(arg1->formattedPrice, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_OneTimePurchaseOfferDetails_formattedPrice_get) + +static bool js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::OneTimePurchaseOfferDetails* arg1 = (cc::OneTimePurchaseOfferDetails*)NULL; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= nativevalue_to_se(arg1->priceAmountMicros, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get) + +static bool js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::OneTimePurchaseOfferDetails* arg1 = (cc::OneTimePurchaseOfferDetails*)NULL; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= nativevalue_to_se(arg1->priceCurrencyCode, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(arg1->priceCurrencyCode, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get) + +static bool js_delete_cc_OneTimePurchaseOfferDetails(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_OneTimePurchaseOfferDetails) + +bool js_register_cc_OneTimePurchaseOfferDetails(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("OneTimePurchaseOfferDetails", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("formattedPrice", _SE(js_cc_OneTimePurchaseOfferDetails_formattedPrice_get), nullptr); + cls->defineProperty("priceAmountMicros", _SE(js_cc_OneTimePurchaseOfferDetails_priceAmountMicros_get), nullptr); + cls->defineProperty("priceCurrencyCode", _SE(js_cc_OneTimePurchaseOfferDetails_priceCurrencyCode_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_OneTimePurchaseOfferDetails)); + + cls->install(); + JSBClassType::registerClass(cls); + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_InstallmentPlanDetails) +static bool js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::InstallmentPlanDetails* arg1 = (cc::InstallmentPlanDetails*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->installmentPlanCommitmentPaymentsCount; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get) + +static bool js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::InstallmentPlanDetails* arg1 = (cc::InstallmentPlanDetails*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->subsequentInstallmentPlanCommitmentPaymentsCount; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get) + +static bool js_delete_cc_InstallmentPlanDetails(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_InstallmentPlanDetails) + +bool js_register_cc_InstallmentPlanDetails(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("InstallmentPlanDetails", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("installmentPlanCommitmentPaymentsCount", _SE(js_cc_InstallmentPlanDetails_installmentPlanCommitmentPaymentsCount_get), nullptr); + cls->defineProperty("subsequentInstallmentPlanCommitmentPaymentsCount", _SE(js_cc_InstallmentPlanDetails_subsequentInstallmentPlanCommitmentPaymentsCount_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_InstallmentPlanDetails)); + + cls->install(); + JSBClassType::registerClass(cls); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PricingPhase) + +static bool js_cc_PricingPhase_billingCycleCount_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->billingCycleCount; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_billingCycleCount_get) + +static bool js_cc_PricingPhase_priceAmountMicros_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + long result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->priceAmountMicros; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_priceAmountMicros_get) + +static bool js_cc_PricingPhase_recurrenceMode_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->recurrenceMode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_recurrenceMode_get) + +static bool js_cc_PricingPhase_billingPeriod_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->billingPeriod; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_billingPeriod_get) + +static bool js_cc_PricingPhase_formattedPrice_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->formattedPrice; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_formattedPrice_get) + +static bool js_cc_PricingPhase_priceCurrencyCode_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::PricingPhase* arg1 = (cc::PricingPhase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->priceCurrencyCode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhase_priceCurrencyCode_get) + +static bool js_delete_cc_PricingPhase(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_PricingPhase) + +bool js_register_cc_PricingPhase(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("PricingPhase", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("billingCycleCount", _SE(js_cc_PricingPhase_billingCycleCount_get), nullptr); + cls->defineProperty("priceAmountMicros", _SE(js_cc_PricingPhase_priceAmountMicros_get), nullptr); + cls->defineProperty("recurrenceMode", _SE(js_cc_PricingPhase_recurrenceMode_get), nullptr); + cls->defineProperty("billingPeriod", _SE(js_cc_PricingPhase_billingPeriod_get), nullptr); + cls->defineProperty("formattedPrice", _SE(js_cc_PricingPhase_formattedPrice_get), nullptr); + cls->defineProperty("priceCurrencyCode", _SE(js_cc_PricingPhase_priceCurrencyCode_get), nullptr); + cls->defineFinalizeFunction(_SE(js_delete_cc_PricingPhase)); + + cls->install(); + JSBClassType::registerClass(cls); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PricingPhases) +static bool js_cc_PricingPhases_pricingPhaseList_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + cc::PricingPhases* result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->pricingPhases.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PricingPhases_pricingPhaseList_get) +static bool js_delete_cc_PricingPhases(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_PricingPhases) +bool js_register_cc_PricingPhases(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("PricingPhases", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("pricingPhaseList", _SE(js_cc_PricingPhases_pricingPhaseList_get), nullptr); + cls->defineFinalizeFunction(_SE(js_delete_cc_PricingPhases)); + + cls->install(); + JSBClassType::registerClass(cls); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_SubscriptionOfferDetails) + +static bool js_cc_SubscriptionOfferDetails_basePlanId_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->basePlanId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_basePlanId_get) + +static bool js_cc_SubscriptionOfferDetails_installmentPlanDetails_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + cc::InstallmentPlanDetails* result = 0; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->installmentPlanDetails.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_installmentPlanDetails_get) + +static bool js_cc_SubscriptionOfferDetails_offerId_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->offerId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerId_get) + +static bool js_cc_SubscriptionOfferDetails_offerTags_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->offerTags; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerTags_get) + +static bool js_cc_SubscriptionOfferDetails_offerToken_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->offerToken; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_offerToken_get) + +static bool js_cc_SubscriptionOfferDetails_pricingPhases_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::SubscriptionOfferDetails* arg1 = (cc::SubscriptionOfferDetails*)NULL; + cc::PricingPhases* result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->pricingPhases.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_SubscriptionOfferDetails_pricingPhases_get) + +static bool js_delete_cc_SubscriptionOfferDetails(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_SubscriptionOfferDetails) + +bool js_register_cc_SubscriptionOfferDetails(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("SubscriptionOfferDetails", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("basePlanId", _SE(js_cc_SubscriptionOfferDetails_basePlanId_get), nullptr); + cls->defineProperty("installmentPlanDetails", _SE(js_cc_SubscriptionOfferDetails_installmentPlanDetails_get), nullptr); + cls->defineProperty("offerId", _SE(js_cc_SubscriptionOfferDetails_offerId_get), nullptr); + cls->defineProperty("offerTags", _SE(js_cc_SubscriptionOfferDetails_offerTags_get), nullptr); + cls->defineProperty("offerToken", _SE(js_cc_SubscriptionOfferDetails_offerToken_get), nullptr); + cls->defineProperty("pricingPhases", _SE(js_cc_SubscriptionOfferDetails_pricingPhases_get), nullptr); + cls->defineFinalizeFunction(_SE(js_delete_cc_SubscriptionOfferDetails)); + + cls->install(); + JSBClassType::registerClass(cls); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_ProductDetails) + +static bool js_cc_ProductDetails_equals(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + cc::ProductDetails* arg2 = (cc::ProductDetails*)NULL; + bool result; + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= sevalue_to_native(args[0], &arg2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + result = (bool)((cc::ProductDetails const*)arg1)->equals(*arg2); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + return true; +} +SE_BIND_FUNC(js_cc_ProductDetails_equals) + +static bool js_cc_ProductDetails_hashCode_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->hashCode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_hashCode_get) + +static bool js_cc_ProductDetails_description_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->description; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_description_get) + +static bool js_cc_ProductDetails_name_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->name; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_name_get) + +static bool js_cc_ProductDetails_oneTimePurchaseOfferDetails_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + cc::OneTimePurchaseOfferDetails* result = 0; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->oneTimePurchaseOfferDetails.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_oneTimePurchaseOfferDetails_get) + +static bool js_cc_ProductDetails_productId_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->productId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_productId_get) + +static bool js_cc_ProductDetails_productType_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->productType; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_productType_get) + +static bool js_cc_ProductDetails_title_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->title; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_title_get) + +static bool js_cc_ProductDetails_toString_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->toString; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_toString_get) + +static bool js_cc_ProductDetails_subscriptionOfferDetails_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::ProductDetails* arg1 = (cc::ProductDetails*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->subscriptionOfferDetails; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_ProductDetails_subscriptionOfferDetails_get) + +static bool js_delete_cc_ProductDetails(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_ProductDetails) + +bool js_register_cc_ProductDetails(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("ProductDetails", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("hashCode", _SE(js_cc_ProductDetails_hashCode_get), nullptr); + cls->defineProperty("description", _SE(js_cc_ProductDetails_description_get), nullptr); + cls->defineProperty("name", _SE(js_cc_ProductDetails_name_get), nullptr); + cls->defineProperty("oneTimePurchaseOfferDetails", _SE(js_cc_ProductDetails_oneTimePurchaseOfferDetails_get), nullptr); + cls->defineProperty("productId", _SE(js_cc_ProductDetails_productId_get), nullptr); + cls->defineProperty("productType", _SE(js_cc_ProductDetails_productType_get), nullptr); + cls->defineProperty("title", _SE(js_cc_ProductDetails_title_get), nullptr); + cls->defineProperty("toStr", _SE(js_cc_ProductDetails_toString_get), nullptr); + cls->defineProperty("subscriptionOfferDetails", _SE(js_cc_ProductDetails_subscriptionOfferDetails_get), nullptr); + + cls->defineFunction("equals", _SE(js_cc_ProductDetails_equals)); + + cls->defineFinalizeFunction(_SE(js_delete_cc_ProductDetails)); + + cls->install(); + JSBClassType::registerClass(cls); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_AccountIdentifiers) + +static bool js_cc_AccountIdentifiers_obfuscatedAccountId_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::AccountIdentifiers* arg1 = (cc::AccountIdentifiers*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->obfuscatedAccountId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_AccountIdentifiers_obfuscatedAccountId_get) + +static bool js_cc_AccountIdentifiers_obfuscatedProfileId_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::AccountIdentifiers* arg1 = (cc::AccountIdentifiers*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->obfuscatedProfileId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_AccountIdentifiers_obfuscatedProfileId_get) + +static bool js_delete_cc_AccountIdentifiers(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_AccountIdentifiers) + +bool js_register_cc_AccountIdentifiers(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("AccountIdentifiers", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("obfuscatedAccountId", _SE(js_cc_AccountIdentifiers_obfuscatedAccountId_get), nullptr); + cls->defineProperty("obfuscatedProfileId", _SE(js_cc_AccountIdentifiers_obfuscatedProfileId_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_AccountIdentifiers)); + + cls->install(); + JSBClassType::registerClass(cls); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_PendingPurchaseUpdate) + +static bool js_cc_PendingPurchaseUpdate_products_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::PendingPurchaseUpdate* arg1 = (cc::PendingPurchaseUpdate*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->products; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PendingPurchaseUpdate_products_get) + +static bool js_cc_PendingPurchaseUpdate_purchaseToken_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::PendingPurchaseUpdate* arg1 = (cc::PendingPurchaseUpdate*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->purchaseToken; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_PendingPurchaseUpdate_purchaseToken_get) + +static bool js_delete_cc_PendingPurchaseUpdate(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_PendingPurchaseUpdate) + +bool js_register_cc_PendingPurchaseUpdate(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("PendingPurchaseUpdate", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("products", _SE(js_cc_PendingPurchaseUpdate_products_get), nullptr); + cls->defineProperty("purchaseToken", _SE(js_cc_PendingPurchaseUpdate_purchaseToken_get), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_PendingPurchaseUpdate)); + + cls->install(); + JSBClassType::registerClass(cls); + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Purchase) + +static bool js_cc_Purchase_equals(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + cc::Purchase* arg1 = (cc::Purchase*)NULL; + cc::Purchase* arg2 = 0; + cc::Purchase temp2; + bool result; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + result = (bool)((cc::Purchase const*)arg1)->equals((cc::Purchase const&)*arg2); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_FUNC(js_cc_Purchase_equals) + +static bool js_cc_Purchase_accountIdentifiers_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + cc::AccountIdentifiers* result = 0; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->accountIdentifiers.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_accountIdentifiers_get) + +static bool js_cc_Purchase_developerPayload_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->developerPayload; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_developerPayload_get) + +static bool js_cc_Purchase_orderId_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->orderId; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_orderId_get) + +static bool js_cc_Purchase_originalJson_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->originalJson; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_originalJson_get) + +static bool js_cc_Purchase_packageName_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->packageName; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_packageName_get) + +static bool js_cc_Purchase_pendingPurchaseUpdate_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + cc::PendingPurchaseUpdate* result = 0; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->pendingPurchaseUpdate.get(); + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_pendingPurchaseUpdate_get) + +static bool js_cc_Purchase_products_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::vector result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->products; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_products_get) + +static bool js_cc_Purchase_purchaseState_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->purchaseState; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_purchaseState_get) + +static bool js_cc_Purchase_purchaseTime_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + long result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = (long)arg1->purchaseTime; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_purchaseTime_get) + +static bool js_cc_Purchase_purchaseToken_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->purchaseToken; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_purchaseToken_get) + +static bool js_cc_Purchase_quantity_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->quantity; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_quantity_get) + +static bool js_cc_Purchase_signature_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->signature; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_signature_get) + +static bool js_cc_Purchase_hashCode_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + int result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->hashCode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_hashCode_get) + +static bool js_cc_Purchase_isAcknowledged_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + bool result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->isAcknowledged; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_isAcknowledged_get) + +static bool js_cc_Purchase_isAutoRenewing_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + bool result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->isAutoRenewing; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_isAutoRenewing_get) + +static bool js_cc_Purchase_toString_get(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::Purchase* arg1 = (cc::Purchase*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->toString; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_Purchase_toString_get) + +static bool js_delete_cc_Purchase(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_Purchase) + +bool js_register_cc_Purchase(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("Purchase", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + cls->defineProperty("accountIdentifiers", _SE(js_cc_Purchase_accountIdentifiers_get), nullptr); + cls->defineProperty("developerPayload", _SE(js_cc_Purchase_developerPayload_get), nullptr); + cls->defineProperty("orderId", _SE(js_cc_Purchase_orderId_get), nullptr); + cls->defineProperty("originalJson", _SE(js_cc_Purchase_originalJson_get), nullptr); + cls->defineProperty("packageName", _SE(js_cc_Purchase_packageName_get), nullptr); + cls->defineProperty("pendingPurchaseUpdate", _SE(js_cc_Purchase_pendingPurchaseUpdate_get), nullptr); + cls->defineProperty("products", _SE(js_cc_Purchase_products_get), nullptr); + cls->defineProperty("purchaseState", _SE(js_cc_Purchase_purchaseState_get), nullptr); + cls->defineProperty("purchaseTime", _SE(js_cc_Purchase_purchaseTime_get), nullptr); + cls->defineProperty("purchaseToken", _SE(js_cc_Purchase_purchaseToken_get), nullptr); + cls->defineProperty("quantity", _SE(js_cc_Purchase_quantity_get), nullptr); + cls->defineProperty("signature", _SE(js_cc_Purchase_signature_get), nullptr); + cls->defineProperty("hashCode", _SE(js_cc_Purchase_hashCode_get), nullptr); + cls->defineProperty("isAcknowledged", _SE(js_cc_Purchase_isAcknowledged_get), nullptr); + cls->defineProperty("isAutoRenewing", _SE(js_cc_Purchase_isAutoRenewing_get), nullptr); + cls->defineProperty("toStr", _SE(js_cc_Purchase_toString_get), nullptr); + + cls->defineFunction("equals", _SE(js_cc_Purchase_equals)); + + cls->defineFinalizeFunction(_SE(js_delete_cc_Purchase)); + + cls->install(); + JSBClassType::registerClass(cls); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +static bool js_cc_BillingConfig_countryCode(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::BillingConfig* arg1 = (cc::BillingConfig*)NULL; + std::string result; + + arg1 = SE_THIS_OBJECT(s); + if (nullptr == arg1) return true; + result = arg1->countryCode; + + ok &= nativevalue_to_se(result, s.rval(), s.thisObject() /*ctx*/); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + + return true; +} +SE_BIND_PROP_GET(js_cc_BillingConfig_countryCode) + +static bool js_delete_cc_BillingConfig(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_BillingConfig) + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_BillingConfig) + +bool js_register_cc_BillingConfig(se::Object* obj) { // NOLINT + auto* cls = se::Class::create("BillingConfig", obj, nullptr, nullptr); + + cls->defineStaticProperty("__isJSB", se::Value(true), se::PropertyAttribute::READ_ONLY | se::PropertyAttribute::DONT_ENUM | se::PropertyAttribute::DONT_DELETE); + + cls->defineProperty("countryCode", _SE(js_cc_BillingConfig_countryCode), nullptr); + + cls->defineFinalizeFunction(_SE(js_delete_cc_BillingConfig)); + + cls->install(); + JSBClassType::registerClass(cls); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +SE_DECLARE_FINALIZE_FUNC(js_delete_cc_Google_Billing) + +static bool js_cc_Google_Billing_startConnection(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().startConnection(); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_startConnection) + +static bool js_cc_Google_Billing_endConnection(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().endConnection(); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_endConnection) + +static bool js_cc_Google_Billing_getConnectionState(se::State& s) { // NOLINT + int connectionState = cc::GoogleBilling::getInstance().getConnectionState(); + s.rval().setFloat(connectionState); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_getConnectionState) + +static bool js_cc_Google_Billing_isReady(se::State& s) { + int isReady = cc::GoogleBilling::getInstance().isReady(); + s.rval().setFloat(isReady); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_isReady) + +static bool js_cc_Google_Billing_queryProductDetailsParams(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + + std::vector* arg2 = 0; + std::string* arg3 = 0; + std::vector temp2; + std::string temp3; + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + ok &= sevalue_to_native(args[1], &temp3, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg3 = &temp3; + + cc::GoogleBilling::getInstance().queryProductDetailsParams((std::vector const&)*arg2, (std::string const&)*arg3); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_queryProductDetailsParams) + +static bool js_cc_Google_Billing_launchBillingFlow(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + + std::vector* arg2 = 0; + std::string* arg3 = 0; + std::vector temp2; + std::string temp3; + + if (argc != 2) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + ok &= sevalue_to_native(args[1], &temp3, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg3 = &temp3; + + cc::GoogleBilling::getInstance().launchBillingFlow((std::vector const&)*arg2, (std::string const&)*arg3); + + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_launchBillingFlow) + +static bool js_cc_Google_Billing_consumePurchases(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + std::vector* arg2 = 0; + std::vector temp2; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + cc::GoogleBilling::getInstance().consumePurchases((std::vector const&)*arg2); + + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_consumePurchases) + +static bool js_cc_Google_Billing_acknowledgePurchase(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + std::vector* arg1 = 0; + std::vector temp2; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg1 = &temp2; + + cc::GoogleBilling::getInstance().acknowledgePurchase((std::vector const&)*arg1); + + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_acknowledgePurchase) + +static bool js_cc_Google_Billing_queryPurchasesAsync(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + std::string* arg2 = 0; + std::string temp2; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + arg2 = &temp2; + + cc::GoogleBilling::getInstance().queryPurchasesAsync((std::string const&)*arg2); + + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_queryPurchasesAsync) + +static bool js_cc_Google_Billing_getBillingConfigAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().getBillingConfigAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_getBillingConfigAsync) + +static bool js_cc_Google_Billing_createAlternativeBillingOnlyReportingDetailsAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().createAlternativeBillingOnlyReportingDetailsAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_createAlternativeBillingOnlyReportingDetailsAsync) + +static bool js_cc_Google_Billing_isAlternativeBillingOnlyAvailableAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().isAlternativeBillingOnlyAvailableAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_isAlternativeBillingOnlyAvailableAsync) + +static bool js_cc_Google_Billing_createExternalOfferReportingDetailsAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().createExternalOfferReportingDetailsAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_createExternalOfferReportingDetailsAsync) + +static bool js_cc_Google_Billing_isExternalOfferAvailableAsync(se::State& s) { // NOLINT + cc::GoogleBilling::getInstance().isExternalOfferAvailableAsync(); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_isExternalOfferAvailableAsync) + +static bool js_cc_Google_Billing_isFeatureSupported(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + std::string temp2; + + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; + } + + ok &= sevalue_to_native(args[0], &temp2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + cc::BillingResult* result = cc::GoogleBilling::getInstance().isFeatureSupported(temp2); + + ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_isFeatureSupported) + +static bool js_cc_Google_Billing_showAlternativeBillingOnlyInformationDialog(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::BillingResult* result = cc::GoogleBilling::getInstance().showAlternativeBillingOnlyInformationDialog(); + ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_showAlternativeBillingOnlyInformationDialog) + +static bool js_cc_Google_Billing_showExternalOfferInformationDialog(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::BillingResult* result = cc::GoogleBilling::getInstance().showExternalOfferInformationDialog(); + ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_showExternalOfferInformationDialog) + +static bool js_cc_Google_Billing_showInAppMessages(se::State& s) { // NOLINT + CC_UNUSED bool ok = true; + cc::BillingResult* result = cc::GoogleBilling::getInstance().showInAppMessages(); + ok &= nativevalue_to_se(*result, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; +} +SE_BIND_FUNC(js_cc_Google_Billing_showInAppMessages) + +static bool js_delete_cc_Google_Billing(se::State& s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(js_delete_cc_Google_Billing) + +bool js_register_cc_Google_Billing(se::Object* obj) { // NOLINT + se::Value googleBillingVal{se::Object::createPlainObject()}; + auto* cls = googleBillingVal.toObject(); + + cls->defineFunction("startConnection", _SE(js_cc_Google_Billing_startConnection)); + cls->defineFunction("endConnection", _SE(js_cc_Google_Billing_endConnection)); + cls->defineFunction("getConnectionState", _SE(js_cc_Google_Billing_getConnectionState)); + cls->defineFunction("isReady", _SE(js_cc_Google_Billing_isReady)); + cls->defineFunction("queryProductDetailsParams", _SE(js_cc_Google_Billing_queryProductDetailsParams)); + + cls->defineFunction("launchBillingFlow", _SE(js_cc_Google_Billing_launchBillingFlow)); + cls->defineFunction("consumePurchases", _SE(js_cc_Google_Billing_consumePurchases)); + cls->defineFunction("acknowledgePurchase", _SE(js_cc_Google_Billing_acknowledgePurchase)); + cls->defineFunction("queryPurchasesAsync", _SE(js_cc_Google_Billing_queryPurchasesAsync)); + cls->defineFunction("getBillingConfigAsync", _SE(js_cc_Google_Billing_getBillingConfigAsync)); + + cls->defineFunction("createAlternativeBillingOnlyReportingDetailsAsync", _SE(js_cc_Google_Billing_createAlternativeBillingOnlyReportingDetailsAsync)); + cls->defineFunction("isAlternativeBillingOnlyAvailableAsync", _SE(js_cc_Google_Billing_isAlternativeBillingOnlyAvailableAsync)); + cls->defineFunction("createExternalOfferReportingDetailsAsync", _SE(js_cc_Google_Billing_createExternalOfferReportingDetailsAsync)); + cls->defineFunction("isExternalOfferAvailableAsync", _SE(js_cc_Google_Billing_isExternalOfferAvailableAsync)); + + cls->defineFunction("isFeatureSupported", _SE(js_cc_Google_Billing_isFeatureSupported)); + + cls->defineFunction("showAlternativeBillingOnlyInformationDialog", _SE(js_cc_Google_Billing_showAlternativeBillingOnlyInformationDialog)); + cls->defineFunction("showExternalOfferInformationDialog", _SE(js_cc_Google_Billing_showExternalOfferInformationDialog)); + cls->defineFunction("showInAppMessages", _SE(js_cc_Google_Billing_showInAppMessages)); + + obj->setProperty("googleBilling", googleBillingVal); + + return true; +} + +bool jsb_register_all_billing(se::Object* obj) { // NOLINT + se::Value nsVal; + if (!obj->getProperty("jsb", &nsVal, true)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + obj->setProperty("jsb", nsVal); + } + se::Object* ns = nsVal.toObject(); + /* Register classes */ + js_register_cc_BillingResult(ns); + js_register_cc_OneTimePurchaseOfferDetails(ns); + js_register_cc_InstallmentPlanDetails(ns); + js_register_cc_PricingPhases(ns); + js_register_cc_PricingPhase(ns); + + js_register_cc_SubscriptionOfferDetails(ns); + js_register_cc_ProductDetails(ns); + js_register_cc_AccountIdentifiers(ns); + js_register_cc_PendingPurchaseUpdate(ns); + js_register_cc_Purchase(ns); + js_register_cc_BillingConfig(ns); + js_register_cc_Google_Billing(ns); + return true; +} + +#else +void jsb_register_all_billing(se::Object *ns) {} // NOLINT + +#endif // CC_PLATFORM_ANDROID diff --git a/native/cocos/bindings/manual/jsb_module_register.cpp b/native/cocos/bindings/manual/jsb_module_register.cpp index 63237ec6cef..0e0c7ee52ff 100644 --- a/native/cocos/bindings/manual/jsb_module_register.cpp +++ b/native/cocos/bindings/manual/jsb_module_register.cpp @@ -125,6 +125,8 @@ #include "cocos/bindings/auto/jsb_physics_auto.h" #endif +extern bool jsb_register_all_billing(se::Object *); // NOLINT + bool jsb_register_all_modules() { se::ScriptEngine *se = se::ScriptEngine::getInstance(); @@ -190,6 +192,10 @@ bool jsb_register_all_modules() { se->addRegisterCallback(register_all_socketio); #endif +#if CC_USE_GOOGLE_BILLING + se->addRegisterCallback(jsb_register_all_billing); +#endif + #if CC_USE_MIDDLEWARE se->addRegisterCallback(register_all_editor_support); diff --git a/native/cocos/platform/android/java/vendor/google/billing/GoogleBilling.java b/native/cocos/platform/android/java/vendor/google/billing/GoogleBilling.java new file mode 100644 index 00000000000..9cd1bc853d8 --- /dev/null +++ b/native/cocos/platform/android/java/vendor/google/billing/GoogleBilling.java @@ -0,0 +1,496 @@ +/**************************************************************************** +Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. + ****************************************************************************/ + +package google.billing; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.billingclient.api.BillingConfig; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.InAppMessageResult; +import com.android.billingclient.api.ProductDetails; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.AcknowledgePurchaseParams; +import com.android.billingclient.api.AcknowledgePurchaseResponseListener; +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingFlowParams; +import com.android.billingclient.api.ConsumeParams; +import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.AlternativeBillingOnlyReportingDetailsListener; +import com.android.billingclient.api.ProductDetailsResponseListener; +import com.android.billingclient.api.PurchasesResponseListener; +import com.android.billingclient.api.PurchasesUpdatedListener; +import com.android.billingclient.api.QueryProductDetailsParams; +import com.android.billingclient.api.QueryPurchasesParams; +import com.android.billingclient.api.GetBillingConfigParams; +import com.android.billingclient.api.BillingConfigResponseListener; +import com.android.billingclient.api.AlternativeBillingOnlyReportingDetails; +import com.android.billingclient.api.AlternativeBillingOnlyAvailabilityListener; +import com.android.billingclient.api.AlternativeBillingOnlyInformationDialogListener; +import com.android.billingclient.api.ExternalOfferReportingDetailsListener; +import com.android.billingclient.api.ExternalOfferReportingDetails; +import com.android.billingclient.api.ExternalOfferAvailabilityListener; +import com.android.billingclient.api.ExternalOfferInformationDialogListener; +import com.android.billingclient.api.InAppMessageParams; +import com.android.billingclient.api.InAppMessageResponseListener; +import com.cocos.lib.GlobalObject; +import com.cocos.lib.CocosHelper; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GoogleBilling implements BillingClientStateListener, + PurchasesUpdatedListener, ProductDetailsResponseListener, PurchasesResponseListener, + ConsumeResponseListener, AcknowledgePurchaseResponseListener, BillingConfigResponseListener, + AlternativeBillingOnlyReportingDetailsListener, AlternativeBillingOnlyAvailabilityListener,AlternativeBillingOnlyInformationDialogListener, + ExternalOfferReportingDetailsListener, ExternalOfferAvailabilityListener,ExternalOfferInformationDialogListener, InAppMessageResponseListener { + + private static final String TAG = GoogleBilling.class.getSimpleName(); + private Map _productDetails = new HashMap<>(); + private Map _purchase = new HashMap<>(); + private int _productDetailsNextID = 0; + private int _purchaseNextID = 0; + + /** + * The billing client. + */ + private BillingClient _billingClient; + + public GoogleBilling() { + _billingClient = BillingClient.newBuilder(GlobalObject.getActivity()) + .setListener(this) + .enablePendingPurchases() + .build(); + } + + public void removeProductDetails(int productDetailsID) { + if (_productDetails.containsKey(productDetailsID)) { + _productDetails.remove(productDetailsID); + } else { + Log.w(TAG, "Remove invalid product details id"); + } + } + + public void removePurchase(int purchaseID) { + if (_purchase.containsKey(purchaseID)) { + _purchase.remove(purchaseID); + } else { + Log.w(TAG, "Remove invalid purchase id"); + } + } + + public void startConnection() { + _billingClient.startConnection(this); + } + public void endConnection() { + _billingClient.endConnection(); + } + + public int getConnectionState() { + return _billingClient.getConnectionState(); + } + + public BillingResult isFeatureSupported(String feature) { + return _billingClient.isFeatureSupported(feature); + } + + public boolean isReady() { + return _billingClient.isReady(); + } + + public boolean isConnected() { + return getConnectionState() == BillingClient.ConnectionState.CONNECTED; + } + + public void createAlternativeBillingOnlyReportingDetailsAsync() { + _billingClient.createAlternativeBillingOnlyReportingDetailsAsync(this); + } + + public void isAlternativeBillingOnlyAvailableAsync() { + _billingClient.isAlternativeBillingOnlyAvailableAsync(this); + } + + public void createExternalOfferReportingDetailsAsync() { + _billingClient.createExternalOfferReportingDetailsAsync(this); + } + + public void isExternalOfferAvailableAsync() { + _billingClient.isExternalOfferAvailableAsync(this); + } + + public void queryProductDetailsParams(String[] productIds, String type) { + if(!isConnected()) { + Log.e(TAG, "Must be connected before use this interface"); + return; + } + if(productIds.length == 0) { + Log.e(TAG, "Product ID cannot be empty"); + return; + } + String inputType; + if(type.equals(BillingClient.ProductType.INAPP)) { + inputType = BillingClient.ProductType.INAPP; + } else if(type.equals(BillingClient.ProductType.SUBS)) { + inputType = BillingClient.ProductType.SUBS; + } else { + Log.e(TAG, "Undefined product types."); + return; + } + List products = new ArrayList<>(); + for(String productId: productIds) { + products.add( + QueryProductDetailsParams.Product.newBuilder() + .setProductId(productId) + .setProductType(inputType) + .build()); + } + QueryProductDetailsParams params = + QueryProductDetailsParams.newBuilder().setProductList(products).build(); + _billingClient.queryProductDetailsAsync(params, this); + } + + public void launchBillingFlow(int[] productDetailsHashs, String selectedOfferToken) { + if(!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + List productDetailsParamsList = new ArrayList<>(); + for (int productDetailsHash: productDetailsHashs) { + if(_productDetails.containsKey(productDetailsHash)) { + if(selectedOfferToken.isEmpty()) { + productDetailsParamsList.add( + BillingFlowParams.ProductDetailsParams.newBuilder().setProductDetails(_productDetails.get(productDetailsHash)).build() + ); + } else { + productDetailsParamsList.add( + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(_productDetails.get(productDetailsHash)) + .setOfferToken(selectedOfferToken) + .build() + ); + } + } else { + Log.w(TAG, "Purchased product ID does not exist"); + } + + } + if(productDetailsParamsList.isEmpty()) { + Log.w(TAG, "Purchased product ID does not exist"); + return; + } + BillingFlowParams params = BillingFlowParams.newBuilder().setProductDetailsParamsList(productDetailsParamsList).build(); + _billingClient.launchBillingFlow(GlobalObject.getActivity(), params); + } + + public void queryPurchasesAsync(String type) { + if(!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + String inputType; + if(type.equals(BillingClient.ProductType.INAPP)) { + inputType = BillingClient.ProductType.INAPP; + } else if(type.equals(BillingClient.ProductType.SUBS)) { + inputType = BillingClient.ProductType.SUBS; + } else { + Log.w(TAG, "Undefined product types."); + return; + } + _billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder() + .setProductType(inputType) + .build(), this); + } + + public void consumePurchase(@Nullable Purchase purchase) { + if(!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + if(purchase != null && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { + _billingClient.consumeAsync(ConsumeParams.newBuilder() + .setPurchaseToken(purchase.getPurchaseToken()).build(), + this); + } + } + + private void consumePurchases(@NonNull List purchases) { + if(!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + for (Purchase purchase : purchases) { + consumePurchase(purchase); + } + } + + public void consumePurchases(@NonNull int[] purchaseHashs) { + if(purchaseHashs.length == 0) { + return; + } + List purchases = new ArrayList<>(); + for (int purchaseHash: purchaseHashs) { + if(_purchase.containsKey(purchaseHash)) { + purchases.add(_purchase.get(purchaseHash)); + } else { + Log.w(TAG, "Purchase id for consumption does not exist"); + } + } + if (!isConnected()) { + startConnection(); + return; + } + consumePurchases(purchases); + } + + private void acknowledgePurchase(@Nullable Purchase purchase) { + if (!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + + if (purchase != null && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { + //if (!purchase.isAcknowledged()) { + _billingClient.acknowledgePurchase(AcknowledgePurchaseParams.newBuilder() + .setPurchaseToken(purchase.getPurchaseToken()).build(), + this); + //} + } + } + + private void acknowledgePurchases(@Nullable List purchases) { + if (!isConnected()) { + Log.w(TAG, "Must be connected before use this interface"); + return; + } + if(purchases != null) { + for (Purchase purchase : purchases) { + acknowledgePurchase(purchase); + } + } + } + + public void acknowledgePurchases(@NonNull int[] purchaseHashs) { + List purchases = new ArrayList<>(); + for (int purchaseHash: purchaseHashs) { + if(_purchase.containsKey(purchaseHash)) { + purchases.add(_purchase.get(purchaseHash)); + } else { + Log.w(TAG, "Purchase id for acknowledge does not exist"); + } + } + acknowledgePurchases(purchases); + } + + public void getBillingConfigAsync() { + GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build(); + _billingClient.getBillingConfigAsync(getBillingConfigParams, this); + } + + public BillingResult showAlternativeBillingOnlyInformationDialog() { + return _billingClient.showAlternativeBillingOnlyInformationDialog(GlobalObject.getActivity(), this); + } + + public BillingResult showExternalOfferInformationDialog() { + return _billingClient.showExternalOfferInformationDialog(GlobalObject.getActivity(), this); + } + public BillingResult showInAppMessages() { + return _billingClient.showInAppMessages(GlobalObject.getActivity(), InAppMessageParams.newBuilder().build(), this); + } + + @Override + public void onBillingSetupFinished(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onBillingSetupFinished(billingResult); + } + }); + } + + public void onBillingServiceDisconnected() { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onBillingServiceDisconnected(); + } + }); + } + + @Override + public void onProductDetailsResponse(@NonNull BillingResult billingResult, + @NonNull List productDetailsList) { + int startID = _productDetailsNextID; + for (ProductDetails productDetails: productDetailsList) { + _productDetails.put(_productDetailsNextID++, productDetails); + } + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onProductDetailsResponse(billingResult, productDetailsList, startID); + } + }); + } + + @Override + public void onPurchasesUpdated(@NonNull BillingResult billingResult, + @Nullable List purchaseList) { + int startID = _purchaseNextID; + if(purchaseList != null) { + for (Purchase purchase: purchaseList) { + _purchase.put(_purchaseNextID++, purchase); + } + } + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onPurchasesUpdated(billingResult, purchaseList, startID); + } + }); + } + + @Override + public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, + @NonNull List purchaseList) { + int startID = _purchaseNextID; + for (Purchase purchase: purchaseList) { + _purchase.put(_purchaseNextID++, purchase); + } + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onQueryPurchasesResponse(billingResult, purchaseList, startID); + } + }); + } + + @Override + public void onConsumeResponse(@NonNull BillingResult billingResult, + @NonNull String purchaseToken) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onConsumeResponse(billingResult, purchaseToken); + } + }); + } + + @Override + public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onAcknowledgePurchaseResponse(billingResult); + } + }); + } + + @Override + public void onBillingConfigResponse(@NonNull BillingResult billingResult, @Nullable BillingConfig billingConfig) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onBillingConfigResponse(billingResult, billingConfig); + } + }); + } + + @Override + public void onAlternativeBillingOnlyTokenResponse( + @NonNull BillingResult billingResult, + @Nullable AlternativeBillingOnlyReportingDetails alternativeBillingOnlyReportingDetails) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onAlternativeBillingOnlyTokenResponse(billingResult, alternativeBillingOnlyReportingDetails); + } + }); + } + + @Override + public void onAlternativeBillingOnlyAvailabilityResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onAlternativeBillingOnlyAvailabilityResponse(billingResult); + } + }); + } + + @Override + public void onExternalOfferReportingDetailsResponse( + @NonNull BillingResult billingResult, + @Nullable ExternalOfferReportingDetails externalOfferReportingDetails + ) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onExternalOfferReportingDetailsResponse(billingResult, externalOfferReportingDetails); + } + }); + } + + @Override + public void onExternalOfferAvailabilityResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onExternalOfferAvailabilityResponse(billingResult); + } + }); + } + + @Override + public void onExternalOfferInformationDialogResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onExternalOfferInformationDialogResponse(billingResult); + } + }); + } + + @Override + public void onAlternativeBillingOnlyInformationDialogResponse(@NonNull BillingResult billingResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onAlternativeBillingOnlyInformationDialogResponse(billingResult); + } + }); + } + + @Override + public void onInAppMessageResponse(@NonNull InAppMessageResult inAppMessageResult) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + GoogleBillingHelper.onInAppMessageResponse(inAppMessageResult); + } + }); + } +} diff --git a/native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java b/native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java new file mode 100644 index 00000000000..2cef42b49e2 --- /dev/null +++ b/native/cocos/platform/android/java/vendor/google/billing/GoogleBillingHelper.java @@ -0,0 +1,214 @@ +/**************************************************************************** +Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. + ****************************************************************************/ + +package google.billing; + +import android.annotation.SuppressLint; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.billingclient.api.AlternativeBillingOnlyReportingDetails; +import com.android.billingclient.api.BillingConfig; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.ExternalOfferReportingDetails; +import com.android.billingclient.api.InAppMessageResult; +import com.android.billingclient.api.ProductDetails; +import com.android.billingclient.api.Purchase; +import java.util.List; +import com.cocos.lib.GlobalObject; +import google.billing.GoogleBilling; + +public class GoogleBillingHelper { + public static native void onBillingSetupFinished(@NonNull BillingResult billingResult); + public static native void onBillingServiceDisconnected(); + public static native void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List productDetailsList, int startID); + public static native void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List purchasesList, int startID); + public static native void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List purchasesList, int startID); + public static native void onConsumeResponse(@NonNull BillingResult billingResult, @NonNull String purchaseToken); + public static native void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult); + public static native void onBillingConfigResponse(@NonNull BillingResult billingResult, @Nullable BillingConfig billingConfig); + + public static native void onAlternativeBillingOnlyTokenResponse(@NonNull BillingResult billingResult, + @Nullable AlternativeBillingOnlyReportingDetails alternativeBillingOnlyReportingDetails); + public static native void onAlternativeBillingOnlyAvailabilityResponse(@NonNull BillingResult billingResult); + + public static native void onExternalOfferReportingDetailsResponse( + @NonNull BillingResult billingResult, + @Nullable ExternalOfferReportingDetails externalOfferReportingDetails + ); + public static native void onExternalOfferAvailabilityResponse(@NonNull BillingResult billingResult); + public static native void onExternalOfferInformationDialogResponse(@NonNull BillingResult billingResult); + public static native void onAlternativeBillingOnlyInformationDialogResponse(@NonNull BillingResult billingResult); + public static native void onInAppMessageResponse(@NonNull InAppMessageResult inAppMessageResult); + + @SuppressLint("StaticFieldLeak") + private static GoogleBilling instance; + + public static GoogleBilling getInstance() { + if (instance == null) { + instance = new GoogleBilling(); + } + return instance; + } + + public static void removeProductDetails(int productDetailsID) { + getInstance().removeProductDetails(productDetailsID); + } + + public static void removePurchase(int purchaseID) { + getInstance().removeProductDetails(purchaseID); + } + + public static void startConnection() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().startConnection(); + } + }); + } + + public static void endConnection() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().endConnection(); + } + }); + } + + public static int getConnectionState() { + return getInstance().getConnectionState(); + } + + public static BillingResult isFeatureSupported(String feature) { + return getInstance().isFeatureSupported(feature); + } + + public static boolean isReady() { + return getInstance().isReady(); + } + + public static void createAlternativeBillingOnlyReportingDetailsAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().createAlternativeBillingOnlyReportingDetailsAsync(); + } + }); + } + + public static void isAlternativeBillingOnlyAvailableAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().isAlternativeBillingOnlyAvailableAsync(); + } + }); + } + + public static void createExternalOfferReportingDetailsAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().createExternalOfferReportingDetailsAsync(); + } + }); + } + + public static void isExternalOfferAvailableAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().isExternalOfferAvailableAsync(); + } + }); + } + + public static void queryProductDetailsParams(String[] productIds, String type) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().queryProductDetailsParams(productIds, type); + } + }); + } + + public static void launchBillingFlow(int[] productDetailsHashs, String selectedOfferToken) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().launchBillingFlow(productDetailsHashs, selectedOfferToken); + } + }); + } + + public static void queryPurchasesAsync(String type) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().queryPurchasesAsync(type); + } + }); + } + + public static void consumePurchases(@NonNull int[] purchaseHashs) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().consumePurchases(purchaseHashs); + } + }); + } + + public static void acknowledgePurchases(@NonNull int[] purchaseHashs) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().acknowledgePurchases(purchaseHashs); + } + }); + } + + public static void getBillingConfigAsync() { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + getInstance().getBillingConfigAsync(); + } + }); + } + + public static BillingResult showAlternativeBillingOnlyInformationDialog() { + return getInstance().showAlternativeBillingOnlyInformationDialog(); + } + + public static BillingResult showExternalOfferInformationDialog() { + return getInstance().showExternalOfferInformationDialog(); + } + public static BillingResult showInAppMessages() { + return getInstance().showInAppMessages(); + } +} diff --git a/native/cocos/platform/android/libcocos2dx/AndroidManifest.xml b/native/cocos/platform/android/libcocos2dx/AndroidManifest.xml index b6ea80dffee..5364438c306 100644 --- a/native/cocos/platform/android/libcocos2dx/AndroidManifest.xml +++ b/native/cocos/platform/android/libcocos2dx/AndroidManifest.xml @@ -1,4 +1,5 @@ + diff --git a/native/cocos/platform/android/libcocos2dx/build.gradle b/native/cocos/platform/android/libcocos2dx/build.gradle index 5c47318e45b..801cff10d39 100644 --- a/native/cocos/platform/android/libcocos2dx/build.gradle +++ b/native/cocos/platform/android/libcocos2dx/build.gradle @@ -17,10 +17,13 @@ android { sourceSets.main { aidl.srcDir "../java/src" - java.srcDir "../java/src" + java.srcDirs = ["../java/src"] manifest.srcFile "AndroidManifest.xml" } + if(project.hasProperty("PROP_ENABLE_GOOGLE_BILLING") && project.PROP_ENABLE_GOOGLE_BILLING.toBoolean()) { + sourceSets.main.java.srcDirs += ["../java/vendor"] + } buildDir = new File(rootProject.buildDir, project.name) buildTypes { @@ -37,4 +40,7 @@ android { dependencies { api fileTree(include: ['*.jar'], dir: '../java/libs') + if(project.hasProperty("PROP_ENABLE_GOOGLE_BILLING") && project.PROP_ENABLE_GOOGLE_BILLING.toBoolean()) { + implementation "com.android.billingclient:billing:7.1.0" + } } diff --git a/native/cocos/platform/java/jni/JniGoogleBillingHandler.cpp b/native/cocos/platform/java/jni/JniGoogleBillingHandler.cpp new file mode 100644 index 00000000000..a27ce9f0aa6 --- /dev/null +++ b/native/cocos/platform/java/jni/JniGoogleBillingHandler.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** + Copyright (c) 2024 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 documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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. +****************************************************************************/ + +#include "platform/java/jni/JniHelper.h" +#include +#include "vendor/google/billing/GoogleBillingHelper.h" + +extern "C" { + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onBillingSetupFinished(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onBillingSetupFinished(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onBillingServiceDisconnected(JNIEnv *env, jclass clazz) { + cc::GoogleBillingHelper::onBillingServiceDisconnected(env, clazz); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onProductDetailsResponse(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject productDetailsListObj, + jint startID) { + cc::GoogleBillingHelper::onProductDetailsResponse(env, clazz, billingResultObj, productDetailsListObj, startID); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onPurchasesUpdated(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject purchaseListObj, + jint startID) { + cc::GoogleBillingHelper::onPurchasesUpdated(env, clazz, billingResultObj, purchaseListObj, startID); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onConsumeResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { + cc::GoogleBillingHelper::onConsumeResponse(env, clazz, billingResultObj, purchaseToken); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, + jobject purchaseListObj, jint startID) { + cc::GoogleBillingHelper::onQueryPurchasesResponse(env, clazz, billingResultObj, purchaseListObj, startID); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onAcknowledgePurchaseResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onAcknowledgePurchaseResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onBillingConfigResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { + cc::GoogleBillingHelper::onBillingConfigResponse(env, clazz, billingResultObj, billingConfigObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onAlternativeBillingOnlyTokenResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { + cc::GoogleBillingHelper::onAlternativeBillingOnlyTokenResponse(env, clazz, billingResultObj, alternativeBillingOnlyReportingDetailsObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onExternalOfferReportingDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { + cc::GoogleBillingHelper::onExternalOfferReportingDetailsResponse(env, clazz, billingResultObj, externalOfferReportingDetailsObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onAlternativeBillingOnlyAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onAlternativeBillingOnlyAvailabilityResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onExternalOfferAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onExternalOfferAvailabilityResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onAlternativeBillingOnlyInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onAlternativeBillingOnlyInformationDialogResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onExternalOfferInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj) { + cc::GoogleBillingHelper::onExternalOfferInformationDialogResponse(env, clazz, billingResultObj); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_google_billing_GoogleBillingHelper_onInAppMessageResponse(JNIEnv *env, jclass clazz, jobject inAppMessageResultObj) { + cc::GoogleBillingHelper::onInAppMessageResponse(env, clazz, inAppMessageResultObj); +} +} diff --git a/native/vendor/google/billing/GoogleBilling.cpp b/native/vendor/google/billing/GoogleBilling.cpp new file mode 100644 index 00000000000..04155f2ebb1 --- /dev/null +++ b/native/vendor/google/billing/GoogleBilling.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** + Copyright (c) 2024 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 documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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. +****************************************************************************/ + +#include "vendor/google/billing/GoogleBilling.h" +#include "vendor/google/billing/GoogleBillingHelper.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/JniImp.h" + +namespace cc { + +ProductDetails::~ProductDetails() { + for (auto* ptr : subscriptionOfferDetails) { + delete ptr; + } + subscriptionOfferDetails.clear(); + GoogleBillingHelper::removeProductDetails(_id); +} + +Purchase::~Purchase() { + GoogleBillingHelper::removePurchase(_id); +} + +void GoogleBilling::startConnection() { + GoogleBillingHelper::startConnection(); +} + +void GoogleBilling::endConnection() { + GoogleBillingHelper::endConnection(); +} + +int GoogleBilling::getConnectionState() const { + return GoogleBillingHelper::getConnectionState(); +} + +bool GoogleBilling::isReady() const { + return GoogleBillingHelper::isReady(); +} + +void GoogleBilling::queryProductDetailsParams(const std::vector& productIds, const std::string& type) { + GoogleBillingHelper::queryProductDetailsParams(productIds, type); +} + +void GoogleBilling::launchBillingFlow(const std::vector& productDetailsList, const std::string& selectedOfferToken) { + GoogleBillingHelper::launchBillingFlow(productDetailsList, selectedOfferToken); +} + +void GoogleBilling::consumePurchases(const std::vector& purchases) { + GoogleBillingHelper::consumePurchases(purchases); +} + +void GoogleBilling::acknowledgePurchase(const std::vector& purchases) { + GoogleBillingHelper::acknowledgePurchase(purchases); +} + +void GoogleBilling::queryPurchasesAsync(const std::string& productType) { + GoogleBillingHelper::queryPurchasesAsync(productType); +} + +void GoogleBilling::getBillingConfigAsync() { + GoogleBillingHelper::getBillingConfigAsync(); +} + +BillingResult* GoogleBilling::isFeatureSupported(const std::string& feature) { + return GoogleBillingHelper::isFeatureSupported(feature); +} + +void GoogleBilling::createAlternativeBillingOnlyReportingDetailsAsync() { + GoogleBillingHelper::createAlternativeBillingOnlyReportingDetailsAsync(); +} + +void GoogleBilling::isAlternativeBillingOnlyAvailableAsync() { + GoogleBillingHelper::isAlternativeBillingOnlyAvailableAsync(); +} + +void GoogleBilling::createExternalOfferReportingDetailsAsync() { + GoogleBillingHelper::createExternalOfferReportingDetailsAsync(); +} + +void GoogleBilling::isExternalOfferAvailableAsync() { + GoogleBillingHelper::isExternalOfferAvailableAsync(); +} + +BillingResult* GoogleBilling::showAlternativeBillingOnlyInformationDialog() { + return GoogleBillingHelper::showAlternativeBillingOnlyInformationDialog(); +} + +BillingResult* GoogleBilling::showExternalOfferInformationDialog() { + return GoogleBillingHelper::showExternalOfferInformationDialog(); +} + +BillingResult* GoogleBilling::showInAppMessages() { + return GoogleBillingHelper::showInAppMessages(); +} + +} // namespace cc diff --git a/native/vendor/google/billing/GoogleBilling.h b/native/vendor/google/billing/GoogleBilling.h new file mode 100644 index 00000000000..b394775f2f7 --- /dev/null +++ b/native/vendor/google/billing/GoogleBilling.h @@ -0,0 +1,192 @@ +/**************************************************************************** + Copyright (c) 2024 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 documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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. +****************************************************************************/ + +#pragma once + +#include + +#include "base/Macros.h" +#include "base/RefCounted.h" + +namespace cc { + +class CC_DLL BillingResult : public cc::RefCounted { +public: + int responseCode{0}; + std::string debugMessage; + std::string toString; +}; + +class CC_DLL OneTimePurchaseOfferDetails { +public: + long priceAmountMicros; + std::string formattedPrice; + std::string priceCurrencyCode; +}; + +class CC_DLL InstallmentPlanDetails { +public: + int installmentPlanCommitmentPaymentsCount; + int subsequentInstallmentPlanCommitmentPaymentsCount; +}; + +class CC_DLL PricingPhase { +public: + int billingCycleCount; + long priceAmountMicros; + int recurrenceMode; + std::string billingPeriod; + std::string formattedPrice; + std::string priceCurrencyCode; +}; + +class CC_DLL PricingPhases { +public: + ~PricingPhases() { + for (auto* pricingPhase : pricingPhaseList) { + delete pricingPhase; + } + pricingPhaseList.clear(); + } + std::vector pricingPhaseList; +}; + +class CC_DLL SubscriptionOfferDetails { +public: + std::string basePlanId; + std::string offerId; + std::string offerToken; + std::vector offerTags; + std::unique_ptr pricingPhases; + std::unique_ptr installmentPlanDetails; +}; + +class CC_DLL ProductDetails : public cc::RefCounted { +public: + ~ProductDetails() override; + bool equals(const ProductDetails& other) const { + return hashCode == other.hashCode; + } + int _id; // This is an ID that is not visible to ts and is used to free the java object. + int hashCode; + std::string description; + std::string name; + std::string productId; + std::string productType; + std::string title; + std::string toString; + std::unique_ptr oneTimePurchaseOfferDetails; + std::vector subscriptionOfferDetails; +}; + + +class CC_DLL AccountIdentifiers { +public: + std::string obfuscatedAccountId; + std::string obfuscatedProfileId; +}; + +class CC_DLL PendingPurchaseUpdate { +public: + std::string purchaseToken; + std::vector products; +}; + +class CC_DLL Purchase : public cc::RefCounted { +public: + ~Purchase() override; + bool equals(const Purchase& other) const { + return hashCode == other.hashCode; + } + int _id; // This is an ID that is not visible to ts and is used to free the java object. + bool isAcknowledged; + bool isAutoRenewing; + int purchaseState; + int hashCode; + int quantity; + long purchaseTime; + std::string developerPayload; + std::string orderId; + std::string originalJson; + std::string packageName; + std::string purchaseToken; + std::string signature; + std::string toString; + std::unique_ptr accountIdentifiers; + std::unique_ptr pendingPurchaseUpdate; + std::vector products; +}; + +class CC_DLL BillingConfig : public cc::RefCounted { +public: + std::string countryCode; +}; + +class AlternativeBillingOnlyReportingDetails : public cc::RefCounted{ +public: + std::string externalTransactionToken; +}; + +class ExternalOfferReportingDetails : public cc::RefCounted{ +public: + std::string externalTransactionToken; +}; + +class InAppMessageResult : public cc::RefCounted{ +public: + int responseCode; + std::string purchaseToken; +}; + +class CC_DLL GoogleBilling { +public: + static GoogleBilling &getInstance() { + static GoogleBilling instance; + return instance; + } + + void startConnection(); + void endConnection(); + int getConnectionState() const; + bool isReady() const; + void queryProductDetailsParams(const std::vector& productIds, const std::string& type); + void launchBillingFlow(const std::vector& productDetails, const std::string& selectedOfferToken); + void consumePurchases(const std::vector& purchase); + void acknowledgePurchase(const std::vector& purchase); + void queryPurchasesAsync(const std::string& productType); + void getBillingConfigAsync(); + + void createAlternativeBillingOnlyReportingDetailsAsync(); + void isAlternativeBillingOnlyAvailableAsync(); + void createExternalOfferReportingDetailsAsync(); + void isExternalOfferAvailableAsync(); + BillingResult* isFeatureSupported(const std::string& feature); + BillingResult* showAlternativeBillingOnlyInformationDialog(); + BillingResult* showExternalOfferInformationDialog(); + BillingResult* showInAppMessages(); +private: + GoogleBilling() = default; +}; + +} // namespace cc diff --git a/native/vendor/google/billing/GoogleBillingHelper.cpp b/native/vendor/google/billing/GoogleBillingHelper.cpp new file mode 100644 index 00000000000..fc34a726e39 --- /dev/null +++ b/native/vendor/google/billing/GoogleBillingHelper.cpp @@ -0,0 +1,596 @@ +/**************************************************************************** + Copyright (c) 2024 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 documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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. +****************************************************************************/ +#include "vendor/google/billing/GoogleBillingHelper.h" +#include "vendor/google/billing/GoogleBilling.h" +#include "platform/java/jni/JniHelper.h" + +#include +#include "base/UTF8.h" +#include "base/memory/Memory.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/jswrapper/Value.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global_init.h" + +namespace { + +#ifndef JCLS_BILLING + #define JCLS_BILLING "google/billing/GoogleBillingHelper" +#endif +}; // namespace + +namespace cc { +namespace { +std::string callStringMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName) { + jmethodID methodId = env->GetMethodID(clazz, methodName, "()Ljava/lang/String;"); + jobject jStringObj = env->CallObjectMethod(obj, methodId); + if (jStringObj != nullptr) { + return cc::StringUtils::getStringUTFCharsJNI(env, static_cast(jStringObj)); + } + return ""; +} +jobject callObjectMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName, const char* returnType) { + std::string returnSign = cc::StringUtils::format("()L%s", returnType); + jmethodID methodId = env->GetMethodID(clazz, methodName, returnSign.c_str()); + return env->CallObjectMethod(obj, methodId); +} + +int callIntMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName) { + jmethodID methodId = env->GetMethodID(clazz, methodName, "()I"); + return env->CallIntMethod(obj, methodId); +} + +bool callBooleanMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName) { + jmethodID methodId = env->GetMethodID(clazz, methodName, "()Z"); + return env->CallBooleanMethod(obj, methodId); +} + +long callLongMethod(JNIEnv* env, jclass clazz, jobject obj, const char* methodName) { + jmethodID methodId = env->GetMethodID(clazz, methodName, "()J"); + return env->CallLongMethod(obj, methodId); +} +} // namespace + +template +void callJSfunc(const char* jsFunctionName, Args&&... inargs) { // NOLINT(readability-identifier-naming) + if (!se::ScriptEngine::getInstance()->isValid()) { + return; + } + se::AutoHandleScope scope; + se::ValueArray args; + args.resize(sizeof...(Args)); + nativevalue_to_se_args_v(args, inargs...); + se::Value func; + __jsbObj->getProperty(jsFunctionName, &func); + if (func.isObject() && func.toObject()->isFunction()) { + func.toObject()->call(args, nullptr); + } + return; +} + +void callJSfunc(const char* jsFunctionName) { // NOLINT(readability-identifier-naming) + if (!se::ScriptEngine::getInstance()->isValid()) { + return; + } + se::AutoHandleScope scope; + se::ValueArray args; + se::Value func; + __jsbObj->getProperty(jsFunctionName, &func); + if (func.isObject() && func.toObject()->isFunction()) { + func.toObject()->call(args, nullptr); + } +} + +template void callJSfunc(const char*, BillingResult*&&); +template void callJSfunc(const char*, BillingResult*&&, const std::vector&); +template void callJSfunc(const char*, BillingResult*&&, const std::vector&); +template void callJSfunc(const char*, BillingResult*&&, const std::string&); +template void callJSfunc(const char*, BillingResult*&&, BillingConfig*&&); +template void callJSfunc(const char*, BillingResult*&&, AlternativeBillingOnlyReportingDetails*&&); +template void callJSfunc(const char*, BillingResult*&&, ExternalOfferReportingDetails*&&); +template void callJSfunc(const char*, InAppMessageResult*&&); + +void GoogleBillingHelper::removeProductDetails(int productDetailsID) { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "removeProductDetails", productDetailsID); +} + +void GoogleBillingHelper::removePurchase(int purchaseID) { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "removePurchase", purchaseID); +} + + +void GoogleBillingHelper::startConnection() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "startConnection"); +} + +void GoogleBillingHelper::endConnection() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "endConnection"); +} + +int GoogleBillingHelper::getConnectionState() { + return JniHelper::callStaticIntMethod(JCLS_BILLING, "getConnectionState"); +} + +bool GoogleBillingHelper::isReady() { + return JniHelper::callStaticBooleanMethod(JCLS_BILLING, "isReady"); +} + +void GoogleBillingHelper::queryProductDetailsParams(const std::vector& productIds, const std::string& type) { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "queryProductDetailsParams", productIds, type); +} + +void GoogleBillingHelper::queryPurchasesAsync(const std::string& productType) { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "queryPurchasesAsync", productType); +} + +void GoogleBillingHelper::getBillingConfigAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "getBillingConfigAsync"); +} + +void GoogleBillingHelper::createAlternativeBillingOnlyReportingDetailsAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "createAlternativeBillingOnlyReportingDetailsAsync"); +} + +void GoogleBillingHelper::isAlternativeBillingOnlyAvailableAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "isAlternativeBillingOnlyAvailableAsync"); +} + +void GoogleBillingHelper::createExternalOfferReportingDetailsAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "createExternalOfferReportingDetailsAsync"); +} + +void GoogleBillingHelper::isExternalOfferAvailableAsync() { + JniHelper::callStaticVoidMethod(JCLS_BILLING, "isExternalOfferAvailableAsync"); +} + +BillingResult* GoogleBillingHelper::showAlternativeBillingOnlyInformationDialog() { + return callFunctionAndReturnBillingResult("showAlternativeBillingOnlyInformationDialog"); +} + +BillingResult* GoogleBillingHelper::showExternalOfferInformationDialog() { + return callFunctionAndReturnBillingResult("showExternalOfferInformationDialog"); +} + +BillingResult* GoogleBillingHelper::showInAppMessages() { + return callFunctionAndReturnBillingResult("showInAppMessages"); +} + +void GoogleBillingHelper::launchBillingFlow(const std::vector& productDetailsList, const std::string& selectedOfferToken) { + if (productDetailsList.empty()) { + return; + } + auto* env = JniHelper::getEnv(); + const int size = productDetailsList.size(); + jintArray result = env->NewIntArray(size); + jint* buf = new jint[size]; + for (int i = 0; i < size; ++i) { + buf[i] = productDetailsList[i]->_id; + } + env->SetIntArrayRegion(result, 0, size, buf); + delete[] buf; + cc::JniMethodInfo t; + cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, "launchBillingFlow", "([ILjava/lang/String;)V"); + jstring offerToken = env->NewStringUTF(selectedOfferToken.c_str()); + t.env->CallStaticVoidMethod(t.classID, t.methodID, result, offerToken); +} + +void GoogleBillingHelper::consumePurchases(const std::vector& purchases) { + if (purchases.empty()) { + return; + } + auto* env = JniHelper::getEnv(); + const int size = purchases.size(); + jintArray result = env->NewIntArray(size); + jint* buf = new jint[size]; + for (int i = 0; i < size; ++i) { + buf[i] = purchases[i]->_id; + } + env->SetIntArrayRegion(result, 0, size, buf); + delete[] buf; + cc::JniMethodInfo t; + cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, "consumePurchases", "([I)V"); + t.env->CallStaticVoidMethod(t.classID, t.methodID, result); +} + +void GoogleBillingHelper::acknowledgePurchase(const std::vector& purchases) { + if (purchases.empty()) { + return; + } + auto* env = JniHelper::getEnv(); + const int size = purchases.size(); + jintArray result = env->NewIntArray(size); + jint* buf = new jint[size]; + for (int i = 0; i < size; ++i) { + buf[i] = purchases[i]->_id; + } + env->SetIntArrayRegion(result, 0, size, buf); + delete[] buf; + cc::JniMethodInfo t; + cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, "acknowledgePurchases", "([I)V"); + t.env->CallStaticVoidMethod(t.classID, t.methodID, result); +} + +BillingResult* GoogleBillingHelper::callFunctionAndReturnBillingResult(const std::string& functionName) { + auto* env = JniHelper::getEnv(); + cc::JniMethodInfo t; + if (cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, functionName.c_str(), "()Lcom/android/billingclient/api/BillingResult;")) { + jobject obj = t.env->CallStaticObjectMethod(t.classID, t.methodID); + return toBillingResult(env, obj); + } + return nullptr; +} + +BillingResult* GoogleBillingHelper::isFeatureSupported(const std::string& feature) { + auto* env = JniHelper::getEnv(); + cc::JniMethodInfo t; + if (cc::JniHelper::getStaticMethodInfo(t, JCLS_BILLING, "isFeatureSupported", "(Ljava/lang/String;)Lcom/android/billingclient/api/BillingResult;")) { + jstring jFeature = cc::StringUtils::newStringUTFJNI(env, feature); + jobject obj = t.env->CallStaticObjectMethod(t.classID, t.methodID, jFeature); + return toBillingResult(env, obj); + } + return nullptr; +} + +void GoogleBillingHelper::onBillingSetupFinished(JNIEnv* env, jclass clazz, jobject billingResultObj) { + cc::callJSfunc("onBillingSetupFinished", toBillingResult(env, billingResultObj)); +} + +void GoogleBillingHelper::onBillingServiceDisconnected(JNIEnv* env, jclass clazz) { + cc::callJSfunc("onBillingServiceDisconnected"); +} + +void GoogleBillingHelper::onProductDetailsResponse(JNIEnv* env, jclass clazz, + jobject billingResultObj, + jobject productDetailsListObj, + jint startID) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + std::vector productDetailsList = cc::GoogleBillingHelper::toProductDetailList(env, productDetailsListObj, startID); + cc::callJSfunc("onProductDetailsResponse", billingResult, productDetailsList); +} + +void GoogleBillingHelper::onPurchasesUpdated(JNIEnv* env, jclass clazz, + jobject billingResultObj, + jobject purchasesListObj, + jint startID) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + if (purchasesListObj != nullptr) { + std::vector purchasesList = cc::GoogleBillingHelper::toPurchaseList(env, purchasesListObj, startID); + cc::callJSfunc("onPurchasesUpdated", billingResult, purchasesList); + } else { + cc::callJSfunc("onPurchasesUpdated", billingResult, std::vector()); + } +} + +void GoogleBillingHelper::onConsumeResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jstring purchaseToken) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onConsumeResponse", billingResult, cc::StringUtils::getStringUTFCharsJNI(env, static_cast(purchaseToken))); +} + +void GoogleBillingHelper::onQueryPurchasesResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject purchasesListObj, jint startID) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + std::vector purchasesList = cc::GoogleBillingHelper::toPurchaseList(env, purchasesListObj, startID); + cc::callJSfunc("onQueryPurchasesResponse", billingResult, purchasesList); +} + +void GoogleBillingHelper::onAcknowledgePurchaseResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onAcknowledgePurchaseResponse", billingResult); +} + +void GoogleBillingHelper::onBillingConfigResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject billingConfigObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + cc::BillingConfig* billingConfig = nullptr; + if (billingConfigObj) { + billingConfig = cc::GoogleBillingHelper::toBillingConfig(env, billingConfigObj); + } + cc::callJSfunc("onBillingConfigResponse", billingResult, billingConfig); +} +void GoogleBillingHelper::onAlternativeBillingOnlyTokenResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + cc::AlternativeBillingOnlyReportingDetails* toAlternativeBillingOnlyReporting = nullptr; + if (alternativeBillingOnlyReportingDetailsObj) { + toAlternativeBillingOnlyReporting = cc::GoogleBillingHelper::toAlternativeBillingOnlyReportingDetails(env, alternativeBillingOnlyReportingDetailsObj); + } + cc::callJSfunc("onAlternativeBillingOnlyTokenResponse", billingResult, toAlternativeBillingOnlyReporting); +} + +void GoogleBillingHelper::onExternalOfferReportingDetailsResponse(JNIEnv* env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + cc::ExternalOfferReportingDetails* externalOfferReportingDetails = nullptr; + if (externalOfferReportingDetailsObj) { + externalOfferReportingDetails = cc::GoogleBillingHelper::toExternalOfferReportingDetails(env, externalOfferReportingDetailsObj); + } + cc::callJSfunc("onExternalOfferReportingDetailsResponse", billingResult, externalOfferReportingDetails); +} + +void GoogleBillingHelper::onAlternativeBillingOnlyAvailabilityResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onAlternativeBillingOnlyAvailabilityResponse", billingResult); +} + +void GoogleBillingHelper::onExternalOfferAvailabilityResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onExternalOfferAvailabilityResponse", billingResult); +} + +void GoogleBillingHelper::onAlternativeBillingOnlyInformationDialogResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onAlternativeBillingOnlyInformationDialogResponse", billingResult); +} + +void GoogleBillingHelper::onExternalOfferInformationDialogResponse(JNIEnv* env, jclass clazz, jobject billingResultObj) { + auto* billingResult = cc::GoogleBillingHelper::toBillingResult(env, billingResultObj); + cc::callJSfunc("onExternalOfferInformationDialogResponse", billingResult); +} + +void GoogleBillingHelper::onInAppMessageResponse(JNIEnv* env, jclass clazz, jobject inAppMessageResultObj) { + auto* inAppMessageResult = cc::GoogleBillingHelper::toInAppMessageResult(env, inAppMessageResultObj); + cc::callJSfunc("onInAppMessageResponse", inAppMessageResult); +} + +cc::BillingResult* GoogleBillingHelper::toBillingResult(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* billingResult = ccnew cc::BillingResult; + billingResult->debugMessage = callStringMethod(env, clazz, obj, "getDebugMessage"); + billingResult->responseCode = callIntMethod(env, clazz, obj, "getResponseCode"); + billingResult->toString = callStringMethod(env, clazz, obj, "toString"); + return billingResult; +} + +cc::OneTimePurchaseOfferDetails* GoogleBillingHelper::toOneTimePurchaseOfferDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto oneTimePurchaseOfferDetails = new cc::OneTimePurchaseOfferDetails; + oneTimePurchaseOfferDetails->formattedPrice = callStringMethod(env, clazz, obj, "getFormattedPrice"); + oneTimePurchaseOfferDetails->priceAmountMicros = callLongMethod(env, clazz, obj, "getPriceAmountMicros"); + oneTimePurchaseOfferDetails->priceCurrencyCode = callStringMethod(env, clazz, obj, "getPriceCurrencyCode"); + return oneTimePurchaseOfferDetails; +} + +cc::InstallmentPlanDetails* GoogleBillingHelper::toInstallmentPlanDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* installmentPlanDetails = new cc::InstallmentPlanDetails; + installmentPlanDetails->installmentPlanCommitmentPaymentsCount = callIntMethod(env, clazz, obj, "getInstallmentPlanCommitmentPaymentsCount"); + installmentPlanDetails->subsequentInstallmentPlanCommitmentPaymentsCount = callIntMethod(env, clazz, obj, "subsequentInstallmentPlanCommitmentPaymentsCount"); + return installmentPlanDetails; +} + +cc::PricingPhase* GoogleBillingHelper::toPricingPhase(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + + auto* pricingPhase = new cc::PricingPhase; + pricingPhase->billingCycleCount = callIntMethod(env, clazz, obj, "getBillingCycleCount"); + pricingPhase->billingPeriod = callStringMethod(env, clazz, obj, "getBillingPeriod"); + pricingPhase->formattedPrice = callStringMethod(env, clazz, obj, "getFormattedPrice"); + pricingPhase->priceAmountMicros = callLongMethod(env, clazz, obj, "getPriceAmountMicros"); + pricingPhase->priceCurrencyCode = callStringMethod(env, clazz, obj, "getPriceCurrencyCode"); + pricingPhase->recurrenceMode = callIntMethod(env, clazz, obj, "getRecurrenceMode"); + return pricingPhase; +} + +cc::PricingPhases* GoogleBillingHelper::toPricingPhases(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + + auto* pricingPhases = new cc::PricingPhases; + jmethodID methodId = env->GetMethodID(clazz, "getPricingPhaseList", "()Ljava/util/List;"); + jobject listObj = env->CallObjectMethod(obj, methodId); + jclass listClazz = env->GetObjectClass(listObj); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + for (int i = 0; i < size; ++i) { + jobject pricingPhaseObj = env->CallObjectMethod(listObj, listGetMethod, i); + cc::PricingPhase* pricingPhase = toPricingPhase(env, pricingPhaseObj); + pricingPhases->pricingPhaseList.push_back(pricingPhase); + } + return pricingPhases; +} + +cc::SubscriptionOfferDetails* GoogleBillingHelper::toSubscriptionOfferDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* details = ccnew cc::SubscriptionOfferDetails; + details->basePlanId = callStringMethod(env, clazz, obj, + "getBasePlanId"); + + jobject installmentPlanDetailsObj = callObjectMethod(env, clazz, obj, "getInstallmentPlanDetails", "com/android/billingclient/api/ProductDetails$InstallmentPlanDetails;"); + if (installmentPlanDetailsObj != nullptr) { + details->installmentPlanDetails.reset(toInstallmentPlanDetails(env, installmentPlanDetailsObj)); + } + details->offerId = callStringMethod(env, clazz, obj, "getOfferId"); + + jmethodID methodId = env->GetMethodID(clazz, "getOfferTags", "()Ljava/util/List;"); + jobject listObj = env->CallObjectMethod(obj, methodId); + jclass listClazz = env->GetObjectClass(listObj); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + for (int i = 0; i < size; ++i) { + jobject strObj = env->CallObjectMethod(listObj, listGetMethod, i); + details->offerTags.push_back(cc::StringUtils::getStringUTFCharsJNI(env, static_cast(strObj))); + } + details->offerToken = callStringMethod(env, clazz, obj, "getOfferToken"); + jobject pricingPhasesObj = callObjectMethod(env, clazz, obj, "getPricingPhases", "com/android/billingclient/api/ProductDetails$PricingPhases;"); + details->pricingPhases.reset(toPricingPhases(env, pricingPhasesObj)); + + return details; +} + +std::vector GoogleBillingHelper::toProductDetailList(JNIEnv* env, jobject productListObj, jint startID) { + jclass clazz = env->GetObjectClass(productListObj); + jmethodID listGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, clazz, productListObj, "size"); + std::vector productDetailsList; + for (int i = 0; i < size; ++i) { + jobject productDetailObj = env->CallObjectMethod(productListObj, listGetMethod, i); + cc::ProductDetails* productDetails = cc::GoogleBillingHelper::toProductDetail(env, productDetailObj); + productDetails->_id = startID++; + productDetailsList.push_back(productDetails); + } + return std::move(productDetailsList); +} + +cc::ProductDetails* GoogleBillingHelper::toProductDetail(JNIEnv* env, jobject productObj) { + jclass clazz = env->GetObjectClass(productObj); + auto* productDetails = new cc::ProductDetails; + + productDetails->hashCode = callIntMethod(env, clazz, productObj, "hashCode"); + productDetails->description = callStringMethod(env, clazz, productObj, "getDescription"); + productDetails->name = callStringMethod(env, clazz, productObj, "getName"); + productDetails->productId = callStringMethod(env, clazz, productObj, "getProductId"); + productDetails->productType = callStringMethod(env, clazz, productObj, "getProductType"); + productDetails->title = callStringMethod(env, clazz, productObj, "getTitle"); + productDetails->toString = callStringMethod(env, clazz, productObj, "toString"); + + jmethodID methodId = env->GetMethodID(clazz, "getOneTimePurchaseOfferDetails", "()Lcom/android/billingclient/api/ProductDetails$OneTimePurchaseOfferDetails;"); + jobject oneTimePurchaseOfferDetailsObj = env->CallObjectMethod(productObj, methodId); + if (oneTimePurchaseOfferDetailsObj != nullptr) { + productDetails->oneTimePurchaseOfferDetails.reset(toOneTimePurchaseOfferDetails(env, oneTimePurchaseOfferDetailsObj)); + } + + jmethodID getSubscriptionOfferDetails = env->GetMethodID(clazz, "getSubscriptionOfferDetails", "()Ljava/util/List;"); + jobject listObj = env->CallObjectMethod(productObj, getSubscriptionOfferDetails); + if (listObj != nullptr) { + jclass listClazz = env->GetObjectClass(listObj); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + std::vector details; + for (int i = 0; i < size; ++i) { + jobject subscriptionOfferDetailsObj = env->CallObjectMethod(listObj, listGetMethod, i); + cc::SubscriptionOfferDetails* detail = toSubscriptionOfferDetails(env, subscriptionOfferDetailsObj); + productDetails->subscriptionOfferDetails.push_back(detail); + } + } + + return productDetails; +} + +cc::AccountIdentifiers* GoogleBillingHelper::toAccountIdentifiers(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* accountIdentifiers = new cc::AccountIdentifiers; + accountIdentifiers->obfuscatedAccountId = callStringMethod(env, clazz, obj, "getObfuscatedAccountId"); + accountIdentifiers->obfuscatedProfileId = callStringMethod(env, clazz, obj, "getObfuscatedProfileId"); + return accountIdentifiers; +} + +cc::PendingPurchaseUpdate* GoogleBillingHelper::toPendingPurchaseUpdate(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* pendingPurchaseUpdate = new cc::PendingPurchaseUpdate; + jmethodID methodId = env->GetMethodID(clazz, "getProducts", "()Ljava/util/ArrayList;"); + jobject listObj = env->CallObjectMethod(obj, methodId); + jclass listClazz = env->GetObjectClass(listObj); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/String;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + for (int i = 0; i < size; ++i) { + jobject strObj = env->CallObjectMethod(listObj, listGetMethod, i); + pendingPurchaseUpdate->products.push_back(cc::StringUtils::getStringUTFCharsJNI(env, static_cast(strObj))); + } + pendingPurchaseUpdate->purchaseToken = callStringMethod(env, clazz, obj, "getPurchaseToken"); + return pendingPurchaseUpdate; +} + +std::vector GoogleBillingHelper::toPurchaseList(JNIEnv* env, jobject productsListObj, jint startID) { + jclass clazz = env->GetObjectClass(productsListObj); + jmethodID listGetMethod = env->GetMethodID(clazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, clazz, productsListObj, "size"); + std::vector purchases; + for (int i = 0; i < size; ++i) { + jobject purchaseObj = env->CallObjectMethod(productsListObj, listGetMethod, i); + cc::Purchase* purchase = cc::GoogleBillingHelper::toPurchase(env, purchaseObj); + purchase->_id = startID++; + purchases.push_back(purchase); + } + return std::move(purchases); +} + +cc::Purchase* GoogleBillingHelper::toPurchase(JNIEnv* env, jobject purchaseObj) { + jclass clazz = env->GetObjectClass(purchaseObj); + auto* purchase = new cc::Purchase; + + jmethodID getAccountIdentifiers = env->GetMethodID(clazz, "getAccountIdentifiers", "()Lcom/android/billingclient/api/AccountIdentifiers;"); + jobject accountIdentifiersObj = env->CallObjectMethod(purchaseObj, getAccountIdentifiers); + if (accountIdentifiersObj) { + purchase->accountIdentifiers.reset(toAccountIdentifiers(env, accountIdentifiersObj)); + } + + purchase->developerPayload = callStringMethod(env, clazz, purchaseObj, "getDeveloperPayload"); + purchase->orderId = callStringMethod(env, clazz, purchaseObj, "getOrderId"); + purchase->originalJson = callStringMethod(env, clazz, purchaseObj, "getOriginalJson"); + purchase->packageName = callStringMethod(env, clazz, purchaseObj, "getPackageName"); + + jmethodID getPendingPurchaseUpdate = env->GetMethodID(clazz, "getPendingPurchaseUpdate", "()Lcom/android/billingclient/api/Purchase$PendingPurchaseUpdate;"); + jobject pendingPurchaseUpdateObj = env->CallObjectMethod(purchaseObj, getPendingPurchaseUpdate); + if (pendingPurchaseUpdateObj) { + purchase->pendingPurchaseUpdate.reset(toPendingPurchaseUpdate(env, pendingPurchaseUpdateObj)); + } + + jmethodID methodId = env->GetMethodID(clazz, "getProducts", "()Ljava/util/List;"); + jobject listObj = env->CallObjectMethod(purchaseObj, methodId); + jclass listClazz = env->GetObjectClass(listObj); + jmethodID listGetMethod = env->GetMethodID(listClazz, "get", "(I)Ljava/lang/Object;"); + int size = callIntMethod(env, listClazz, listObj, "size"); + auto& products = purchase->products; + for (int i = 0; i < size; ++i) { + jobject strObj = env->CallObjectMethod(listObj, listGetMethod, i); + products.push_back(cc::StringUtils::getStringUTFCharsJNI(env, static_cast(strObj))); + } + purchase->purchaseState = callIntMethod(env, clazz, purchaseObj, "getPurchaseState"); + purchase->purchaseTime = callLongMethod(env, clazz, purchaseObj, "getPurchaseTime"); + purchase->purchaseToken = callStringMethod(env, clazz, purchaseObj, "getPurchaseToken"); + purchase->quantity = callIntMethod(env, clazz, purchaseObj, "getQuantity"); + purchase->signature = callStringMethod(env, clazz, purchaseObj, "getSignature"); + purchase->hashCode = callIntMethod(env, clazz, purchaseObj, "hashCode"); + purchase->isAcknowledged = callBooleanMethod(env, clazz, purchaseObj, "isAcknowledged"); + purchase->isAutoRenewing = callBooleanMethod(env, clazz, purchaseObj, "isAutoRenewing"); + purchase->toString = callStringMethod(env, clazz, purchaseObj, "toString"); + return purchase; +} + +cc::BillingConfig* GoogleBillingHelper::toBillingConfig(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* billingConfig = new cc::BillingConfig; + billingConfig->countryCode = callStringMethod(env, clazz, obj, "getCountryCode"); + return billingConfig; +} + +cc::AlternativeBillingOnlyReportingDetails* GoogleBillingHelper::toAlternativeBillingOnlyReportingDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* alternativeBillingOnlyReportingDetails = new cc::AlternativeBillingOnlyReportingDetails; + alternativeBillingOnlyReportingDetails->externalTransactionToken = callStringMethod(env, clazz, obj, "getExternalTransactionToken"); + return alternativeBillingOnlyReportingDetails; +} + +cc::ExternalOfferReportingDetails* GoogleBillingHelper::toExternalOfferReportingDetails(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* externalOfferReportingDetails = new cc::ExternalOfferReportingDetails; + externalOfferReportingDetails->externalTransactionToken = callStringMethod(env, clazz, obj, "getExternalTransactionToken"); + return externalOfferReportingDetails; +} + +cc::InAppMessageResult* GoogleBillingHelper::toInAppMessageResult(JNIEnv* env, jobject obj) { + jclass clazz = env->GetObjectClass(obj); + auto* inAppMessageResult = new cc::InAppMessageResult; + inAppMessageResult->responseCode = callIntMethod(env, clazz, obj, "getResponseCode"); + inAppMessageResult->purchaseToken = callStringMethod(env, clazz, obj, "getPurchaseToken"); + return inAppMessageResult; +} + +} // namespace cc diff --git a/native/vendor/google/billing/GoogleBillingHelper.h b/native/vendor/google/billing/GoogleBillingHelper.h new file mode 100644 index 00000000000..bd73a35a9b5 --- /dev/null +++ b/native/vendor/google/billing/GoogleBillingHelper.h @@ -0,0 +1,117 @@ +/**************************************************************************** + Copyright (c) 2024 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 documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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. +****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include "base/Macros.h" + +namespace cc { +class BillingResult; +class ProductDetails; +class Purchase; +class OneTimePurchaseOfferDetails; +class InstallmentPlanDetails; +class PricingPhase; +class PricingPhases; +class SubscriptionOfferDetails; +class AccountIdentifiers; +class PendingPurchaseUpdate; +class BillingConfig; +class AlternativeBillingOnlyReportingDetails; +class ExternalOfferReportingDetails; +class InAppMessageResult; + +class CC_DLL GoogleBillingHelper { +public: + static void removeProductDetails(int productDetailsID); + static void removePurchase(int purchaseID); + static void startConnection(); + static void endConnection(); + static int getConnectionState(); + static bool isReady(); + static void queryProductDetailsParams(const std::vector &productIds, const std::string &type); + + static void launchBillingFlow(const std::vector &productDetailsList, const std::string &selectedOfferToken); + static void consumePurchases(const std::vector &purchases); + static void acknowledgePurchase(const std::vector &purchases); + static void queryPurchasesAsync(const std::string &productType); + static void getBillingConfigAsync(); + + static void createAlternativeBillingOnlyReportingDetailsAsync(); + static void isAlternativeBillingOnlyAvailableAsync(); + static void createExternalOfferReportingDetailsAsync(); + static void isExternalOfferAvailableAsync(); + static BillingResult *isFeatureSupported(const std::string &feature); + + static BillingResult *showAlternativeBillingOnlyInformationDialog(); + static BillingResult *showExternalOfferInformationDialog(); + static BillingResult *showInAppMessages(); + + static void onBillingSetupFinished(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onBillingServiceDisconnected(JNIEnv *env, jclass clazz); + static void onProductDetailsResponse(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject productDetailsListObj, + jint startID); + static void onPurchasesUpdated(JNIEnv *env, jclass clazz, + jobject billingResultObj, + jobject purchaseListObj, + jint startID); + static void onConsumeResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jstring purchaseToken); + static void onQueryPurchasesResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject purchaseListObj, jint startID); + static void onAcknowledgePurchaseResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onBillingConfigResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject billingConfigObj); + static void onAlternativeBillingOnlyTokenResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject alternativeBillingOnlyReportingDetailsObj); + static void onExternalOfferReportingDetailsResponse(JNIEnv *env, jclass clazz, jobject billingResultObj, jobject externalOfferReportingDetailsObj); + static void onAlternativeBillingOnlyAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onExternalOfferAvailabilityResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onAlternativeBillingOnlyInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onExternalOfferInformationDialogResponse(JNIEnv *env, jclass clazz, jobject billingResultObj); + static void onInAppMessageResponse(JNIEnv *env, jclass clazz, jobject inAppMessageResultObj); + +private: + static BillingResult *toBillingResult(JNIEnv *env, jobject obj); + static std::vector toProductDetailList(JNIEnv *env, jobject productsObj, jint startID); + static std::vector toPurchaseList(JNIEnv *env, jobject productsObj, jint startID); + static BillingConfig *toBillingConfig(JNIEnv *env, jobject billingConfigObj); + static AlternativeBillingOnlyReportingDetails *toAlternativeBillingOnlyReportingDetails(JNIEnv *env, jobject alternativeBillingOnlyReportingDetailsObj); + static ExternalOfferReportingDetails *toExternalOfferReportingDetails(JNIEnv *env, jobject externalOfferReportingDetailsObj); + static InAppMessageResult *toInAppMessageResult(JNIEnv *env, jobject inAppMessageResultObj); + static ProductDetails *toProductDetail(JNIEnv *env, jobject productObj); + static Purchase *toPurchase(JNIEnv *env, jobject purchaseObj); + static OneTimePurchaseOfferDetails *toOneTimePurchaseOfferDetails(JNIEnv *env, jobject obj); + static InstallmentPlanDetails *toInstallmentPlanDetails(JNIEnv *env, jobject obj); + static SubscriptionOfferDetails *toSubscriptionOfferDetails(JNIEnv *env, jobject obj); + static AccountIdentifiers *toAccountIdentifiers(JNIEnv *env, jobject obj); + static PendingPurchaseUpdate *toPendingPurchaseUpdate(JNIEnv *env, jobject obj); + static PricingPhase *toPricingPhase(JNIEnv *env, jobject obj); + static PricingPhases *toPricingPhases(JNIEnv* env, jobject obj); + static BillingResult *callFunctionAndReturnBillingResult(const std::string &functionName); +}; + +} // namespace cc diff --git a/scripts/native-pack-tool/source/base/manager.ts b/scripts/native-pack-tool/source/base/manager.ts index f325cb8f11f..0a43e3c2439 100644 --- a/scripts/native-pack-tool/source/base/manager.ts +++ b/scripts/native-pack-tool/source/base/manager.ts @@ -2,7 +2,7 @@ import { Paths } from "../utils"; import { CocosParams, NativePackTool } from "./default"; -export type ISupportPlatform = 'mac-os' | 'mac' | 'ios' | 'android' | 'ohos'; +export type ISupportPlatform = 'mac-os' | 'mac' | 'ios' | 'android' | 'google-play' | 'ohos'; export class NativePackToolManager { static Paths: Paths; diff --git a/scripts/native-pack-tool/source/index.ts b/scripts/native-pack-tool/source/index.ts index 5dcaf78dd9c..264c64f1786 100644 --- a/scripts/native-pack-tool/source/index.ts +++ b/scripts/native-pack-tool/source/index.ts @@ -2,6 +2,7 @@ import { nativePackToolMg } from './base/manager'; import { MacPackTool } from './platforms/mac'; import { WindowsPackTool } from './platforms/windows'; import { AndroidPackTool } from './platforms/android'; +import { GooglePlayPackTool } from './platforms/google-play'; import { HarmonyOSNextPackTool } from './platforms/harmonyos-next'; import { OHOSPackTool } from './platforms/ohos'; import { IOSPackTool } from './platforms/ios'; @@ -11,6 +12,7 @@ nativePackToolMg.register('ios', new IOSPackTool()); nativePackToolMg.register('mac', new MacPackTool()); nativePackToolMg.register('windows', new WindowsPackTool()); nativePackToolMg.register('android', new AndroidPackTool()); +nativePackToolMg.register('google-play', new GooglePlayPackTool()); nativePackToolMg.register('harmonyos-next', new HarmonyOSNextPackTool()); nativePackToolMg.register('ohos', new OHOSPackTool()); nativePackToolMg.register('huawei-agc', new HuaweiAGCPackTool()); diff --git a/scripts/native-pack-tool/source/platforms/google-play.ts b/scripts/native-pack-tool/source/platforms/google-play.ts new file mode 100644 index 00000000000..dc02b01b25e --- /dev/null +++ b/scripts/native-pack-tool/source/platforms/google-play.ts @@ -0,0 +1,632 @@ +import * as fs from 'fs-extra'; +import * as ps from 'path'; +import { CocosParams, NativePackTool } from "../base/default"; +import { cchelper, Paths } from "../utils"; +import * as URL from 'url'; +import { spawn, spawnSync } from 'child_process'; +import * as xml2js from 'xml2js'; +import { platform } from 'os'; + +export interface IOrientation { + landscapeLeft: boolean; + landscapeRight: boolean; + portrait: boolean; + upsideDown: boolean; +} +/** + * 默认 CustomIconList,存放在 static 下的 icon 路径 + */ +export interface ICustomIconDpi { + fileName: string; + dirName: string; + dpi: number; + path: string; +} + +export interface ICustomIconInfo { + type: string, + display: string, + list: ICustomIconDpi[] +} + +export interface IAndroidParams { + packageName: string; + sdkPath: string; + ndkPath: string; + javaHome: string; + javaPath: string; + androidInstant: boolean, + maxAspectRatio: string; + remoteUrl?: string; + apiLevel: number; + appABIs: string[]; + keystorePassword: string; + keystoreAlias: string; + keystoreAliasPassword: string; + keystorePath: string; + inputSDK: boolean; + + orientation: IOrientation; + appBundle: boolean; + resizeableActivity: boolean; + googleBilling: boolean; + customIconInfo: ICustomIconInfo, +} + +const DefaultAPILevel = 27; + + +export class GooglePlayPackTool extends NativePackTool { + params!: CocosParams; + + protected firstTimeBuild: boolean = false; + + protected async copyPlatformTemplate() { + // 原生工程不重复拷贝 TODO 复用前需要做版本检测 + if (!fs.existsSync(this.paths.nativePrjDir)) { + // 拷贝 lite 仓库的 templates/android/build 文件到构建输出目录 + await fs.copy(ps.join(this.paths.nativeTemplateDirInCocos, this.params.platform, 'build'), this.paths.nativePrjDir, { overwrite: false }); + this.firstTimeBuild = true; + } else { + this.firstTimeBuild = false; + } + // 原生工程不重复拷贝 TODO 复用前需要做版本检测 + if (!fs.existsSync(this.paths.platformTemplateDirInPrj)) { + // 拷贝 lite 仓库的 templates/android/template 文件到构建输出目录 + await fs.copy(ps.join(this.paths.nativeTemplateDirInCocos, this.params.platform, 'template'), this.paths.platformTemplateDirInPrj, { overwrite: false }); + this.writeEngineVersion(); + } else { + this.validateNativeDir(); + } + + // 替换icon + await this.copyCustomIcons(this.params.platformParams.customIconInfo); + } + + /** + * 拷贝自定义 ICON + * @param type - 类型自定义还是默认 + * @param buildDir - 构建路径 + * @param outputName - 用于获取 CustomIcon + */ + protected async copyCustomIcons(customIconInfo: ICustomIconInfo) { + const tasks = customIconInfo.list.map(async (item) => { + const distRoot = ps.join(this.paths.platformTemplateDirInPrj, 'res', item.dirName); + + await fs.ensureDir(distRoot); + const filePath = ps.join(distRoot, item.fileName); + await fs.copy(item.path, filePath); + }); + await Promise.all(tasks); + } + + protected validatePlatformDirectory(missing: string[]): void { + const srcDir = ps.join(this.paths.nativeTemplateDirInCocos, this.params.platform, 'template'); + const dstDir = this.paths.platformTemplateDirInPrj; + this.validateDirectory(srcDir, dstDir, missing); + } + + async create() { + await this.copyCommonTemplate(); + await this.copyPlatformTemplate(); + await this.generateCMakeConfig(); + await this.excuteCocosTemplateTask(); + + await this.updateAndroidGradleValues(); + await this.updateManifest(); + await this.encrypteScripts(); + await this.generateAppNameValues(); + return true; + } + + async make() { + const options = this.params.platformParams; + + if (options.javaHome) { + if (process.env.JAVA_HOME !== options.javaHome) { + process.env.JAVA_HOME = options.javaHome; + console.log(`Update JAVA_HOME to ${options.javaHome}`); + } + if (!process.env.PATH!.startsWith(options.javaHome)) { + const sep = platform() === 'win32' ? ';' : ':'; + process.env.PATH = ps.join(options.javaHome, 'bin') + sep + process.env.PATH; + console.log(`Add JAVA_HOME/bin to PATH`); + } + } + + const projDir: string = this.paths.nativePrjDir; + if (!fs.existsSync(projDir)) { + throw new Error(`dir ${projDir} not exits`); + } + let gradle = 'gradlew'; + if (process.platform === 'win32') { + gradle += '.bat'; + } else { + gradle = './' + gradle; + } + + let buildMode = ''; + const outputMode = this.params.debug ? 'Debug' : 'Release'; + + // compile android + buildMode = `${this.projectNameASCII()}:assemble${outputMode}`; + + // pushd + const originDir = process.cwd(); + try { + process.chdir(projDir); + await cchelper.runCmd(gradle, [buildMode], false, projDir); + } catch (e) { + throw e; + } finally { + // popd + process.chdir(originDir); + } + + + // compile android-instant + if (options.androidInstant) { + buildMode = `instantapp:assemble${outputMode}`; + await cchelper.runCmd(gradle, [buildMode], false, projDir); + } + + // compile google app bundle + if (options.appBundle) { + if (options.androidInstant) { + buildMode = `bundle${outputMode}`; + } else { + buildMode = `${this.params.projectName}:bundle${outputMode}`; + } + await cchelper.runCmd(gradle, [buildMode], false, projDir); + } + return await this.copyToDist(); + } + + /** + * Deprecated, only be compatible with historical packaging tools + */ + protected async setOrientation() { + const cfg = this.params.platformParams.orientation; + const manifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'app/AndroidManifest.xml'); + const instantManifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'instantapp/AndroidManifest.xml'); + if (fs.existsSync(manifestPath) && fs.existsSync(instantManifestPath)) { + const pattern = /android:screenOrientation="[^"]*"/; + let replaceString = 'android:screenOrientation="unspecified"'; + + if (cfg.landscapeRight && cfg.landscapeLeft && (cfg.portrait || cfg.upsideDown)) { + replaceString = 'android:screenOrientation="fullSensor"'; + } else if ((cfg.landscapeRight || cfg.landscapeLeft) && (cfg.portrait || cfg.upsideDown)) { + replaceString = 'android:screenOrientation="unspecified"'; + } else if (cfg.landscapeRight && !cfg.landscapeLeft) { + replaceString = 'android:screenOrientation="landscape"'; + } else if (!cfg.landscapeRight && cfg.landscapeLeft) { + replaceString = 'android:screenOrientation="reverseLandscape"'; + } else if (cfg.landscapeRight && cfg.landscapeLeft) { + replaceString = 'android:screenOrientation="sensorLandscape"'; + } else if (cfg.portrait && !cfg.upsideDown) { + replaceString = 'android:screenOrientation="portrait"'; + } else if (!cfg.portrait && cfg.upsideDown) { + const oriValue = 'reversePortrait'; + replaceString = `android:screenOrientation="${oriValue}"`; + } else if (cfg.portrait && cfg.upsideDown) { + const oriValue = 'sensorPortrait'; + replaceString = `android:screenOrientation="${oriValue}"`; + } + + let content = await fs.readFile(manifestPath, 'utf8'); + content = content.replace(pattern, replaceString); + let instantContent = await fs.readFile(instantManifestPath, 'utf8'); + instantContent = instantContent.replace(pattern, replaceString); + await fs.writeFile(manifestPath, content); + await fs.writeFile(instantManifestPath, instantContent); + } + } + + private mapOrientationValue() { + const orientation = this.params.platformParams.orientation; + let orientationValue = 'unspecified'; + + if (orientation.landscapeRight && orientation.landscapeLeft && (orientation.portrait || orientation.upsideDown)) { + orientationValue = 'fullSensor'; + } else if ((orientation.landscapeRight || orientation.landscapeLeft) && (orientation.portrait || orientation.upsideDown)) { + orientationValue = 'unspecified'; + } else if (orientation.landscapeRight && !orientation.landscapeLeft) { + orientationValue = 'landscape'; + } else if (!orientation.landscapeRight && orientation.landscapeLeft) { + orientationValue = 'reverseLandscape'; + } else if (orientation.landscapeRight && orientation.landscapeLeft) { + orientationValue = 'sensorLandscape'; + } else if (orientation.portrait && !orientation.upsideDown) { + orientationValue = 'portrait'; + } else if (!orientation.portrait && orientation.upsideDown) { + orientationValue = 'reversePortrait'; + } else if (orientation.portrait && orientation.upsideDown) { + orientationValue = 'sensorPortrait'; + } + return orientationValue; + } + + protected async updateManifest() { + + if (!this.firstTimeBuild) { + console.log(`AndroidManifest.xml has already been updated!`); + return; + } + + const resizeableActivity: boolean = this.params.platformParams.resizeableActivity; + const manifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'app/AndroidManifest.xml'); + const instantManifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'instantapp/AndroidManifest.xml'); + + const fnParseXml = async (xmlFile: string) => { + const xmlData = await fs.readFile(xmlFile, 'utf8'); + const data = await xml2js.parseStringPromise(xmlData); + return { + data, + save: async () => { + const builder = new xml2js.Builder(); + const dstXML = builder.buildObject(data); + await fs.writeFile(xmlFile, dstXML, 'utf8'); + } + } + }; + const fnUpdateOrientation = (data: any) => { + const attrRef = data.manifest.application[0].activity[0].$; + attrRef['android:screenOrientation'] = this.mapOrientationValue(); + }; + const fnUpdateResizeableActivity = (data: any) => { + const activityRef = data.manifest.application[0].$; + activityRef['android:resizeableActivity'] = resizeableActivity ? 'true' : 'false'; + }; + const fnUpdateMaxAspectRation = (data: any) => { + if (resizeableActivity) return; // disabled + const maxAspectRatio: string = this.params.platformParams.maxAspectRatio; + if (!maxAspectRatio) return; // value not set + const matchFrac = maxAspectRatio.match(/^(\d+):(\d+)$/); + let aspectRatioFloatValue; + try { + if (matchFrac) { + aspectRatioFloatValue = Number.parseInt(matchFrac[1], 10) / Number.parseInt(matchFrac[2], 10); + } else { + aspectRatioFloatValue = Number.parseFloat(maxAspectRatio); + } + } catch (e) { + console.error(e); + console.error(`Error when parsing '${maxAspectRatio}', fallback maxAspectRatio to default value`); + aspectRatioFloatValue = 2.4; + } + + let apiLevel = this.params.platformParams.apiLevel; + if (apiLevel === undefined) apiLevel = DefaultAPILevel; + + + if (apiLevel >= 26) { // Android 8.0 + const activities = data.manifest.application[0].activity; + for (const activity of activities) { + activity.$['android:maxAspectRatio'] = `${aspectRatioFloatValue}`; + } + } else { + const application = data.manifest.application[0]; + //append meta-data + application['meta-data'].push({ + $: { 'android:name': 'android.max_aspect', 'android:value': `${aspectRatioFloatValue}` } + }); + } + + }; + const fnUpdateCategory = (data: any) => { + if (!this.params.platformParams.androidInstant) { + console.log('android instant not configured'); + return; + } + const url = this.params.platformParams.remoteUrl; + if (!url) { + return; + } + const urlInfo = URL.parse(url); + if (!urlInfo.host) { + throw new Error(`parse url ${url} fail`); + } + const intentFilter: any = data.manifest.application[0].activity[0]['intent-filter'][0]; + if (intentFilter) { + intentFilter.data ||= []; + intentFilter.data = intentFilter.data.concat([ + { $: { 'android:host': urlInfo.host, 'android:pathPattern': urlInfo.path, 'android:scheme': 'https' } }, + { $: { 'android:scheme': 'http' } } + ]); + } + } + + if (fs.existsSync(manifestPath)) { + const app = await fnParseXml(manifestPath); + await fnUpdateOrientation(app.data); + await fnUpdateResizeableActivity(app.data); + await fnUpdateMaxAspectRation(app.data); + await app.save(); + } + if (fs.existsSync(instantManifestPath)) { + const instant = await fnParseXml(instantManifestPath); + await fnUpdateOrientation(instant.data); + await fnUpdateResizeableActivity(instant.data); + await fnUpdateMaxAspectRation(instant.data); + await fnUpdateCategory(instant.data); + await instant.save(); + } + } + + protected async updateAndroidGradleValues() { + const options = this.params.platformParams; + // android-studio gradle.properties + console.log(`update settings.properties`); + await cchelper.replaceInFile([ + { reg: '^rootProject\\.name.*', text: `rootProject.name = "${this.params.projectName}"` }, + { reg: ':CocosGame', text: `:${this.params.projectName}` } + ], ps.join(this.paths.nativePrjDir, 'settings.gradle')); + + console.log(`update gradle.properties`); + const gradlePropertyPath = cchelper.join(this.paths.nativePrjDir, 'gradle.properties'); + if (fs.existsSync(gradlePropertyPath)) { + let keystorePath = options.keystorePath; + if (process.platform === 'win32') { + keystorePath = cchelper.fixPath(keystorePath); + } + let apiLevel = options.apiLevel; + if (!apiLevel) { + apiLevel = DefaultAPILevel; + } + console.log(`AndroidAPI level ${apiLevel}`); + let content = fs.readFileSync(gradlePropertyPath, 'utf-8'); + if (keystorePath) { + content = content.replace(/.*RELEASE_STORE_FILE=.*/, `RELEASE_STORE_FILE=${keystorePath}`); + content = content.replace(/.*RELEASE_STORE_PASSWORD=.*/, `RELEASE_STORE_PASSWORD=${options.keystorePassword}`); + content = content.replace(/.*RELEASE_KEY_ALIAS=.*/, `RELEASE_KEY_ALIAS=${options.keystoreAlias}`); + content = content.replace(/.*RELEASE_KEY_PASSWORD=.*/, `RELEASE_KEY_PASSWORD=${options.keystoreAliasPassword}`); + } else { + content = content.replace(/.*RELEASE_STORE_FILE=.*/, `# RELEASE_STORE_FILE=${keystorePath}`); + content = content.replace(/.*RELEASE_STORE_PASSWORD=.*/, `# RELEASE_STORE_PASSWORD=${options.keystorePassword}`); + content = content.replace(/.*RELEASE_KEY_ALIAS=.*/, `# RELEASE_KEY_ALIAS=${options.keystoreAlias}`); + content = content.replace(/.*RELEASE_KEY_PASSWORD=.*/, `# RELEASE_KEY_PASSWORD=${options.keystoreAliasPassword}`); + } + + const compileSDKVersion = this.parseVersion(content, 'PROP_COMPILE_SDK_VERSION', 27); + const minimalSDKVersion = this.parseVersion(content, 'PROP_MIN_SDK_VERSION', 21); + + content = content.replace(/PROP_TARGET_SDK_VERSION=.*/, `PROP_TARGET_SDK_VERSION=${apiLevel}`); + content = content.replace(/PROP_COMPILE_SDK_VERSION=.*/, `PROP_COMPILE_SDK_VERSION=${Math.max(apiLevel, compileSDKVersion, 27)}`); + content = content.replace(/PROP_MIN_SDK_VERSION=.*/, `PROP_MIN_SDK_VERSION=${Math.min(apiLevel, minimalSDKVersion)}`); + content = content.replace(/PROP_APP_NAME=.*/, `PROP_APP_NAME=${this.params.projectName}`); + content = content.replace(/PROP_ENABLE_INSTANT_APP=.*/, `PROP_ENABLE_INSTANT_APP=${options.androidInstant ? "true" : "false"}`); + content = content.replace(/PROP_ENABLE_INPUTSDK=.*/, `PROP_ENABLE_INPUTSDK=${options.inputSDK ? "true" : "false"}`); + content = content.replace(/PROP_IS_DEBUG=.*/, `PROP_IS_DEBUG=${this.params.debug ? "true" : "false"}`); + + content = content.replace(/RES_PATH=.*/, `RES_PATH=${cchelper.fixPath(this.paths.buildDir)}`); + content = content.replace(/COCOS_ENGINE_PATH=.*/, `COCOS_ENGINE_PATH=${cchelper.fixPath(Paths.nativeRoot)}`); + content = content.replace(/APPLICATION_ID=.*/, `APPLICATION_ID=${options.packageName}`); + content = content.replace(/NATIVE_DIR=.*/, `NATIVE_DIR=${cchelper.fixPath(this.paths.platformTemplateDirInPrj)}`); + + content = content.replace(/PROP_ENABLE_GOOGLE_BILLING=.*/, `PROP_ENABLE_GOOGLE_BILLING=${options.googleBilling ? "true" : "false"}`); + + + + if (process.platform === 'win32') { + options.ndkPath = options.ndkPath.replace(/\\/g, '\\\\'); + } + + content = content.replace(/PROP_NDK_PATH=.*/, `PROP_NDK_PATH=${options.ndkPath}`); + + const abis = (options.appABIs && options.appABIs.length > 0) ? options.appABIs.join(':') : 'armeabi-v7a'; + // todo:新的template里面有个注释也是这个字段,所以要加个g + content = content.replace(/PROP_APP_ABI=.*/g, `PROP_APP_ABI=${abis}`); + fs.writeFileSync(gradlePropertyPath, content); + + // generate local.properties + content = ''; + content += `sdk.dir=${options.sdkPath}`; + // windows 需要使用的这样的格式 e\:\\aa\\bb\\cc + if (process.platform === 'win32') { + content = content.replace(/\\/g, '\\\\'); + content = content.replace(/:/g, '\\:'); + } + + fs.writeFileSync(cchelper.join(ps.dirname(gradlePropertyPath), 'local.properties'), content, { encoding: 'utf8' }); + } else { + console.log(`warning: ${gradlePropertyPath} not found!`); + } + } + + /** + * Deprecated, only be compatible with historical packaging tools + */ + protected async configAndroidInstant() { + if (!this.params.platformParams.androidInstant) { + console.log('android instant not configured'); + return; + } + const url = this.params.platformParams.remoteUrl; + if (!url) { + return; + } + const manifestPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'instantapp/AndroidManifest.xml'); + if (!fs.existsSync(manifestPath)) { + throw new Error(`${manifestPath} not found`); + } + const urlInfo = URL.parse(url); + if (!urlInfo.host) { + throw new Error(`parse url ${url} fail`); + } + let manifest = fs.readFileSync(manifestPath, 'utf8'); + manifest = manifest.replace(//, (str) => { + let newStr = ''; + newStr += `\n ` + + `\n `; + return newStr; + }); + + fs.writeFileSync(manifestPath, manifest, 'utf8'); + } + + private async generateAppNameValues() { + const valuesPath = cchelper.join(this.paths.platformTemplateDirInPrj, 'res/values/strings.xml'); + const matchCnt = fs.readFileSync(valuesPath, 'utf8').toString().split('\n').map(x => x.trim()).filter(x => /name=\"app_name\"/.test(x)).length; + if (matchCnt == 0) { // should generate + const content = [ + ``, + ` ${this.params.projectName}`, + ``, + ]; + const dir = ps.join(this.paths.buildDir, 'proj/res/values'); + await fs.ensureDir(dir); + await fs.writeFileSync(ps.join(dir, `strings.xml`), content.join('\n'), 'utf8'); + } + } + + /** + * 到对应目录拷贝文件到工程发布目录 + */ + async copyToDist(): Promise { + const options = this.params.platformParams; + + const suffix = this.params.debug ? 'debug' : 'release'; + const destDir: string = ps.join(this.paths.buildDir, 'publish', suffix); + fs.ensureDirSync(destDir); + let apkName = `${this.projectNameASCII()}-${suffix}.apk`; + let apkPath = ps.join(this.outputsDir(), `apk/${suffix}/${apkName}`); + if (!fs.existsSync(apkPath)) { + throw new Error(`apk not found at ${apkPath}`); + } + fs.copyFileSync(apkPath, ps.join(destDir, apkName)); + if (options.androidInstant) { + apkName = `instantapp-${suffix}.apk`; + apkPath = ps.join(this.paths.nativePrjDir, `build/instantapp/outputs/apk/${suffix}/${apkName}`); + if (!fs.existsSync(apkPath)) { + throw new Error(`instant apk not found at ${apkPath}`); + } + fs.copyFileSync(apkPath, ps.join(destDir, apkName)); + } + + if (options.appBundle) { + apkName = `${this.params.projectName}-${suffix}.aab`; + apkPath = ps.join(this.outputsDir(), `bundle/${suffix}/${apkName}`); + if (!fs.existsSync(apkPath)) { + throw new Error(`instant apk not found at ${apkPath}`); + } + fs.copyFileSync(apkPath, ps.join(destDir, apkName)); + } + return true; + } + + // ---------------------------- run ------------------------- // + + async run() { + if (await this.install()) { + return await this.startApp(); + } + return true; + } + + getAdbPath() { + return ps.join( + this.params.platformParams.sdkPath, + `platform-tools/adb${process.platform === 'win32' ? '.exe' : ''}`); + } + + getApkPath() { + const suffix = this.params.debug ? 'debug' : 'release'; + const apkName = `${this.projectNameASCII()}-${suffix}.apk`; + return ps.join(this.outputsDir(), `apk/${suffix}/${apkName}`); + } + + private outputsDir() { + const folderName = this.projectNameASCII(); + const targetDir = ps.join(this.paths.nativePrjDir, 'build', folderName); + const fallbackDir = ps.join(this.paths.nativePrjDir, 'build', this.params.projectName); + return ps.join(fs.existsSync(targetDir) ? targetDir : fallbackDir, 'outputs'); + } + + async install(): Promise { + const apkPath = this.getApkPath(); + const adbPath = this.getAdbPath(); + + if (!fs.existsSync(apkPath)) { + throw new Error(`can not find apk at ${apkPath}`); + } + + if (!fs.existsSync(adbPath)) { + throw new Error(`can not find adb at ${adbPath}`); + } + + if (!this.checkConnectedDevices(adbPath)) { + console.error(`can not find any connected devices, please connect you device or start an Android emulator`); + } + + if (await this.checkApkInstalled(adbPath)) { + await cchelper.runCmd( + adbPath, ['uninstall', this.params.platformParams.packageName], false); + } + + await cchelper.runCmd(adbPath, ['install', '-r', apkPath], false); + return true; + } + + checkConnectedDevices(adbPath: string): boolean { + const cp = spawnSync(adbPath, ['devices'], { shell: true, env: process.env, cwd: process.cwd() }); + if (cp.stderr && cp.stderr.length > 0) { + console.log(`[adb devices] stderr: ${cp.stderr.toString('utf8')}`); + } + if (cp.error) { + console.log(`[adb devices] error: ${cp.error}`); + } + if (cp.output.length > 1) { + for (const chunk of cp.output) { + if (chunk) { + const chunkAny: any = chunk; + const chuckStr: string = typeof chunk === 'string' ? chunk : (chunkAny.buffer && chunkAny.buffer instanceof ArrayBuffer ? chunkAny.toString() : chunkAny.toString()); + const lines = chuckStr.split('\n'); + for (let line of lines) { + if (/^[0-9a-zA-Z]+\s+\w+/.test(line)) { + return true; // device connected + } + } + } + } + } + return false; + } + + async checkApkInstalled(adbPath: string) { + const ret: string = await new Promise((resolve, reject) => { + const cp = spawn( + adbPath, + [ + 'shell pm list packages | grep', + this.params.platformParams.packageName, + ], + { + shell: true, + env: process.env, + cwd: process.cwd(), + }); + cp.stdout.on(`data`, (chunk) => { + resolve(chunk.toString()); + }); + cp.stderr.on(`data`, (chunk) => { + resolve(''); + }); + cp.on('close', (code, signal) => { + resolve(''); + }); + }); + return ret.includes(this.params.platformParams.packageName); + } + + async startApp(): Promise { + const adbPath = this.getAdbPath(); + await cchelper.runCmd( + adbPath, + [ + 'shell', 'am', 'start', '-n', + `${this.params.platformParams.packageName}/com.cocos.game.AppActivity`, + ], + false); + return true; + } +} diff --git a/templates/google-play/build/.idea/encodings.xml b/templates/google-play/build/.idea/encodings.xml new file mode 100644 index 00000000000..c2bae49d78c --- /dev/null +++ b/templates/google-play/build/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/templates/google-play/build/build.gradle b/templates/google-play/build/build.gradle new file mode 100644 index 00000000000..930c4fa516a --- /dev/null +++ b/templates/google-play/build/build.gradle @@ -0,0 +1,18 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + // jcenter() // keeped as anchor, will be removed soon + } + dependencies { + classpath 'com.android.tools.build:gradle:8.0.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +apply from: NATIVE_DIR +"/build.gradle" diff --git a/templates/google-play/build/gradle.properties b/templates/google-play/build/gradle.properties new file mode 100644 index 00000000000..c52b0a50848 --- /dev/null +++ b/templates/google-play/build/gradle.properties @@ -0,0 +1,77 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +org.gradle.jvmargs=-Xmx4608m -Dfile.encoding=UTF-8 +android.useAndroidX=true + +android.injected.testOnly=false + +android.native.buildOutput=verbose + +# Android SDK version that will be used as the compile project +PROP_COMPILE_SDK_VERSION=34 + +# Android SDK version that will be used as the earliest version of android this application can run on +PROP_MIN_SDK_VERSION=21 + +# Android SDK version that will be used as the latest version of android this application has been tested on +PROP_TARGET_SDK_VERSION=34 + +# Android Build Tools version that will be used as the compile project +PROP_BUILD_TOOLS_VERSION=34.0.0 + +# Android Application Name +PROP_APP_NAME=Cocos Game + +# Instant App +PROP_ENABLE_INSTANT_APP=true + +# InputSDK +PROP_ENABLE_INPUTSDK=false + +PROP_NDK_PATH= + +# Build variant +PROP_IS_DEBUG=true + +# Cocos Engine Path +COCOS_ENGINE_PATH= + +# Billing +PROP_ENABLE_GOOGLE_BILLING=true + +# Res path +RES_PATH= + +# Native source dir +NATIVE_DIR= + +# Application ID +APPLICATION_ID= + +# List of CPU Archtexture to build that application with +# Available architextures (armeabi-v7a | arm64-v8a | x86 | x86_64) +# To build for multiple architexture, use the `:` between them +# Example - PROP_APP_ABI=armeabi-v7a:arm64-v8a:x86 +PROP_APP_ABI=arm64-v8a + +# fill in sign information for release mode +# RELEASE_STORE_FILE= +# RELEASE_STORE_PASSWORD= +# RELEASE_KEY_ALIAS= +# RELEASE_KEY_PASSWORD= diff --git a/templates/google-play/build/gradle/wrapper/gradle-wrapper.jar b/templates/google-play/build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..8c0fb64a869 Binary files /dev/null and b/templates/google-play/build/gradle/wrapper/gradle-wrapper.jar differ diff --git a/templates/google-play/build/gradle/wrapper/gradle-wrapper.properties b/templates/google-play/build/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..1734c610eb3 --- /dev/null +++ b/templates/google-play/build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Apr 25 14:17:09 CST 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/templates/google-play/build/gradlew b/templates/google-play/build/gradlew new file mode 100644 index 00000000000..91a7e269e19 --- /dev/null +++ b/templates/google-play/build/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/templates/google-play/build/gradlew.bat b/templates/google-play/build/gradlew.bat new file mode 100644 index 00000000000..8a0b282aa68 --- /dev/null +++ b/templates/google-play/build/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/templates/google-play/build/libservice/AndroidManifest.xml b/templates/google-play/build/libservice/AndroidManifest.xml new file mode 100644 index 00000000000..0a0938ae37e --- /dev/null +++ b/templates/google-play/build/libservice/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/templates/google-play/build/libservice/build.gradle b/templates/google-play/build/libservice/build.gradle new file mode 100644 index 00000000000..d8228188f7c --- /dev/null +++ b/templates/google-play/build/libservice/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger() + namespace 'com.cocos.service' + defaultConfig { + minSdkVersion PROP_MIN_SDK_VERSION + targetSdkVersion PROP_TARGET_SDK_VERSION + versionCode 1 + versionName "1.0" + } + + sourceSets.main { + java.srcDirs "src" + res.srcDirs 'res' + jniLibs.srcDirs 'libs' + manifest.srcFile "AndroidManifest.xml" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) + implementation project(':libcocos') +} diff --git a/templates/google-play/build/libservice/proguard-rules.pro b/templates/google-play/build/libservice/proguard-rules.pro new file mode 100644 index 00000000000..f1b424510da --- /dev/null +++ b/templates/google-play/build/libservice/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/templates/google-play/build/libservice/src/com/cocos/service/SDKWrapper.java b/templates/google-play/build/libservice/src/com/cocos/service/SDKWrapper.java new file mode 100644 index 00000000000..e7ab46f77da --- /dev/null +++ b/templates/google-play/build/libservice/src/com/cocos/service/SDKWrapper.java @@ -0,0 +1,185 @@ +/**************************************************************************** + Copyright (c) 2017-2020 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. + ****************************************************************************/ + +package com.cocos.service; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.os.Bundle; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +public final class SDKWrapper { + private SDKWrapper() {} + private static class SDKWrapperInstance { + private static final SDKWrapper mInstance = new SDKWrapper(); + } + public static SDKWrapper shared() { return SDKWrapperInstance.mInstance; } + + @SuppressWarnings("unused") + public interface SDKInterface { + default void init(Context context) {} + default void onStart() {} + default void onPause() {} + default void onResume() {} + default void onStop() {} + default void onDestroy() {} + default void onRestart() {} + default void onNewIntent(Intent intent) {} + default void onActivityResult(int requestCode, int resultCode, Intent data) {} + default void onConfigurationChanged(Configuration newConfig) {} + default void onRestoreInstanceState(Bundle savedInstanceState) {} + default void onSaveInstanceState(Bundle outState) {} + default void onBackPressed() {} + default void onLowMemory() {} + } + + private WeakReference mActivity = null; + private List serviceInstances; + + private void loadSDKInterface() { + ArrayList instances = new ArrayList<>(); + try { + String json = this.getJson("service.json"); + JSONObject jsonObject = new JSONObject(json); + JSONArray serviceClasses = jsonObject.getJSONArray("serviceClasses"); + if (serviceClasses == null) return; + int length = serviceClasses.length(); + for (int i = 0; i < length; i++) { + instances.add((SDKInterface) Class.forName(serviceClasses.getString(i)).newInstance()); + } + } catch (Exception ignored) { } + this.serviceInstances = instances; + } + + @SuppressWarnings("SameParameterValue") + private String getJson(String fileName) { + StringBuilder sb = new StringBuilder(); + try { + AssetManager am = this.mActivity.get().getAssets(); + BufferedReader br = new BufferedReader(new InputStreamReader(am.open(fileName))); + String next; + while (null != (next = br.readLine())) { sb.append(next); } + } catch (IOException e) { + sb.delete(0, sb.length()); + } + return sb.toString().trim(); + } + + public Activity getActivity() { return this.mActivity.get(); } + + public void init(Activity activity) { + this.mActivity = new WeakReference<>(activity); + this.loadSDKInterface(); + for (SDKInterface sdk : this.serviceInstances) { + sdk.init(activity); + } + } + + public void onResume() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onResume(); + } + } + + public void onPause() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onPause(); + } + } + + public void onDestroy() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onDestroy(); + } + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onActivityResult(requestCode, resultCode, data); + } + } + + public void onNewIntent(Intent intent) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onNewIntent(intent); + } + } + + public void onRestart() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onRestart(); + } + } + + public void onStop() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onStop(); + } + } + + public void onBackPressed() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onBackPressed(); + } + } + + public void onConfigurationChanged(Configuration newConfig) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onConfigurationChanged(newConfig); + } + } + + public void onRestoreInstanceState(Bundle savedInstanceState) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onRestoreInstanceState(savedInstanceState); + } + } + + public void onSaveInstanceState(Bundle outState) { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onSaveInstanceState(outState); + } + } + + public void onStart() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onStart(); + } + } + + public void onLowMemory() { + for (SDKInterface sdk : this.serviceInstances) { + sdk.onLowMemory(); + } + } +} \ No newline at end of file diff --git a/templates/google-play/build/settings.gradle b/templates/google-play/build/settings.gradle new file mode 100644 index 00000000000..a92f255a760 --- /dev/null +++ b/templates/google-play/build/settings.gradle @@ -0,0 +1,10 @@ +include ':libcocos',':libservice',':app' +project(':libcocos').projectDir = new File(COCOS_ENGINE_PATH,'cocos/platform/android/libcocos2dx') +project(':app').projectDir = new File(NATIVE_DIR, 'app') +project(':app').name = "CocosGame" +if(PROP_ENABLE_INSTANT_APP == "true" || PROP_ENABLE_INSTANT_APP == "yes") { + include ':instantapp' + project(':instantapp').projectDir = new File(NATIVE_DIR, 'instantapp') +} + +rootProject.name = "CocosGame" diff --git a/templates/google-play/template/.gitignore b/templates/google-play/template/.gitignore new file mode 100644 index 00000000000..9c4de5825b1 --- /dev/null +++ b/templates/google-play/template/.gitignore @@ -0,0 +1,7 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/templates/google-play/template/CMakeLists.txt b/templates/google-play/template/CMakeLists.txt new file mode 100644 index 00000000000..b6f90857a94 --- /dev/null +++ b/templates/google-play/template/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.8) + +option(APP_NAME "Project Name" "CocosGame") +project(${APP_NAME} CXX) +set(CC_LIB_NAME cocos) +set(CC_PROJECT_DIR ${CMAKE_CURRENT_LIST_DIR}) +set(CC_PROJ_SOURCES) +set(CC_COMMON_SOURCES) +set(CC_ALL_SOURCES) + +include(${CMAKE_CURRENT_LIST_DIR}/../common/CMakeLists.txt) + +cc_android_before_target(${CC_LIB_NAME}) +add_library(${CC_LIB_NAME} SHARED ${CC_ALL_SOURCES}) +cc_android_after_target(${CC_LIB_NAME}) diff --git a/templates/google-play/template/app/.gitignore b/templates/google-play/template/app/.gitignore new file mode 100644 index 00000000000..1de99493d64 --- /dev/null +++ b/templates/google-play/template/app/.gitignore @@ -0,0 +1,2 @@ +/build +/jniLibs diff --git a/templates/google-play/template/app/AndroidManifest.xml b/templates/google-play/template/app/AndroidManifest.xml new file mode 100644 index 00000000000..a85bb7ca777 --- /dev/null +++ b/templates/google-play/template/app/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/google-play/template/app/build.gradle b/templates/google-play/template/app/build.gradle new file mode 100644 index 00000000000..184a68947f0 --- /dev/null +++ b/templates/google-play/template/app/build.gradle @@ -0,0 +1,113 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +apply plugin: 'com.android.application' + +RES_PATH = RES_PATH.replace("\\", "/") +COCOS_ENGINE_PATH = COCOS_ENGINE_PATH.replace("\\", "/") + +buildDir = "${RES_PATH}/proj/build/${project.name ==~ /^[_a-zA-Z0-9-]+$/ ? project.name : 'CocosGame'}" +android { + compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger() + buildToolsVersion PROP_BUILD_TOOLS_VERSION + ndkPath PROP_NDK_PATH + namespace APPLICATION_ID + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + applicationId APPLICATION_ID + minSdkVersion PROP_MIN_SDK_VERSION + targetSdkVersion PROP_TARGET_SDK_VERSION + versionCode 1 + versionName "1.0" + + externalNativeBuild { + cmake { + targets "cocos" + arguments "-DRES_DIR=${RES_PATH}", "-DANDROID_STL=c++_static", "-DANDROID_TOOLCHAIN=clang", "-DANDROID_ARM_NEON=TRUE" + } + ndk { abiFilters PROP_APP_ABI.split(':') } + } + } + + sourceSets.main { + java.srcDirs "../src", "src" + res.srcDirs "../res", 'res', "${RES_PATH}/proj/res" + jniLibs.srcDirs "../libs", 'libs' + manifest.srcFile "AndroidManifest.xml" + assets.srcDir "${RES_PATH}/data" + jniLibs { + // Vulkan validation layer + // srcDir "${android.ndkDirectory}/sources/third_party/vulkan/src/build-android/jniLibs" + } + } + + externalNativeBuild { + cmake { + version "3.22.1" + path "../CMakeLists.txt" + buildStagingDirectory "${RES_PATH}/proj/build" + } + } + + signingConfigs { + + release { + if (project.hasProperty("RELEASE_STORE_FILE") && !RELEASE_STORE_FILE.isEmpty()) { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + } + } + } + + buildTypes { + release { + debuggable false + jniDebuggable false + renderscriptDebuggable false + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + if (project.hasProperty("RELEASE_STORE_FILE")) { + signingConfig signingConfigs.release + } + + externalNativeBuild { + cmake { + // switch HIDE_SYMBOLS to OFF to skip compilation flag `-fvisibility=hidden` + arguments "-DHIDE_SYMBOLS=ON" + } + } + + if (!Boolean.parseBoolean(PROP_IS_DEBUG)) { + getIsDefault().set(true) + } + + } + + debug { + debuggable true + jniDebuggable true + renderscriptDebuggable true + // resValue "string", "app_name", "${PROP_APP_NAME}-dbg" + // applicationIdSuffix ".debug" + } + } +} + +dependencies { + implementation fileTree(dir: '../libs', include: ['*.jar','*.aar']) + implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) + implementation fileTree(dir: "${COCOS_ENGINE_PATH}/cocos/platform/android/java/libs", include: ['*.jar']) + implementation project(':libservice') + implementation project(':libcocos') + if (Boolean.parseBoolean(PROP_ENABLE_INPUTSDK)) { + implementation 'com.google.android.libraries.play.games:inputmapping:1.1.0-beta' + implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.10" + } +} diff --git a/templates/google-play/template/app/proguard-rules.pro b/templates/google-play/template/app/proguard-rules.pro new file mode 100644 index 00000000000..fc884b76762 --- /dev/null +++ b/templates/google-play/template/app/proguard-rules.pro @@ -0,0 +1,54 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\developSoftware\Android\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Proguard Cocos2d-x-lite for release +-keep public class com.cocos.** { *; } +-dontwarn com.cocos.** + +# Proguard Apache HTTP for release +-keep class org.apache.http.** { *; } +-dontwarn org.apache.http.** + +# Proguard okhttp for release +-keep class okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +# Proguard Android Webivew for release. you can comment if you are not using a webview +-keep public class android.net.http.SslError +-keep public class android.webkit.WebViewClient + +-keep public class com.google.** { *; } + +-dontwarn android.webkit.WebView +-dontwarn android.net.http.SslError +-dontwarn android.webkit.WebViewClient + +# This is generated automatically by the Android Gradle plugin. +-dontwarn android.hardware.BatteryState +-dontwarn android.hardware.lights.Light +-dontwarn android.hardware.lights.LightState$Builder +-dontwarn android.hardware.lights.LightState +-dontwarn android.hardware.lights.LightsManager$LightsSession +-dontwarn android.hardware.lights.LightsManager +-dontwarn android.hardware.lights.LightsRequest$Builder +-dontwarn android.hardware.lights.LightsRequest +-dontwarn android.net.ssl.SSLSockets +-dontwarn android.os.VibratorManager \ No newline at end of file diff --git a/templates/google-play/template/app/src/com/cocos/game/AppActivity.java b/templates/google-play/template/app/src/com/cocos/game/AppActivity.java new file mode 100644 index 00000000000..5aaece88515 --- /dev/null +++ b/templates/google-play/template/app/src/com/cocos/game/AppActivity.java @@ -0,0 +1,125 @@ +/**************************************************************************** +Copyright (c) 2015-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. +****************************************************************************/ +package com.cocos.game; + +import android.os.Bundle; +import android.content.Intent; +import android.content.res.Configuration; + +import com.cocos.service.SDKWrapper; +import com.cocos.lib.CocosActivity; + +public class AppActivity extends CocosActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // DO OTHER INITIALIZATION BELOW + SDKWrapper.shared().init(this); + + } + + @Override + protected void onResume() { + super.onResume(); + SDKWrapper.shared().onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + SDKWrapper.shared().onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508 + if (!isTaskRoot()) { + return; + } + SDKWrapper.shared().onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + SDKWrapper.shared().onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + SDKWrapper.shared().onNewIntent(intent); + } + + @Override + protected void onRestart() { + super.onRestart(); + SDKWrapper.shared().onRestart(); + } + + @Override + protected void onStop() { + super.onStop(); + SDKWrapper.shared().onStop(); + } + + @Override + public void onBackPressed() { + SDKWrapper.shared().onBackPressed(); + super.onBackPressed(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + SDKWrapper.shared().onConfigurationChanged(newConfig); + super.onConfigurationChanged(newConfig); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + SDKWrapper.shared().onRestoreInstanceState(savedInstanceState); + super.onRestoreInstanceState(savedInstanceState); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + SDKWrapper.shared().onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } + + @Override + protected void onStart() { + SDKWrapper.shared().onStart(); + super.onStart(); + } + + @Override + public void onLowMemory() { + SDKWrapper.shared().onLowMemory(); + super.onLowMemory(); + } +} diff --git a/templates/google-play/template/build-cfg.json b/templates/google-play/template/build-cfg.json new file mode 100644 index 00000000000..fb658e29853 --- /dev/null +++ b/templates/google-play/template/build-cfg.json @@ -0,0 +1,8 @@ +{ + "ndk_module_path" :[ + "${COCOS_ROOT}", + "${COCOS_ROOT}/cocos", + "${COCOS_ROOT}/external" + ], + "copy_resources": [] +} diff --git a/templates/google-play/template/build.gradle b/templates/google-play/template/build.gradle new file mode 100644 index 00000000000..84203e08ee1 --- /dev/null +++ b/templates/google-play/template/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + // jcenter() // keeped as anchor, will be removed soon + } + dependencies { + classpath 'com.android.tools.build:gradle:8.0.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + mavenCentral() + // jcenter() // keeped as anchor, will be removed soon + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/templates/google-play/template/instantapp/AndroidManifest.xml b/templates/google-play/template/instantapp/AndroidManifest.xml new file mode 100644 index 00000000000..0216ac9da8f --- /dev/null +++ b/templates/google-play/template/instantapp/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/google-play/template/instantapp/build.gradle b/templates/google-play/template/instantapp/build.gradle new file mode 100644 index 00000000000..4336667f2c4 --- /dev/null +++ b/templates/google-play/template/instantapp/build.gradle @@ -0,0 +1,100 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +apply plugin: 'com.android.application' + +RES_PATH = RES_PATH.replace("\\", "/") +COCOS_ENGINE_PATH = COCOS_ENGINE_PATH.replace("\\", "/") +buildDir = "${RES_PATH}/proj/build/instantapp" + +android { + compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger() + buildToolsVersion PROP_BUILD_TOOLS_VERSION + ndkPath PROP_NDK_PATH + namespace APPLICATION_ID + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion PROP_MIN_SDK_VERSION + targetSdkVersion PROP_TARGET_SDK_VERSION + versionCode 1 + versionName "1.0" + + externalNativeBuild { + cmake { + targets "cocos" + arguments "-DRES_DIR=${RES_PATH}", "-DCOCOS_X_PATH=${COCOS_ENGINE_PATH}","-DANDROID_STL=c++_static", "-DANDROID_TOOLCHAIN=clang", "-DANDROID_ARM_NEON=TRUE", "-DANDROID_LD=gold" + cppFlags "-frtti -fexceptions -fsigned-char -DANDROID_INSTANT=1" + } + ndk { abiFilters PROP_APP_ABI.split(':') } + } + } + + sourceSets.main { + java.srcDirs "../src", "src" + res.srcDirs "../res", 'res', "${RES_PATH}/proj/res" + jniLibs.srcDirs "../libs", 'libs' + manifest.srcFile "AndroidManifest.xml" + assets.srcDir "${RES_PATH}/data" + } + + externalNativeBuild { + cmake { + version "3.22.1" + path "../CMakeLists.txt" + buildStagingDirectory "${RES_PATH}/proj/build" + } + } + + signingConfigs { + + release { + if (project.hasProperty("RELEASE_STORE_FILE") && !RELEASE_STORE_FILE.isEmpty()) { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + } + } + } + + buildTypes { + release { + debuggable false + jniDebuggable false + renderscriptDebuggable false + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + if (project.hasProperty("RELEASE_STORE_FILE")) { + signingConfig signingConfigs.release + } + + externalNativeBuild { + cmake { + // switch HIDE_SYMBOLS to OFF to skip compilation flag `-fvisibility=hidden` + arguments "-DHIDE_SYMBOLS=ON" + } + } + } + + debug { + debuggable true + jniDebuggable true + renderscriptDebuggable true + // resValue "string", "app_name", "${PROP_APP_NAME}-dbg" + // applicationIdSuffix ".debug" + } + } +} + +dependencies { + implementation fileTree(dir: '../libs', include: ['*.jar','*.aar']) + implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) + implementation fileTree(dir: "${COCOS_ENGINE_PATH}/cocos/platform/android/java/libs", include: ['*.jar']) + implementation project(':libservice') + implementation project(':libcocos') +} diff --git a/templates/google-play/template/instantapp/proguard-rules.pro b/templates/google-play/template/instantapp/proguard-rules.pro new file mode 100644 index 00000000000..fc884b76762 --- /dev/null +++ b/templates/google-play/template/instantapp/proguard-rules.pro @@ -0,0 +1,54 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\developSoftware\Android\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Proguard Cocos2d-x-lite for release +-keep public class com.cocos.** { *; } +-dontwarn com.cocos.** + +# Proguard Apache HTTP for release +-keep class org.apache.http.** { *; } +-dontwarn org.apache.http.** + +# Proguard okhttp for release +-keep class okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + +# Proguard Android Webivew for release. you can comment if you are not using a webview +-keep public class android.net.http.SslError +-keep public class android.webkit.WebViewClient + +-keep public class com.google.** { *; } + +-dontwarn android.webkit.WebView +-dontwarn android.net.http.SslError +-dontwarn android.webkit.WebViewClient + +# This is generated automatically by the Android Gradle plugin. +-dontwarn android.hardware.BatteryState +-dontwarn android.hardware.lights.Light +-dontwarn android.hardware.lights.LightState$Builder +-dontwarn android.hardware.lights.LightState +-dontwarn android.hardware.lights.LightsManager$LightsSession +-dontwarn android.hardware.lights.LightsManager +-dontwarn android.hardware.lights.LightsRequest$Builder +-dontwarn android.hardware.lights.LightsRequest +-dontwarn android.net.ssl.SSLSockets +-dontwarn android.os.VibratorManager \ No newline at end of file diff --git a/templates/google-play/template/instantapp/src/com/cocos/game/InstantActivity.java b/templates/google-play/template/instantapp/src/com/cocos/game/InstantActivity.java new file mode 100644 index 00000000000..318e8398d31 --- /dev/null +++ b/templates/google-play/template/instantapp/src/com/cocos/game/InstantActivity.java @@ -0,0 +1,125 @@ +/**************************************************************************** +Copyright (c) 2015-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. +****************************************************************************/ +package com.cocos.game; + +import android.os.Bundle; +import android.content.Intent; +import android.content.res.Configuration; + +import com.cocos.service.SDKWrapper; +import com.cocos.lib.CocosActivity; + +public class InstantActivity extends CocosActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // DO OTHER INITIALIZATION BELOW + SDKWrapper.shared().init(this); + + } + + @Override + protected void onResume() { + super.onResume(); + SDKWrapper.shared().onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + SDKWrapper.shared().onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508 + if (!isTaskRoot()) { + return; + } + SDKWrapper.shared().onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + SDKWrapper.shared().onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + SDKWrapper.shared().onNewIntent(intent); + } + + @Override + protected void onRestart() { + super.onRestart(); + SDKWrapper.shared().onRestart(); + } + + @Override + protected void onStop() { + super.onStop(); + SDKWrapper.shared().onStop(); + } + + @Override + public void onBackPressed() { + SDKWrapper.shared().onBackPressed(); + super.onBackPressed(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + SDKWrapper.shared().onConfigurationChanged(newConfig); + super.onConfigurationChanged(newConfig); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + SDKWrapper.shared().onRestoreInstanceState(savedInstanceState); + super.onRestoreInstanceState(savedInstanceState); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + SDKWrapper.shared().onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } + + @Override + protected void onStart() { + SDKWrapper.shared().onStart(); + super.onStart(); + } + + @Override + public void onLowMemory() { + SDKWrapper.shared().onLowMemory(); + super.onLowMemory(); + } +} diff --git a/templates/google-play/template/res/mipmap-hdpi/ic_launcher.png b/templates/google-play/template/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..888c0d56abe Binary files /dev/null and b/templates/google-play/template/res/mipmap-hdpi/ic_launcher.png differ diff --git a/templates/google-play/template/res/mipmap-mdpi/ic_launcher.png b/templates/google-play/template/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..d5e3c770af8 Binary files /dev/null and b/templates/google-play/template/res/mipmap-mdpi/ic_launcher.png differ diff --git a/templates/google-play/template/res/mipmap-xhdpi/ic_launcher.png b/templates/google-play/template/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..2b6f32ae038 Binary files /dev/null and b/templates/google-play/template/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/templates/google-play/template/res/mipmap-xxhdpi/ic_launcher.png b/templates/google-play/template/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..2f1e5a6b377 Binary files /dev/null and b/templates/google-play/template/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/templates/google-play/template/res/mipmap-xxxhdpi/ic_launcher.png b/templates/google-play/template/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..a9ebc7956e9 Binary files /dev/null and b/templates/google-play/template/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/templates/google-play/template/res/values/strings.xml b/templates/google-play/template/res/values/strings.xml new file mode 100644 index 00000000000..8542005550c --- /dev/null +++ b/templates/google-play/template/res/values/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/tsconfig.json b/tsconfig.json index 273a16fd24f..369cb10f46b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -55,6 +55,7 @@ "exports/**/*.ts", "editor/exports/**/*.ts", "typedoc-index.ts", - "pal/**/*.ts" + "pal/**/*.ts", + "vendor/**/*.ts" ] } diff --git a/typedoc-index.ts b/typedoc-index.ts index f5f38d1b343..c25b3950dbe 100644 --- a/typedoc-index.ts +++ b/typedoc-index.ts @@ -29,3 +29,4 @@ export * from './exports/video'; export * from './exports/webview'; export * from './exports/sorting'; export * from './exports/xr'; +export * from './exports/vendor-google'; diff --git a/vendor/google/billing/billing.ts b/vendor/google/billing/billing.ts new file mode 100644 index 00000000000..c31f64ad209 --- /dev/null +++ b/vendor/google/billing/billing.ts @@ -0,0 +1,829 @@ +/**************************************************************************** + Copyright (c) 2024 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 documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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 { JSB } from 'internal:constants'; +import { EventTarget } from '../../../cocos/core/event'; + +interface BillingEventMap { + [google.BillingEventType.BILLING_SETUP_FINISHED]: (result: google.BillingResult) => void, + [google.BillingEventType.BILLING_SERVICE_DISCONNECTED]: () => void, + [google.BillingEventType.PRODUCT_DETAILS_RESPONSE]: + (result: google.BillingResult, productDetailsList: google.ProductDetails[]) => void, + [google.BillingEventType.PURCHASES_UPDATED]: (result: google.BillingResult, purchases: google.Purchase[]) => void, + [google.BillingEventType.CONSUME_RESPONSE]: (result: google.BillingResult, purchaseToken: string) => void, + [google.BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.QUERY_PURCHASES_RESPONSE]: (result: google.BillingResult, purchases: google.Purchase[]) => void, + [google.BillingEventType.BILLING_CONFIG_RESPONSE]: (result: google.BillingResult, config: google.BillingConfig) => void + [google.BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE]: + (result: google.BillingResult, alternativeBillingOnlyReportingDetails: google.AlternativeBillingOnlyReportingDetails) => void + [google.BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE]: + (result: google.BillingResult, externalOfferReportingDetails: google.ExternalOfferReportingDetails) => void + [google.BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE]: (result: google.BillingResult) => void + [google.BillingEventType.IN_APP_MESSAGE_RESPONSE]: (result: google.InAppMessageResult) => void +} + +class Billing { + private _eventTarget: EventTarget = new EventTarget(); + constructor () { + if (!JSB || !jsb.googleBilling) { + return; + } + jsb.onBillingSetupFinished = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.BILLING_SETUP_FINISHED, result); + }; + + jsb.onBillingServiceDisconnected = (): void => { + this._eventTarget.emit(google.BillingEventType.BILLING_SERVICE_DISCONNECTED); + }; + + jsb.onProductDetailsResponse = ( + result: google.BillingResult, + productDetailsList: google.ProductDetails[], + ): void => { + this._eventTarget.emit(google.BillingEventType.PRODUCT_DETAILS_RESPONSE, result, productDetailsList); + }; + + jsb.onPurchasesUpdated = ( + result: google.BillingResult, + purchaseList: google.Purchase[], + ): void => { + this._eventTarget.emit(google.BillingEventType.PURCHASES_UPDATED, result, purchaseList); + }; + + jsb.onConsumeResponse = ( + result: google.BillingResult, + purchaseToken: string, + ): void => { + this._eventTarget.emit(google.BillingEventType.CONSUME_RESPONSE, result, purchaseToken); + }; + + jsb.onAcknowledgePurchaseResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.ACKNOWLEDGE_PURCHASES_RESPONSE, result); + }; + + jsb.onQueryPurchasesResponse = ( + result: google.BillingResult, + purchaseList: google.Purchase[], + ): void => { + this._eventTarget.emit(google.BillingEventType.QUERY_PURCHASES_RESPONSE, result, purchaseList); + }; + + jsb.onBillingConfigResponse = (result: google.BillingResult, config: google.BillingConfig): void => { + this._eventTarget.emit(google.BillingEventType.BILLING_CONFIG_RESPONSE, result, config); + }; + + jsb.onAlternativeBillingOnlyTokenResponse = ( + result: google.BillingResult, + details: google.AlternativeBillingOnlyReportingDetails, + ): void => { + this._eventTarget.emit(google.BillingEventType.ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE, result, details); + }; + + jsb.onExternalOfferReportingDetailsResponse = (result: google.BillingResult, details: google.ExternalOfferReportingDetails): void => { + this._eventTarget.emit(google.BillingEventType.EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE, result, details); + }; + + jsb.onAlternativeBillingOnlyAvailabilityResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE, result); + }; + + jsb.onExternalOfferAvailabilityResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.EXTERNAL_OFFER_AVAILABILITY_RESPONSE, result); + }; + + jsb.onAlternativeBillingOnlyInformationDialogResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE, result); + }; + + jsb.onExternalOfferInformationDialogResponse = (result: google.BillingResult): void => { + this._eventTarget.emit(google.BillingEventType.EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE, result); + }; + + jsb.onInAppMessageResponse = (result: google.InAppMessageResult): void => { + this._eventTarget.emit(google.BillingEventType.IN_APP_MESSAGE_RESPONSE, result); + }; + } + + /** + * @en Starts up BillingClient setup process asynchronously. + * @zh 异步启动 BillingClient 设置过程。 + */ + public startConnection (): void { + jsb.googleBilling?.startConnection(); + } + + /** + * @en Closes the connection and releases all held resources such as service connections. + * @zh 关闭连接并释放所有持有的资源,例如服务连接。 + */ + public endConnection (): void { + jsb.googleBilling?.endConnection(); + } + + /** + * @en Get the current billing client connection state. + * @zh 获取当前billing客户端连接状态。 + */ + public getConnectionState (): number { + if (jsb.googleBilling) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.googleBilling.getConnectionState(); + } + return google.ConnectionState.DISCONNECTED; + } + + /** + * @en Checks if the client is currently connected to the service, so that requests to other methods will succeed. + Returns true if the client is currently connected to the service, false otherwise. + * @zh 检查客户端当前是否连接到服务,以便对其他方法的请求能够成功。 + 如果客户端当前已连接到服务,则返回 true,否则返回 false。 + */ + public isReady (): boolean { + if (jsb.googleBilling) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.googleBilling.isReady(); + } + return false; + } + + /** + * @en Performs a network query the details of products available for sale in your app. + * @zh 执行网络查询您的应用中可供销售的产品的详细信息。 + * @param productId @zh 产品ID。 @en product id. + * @param productType @zh 产品类型。 @en product type. + * + */ + public queryProductDetailsParams (productId: string[], productType: google.ProductType): void { + jsb.googleBilling?.queryProductDetailsParams(productId, productType); + } + + /** + * @en Initiates the billing flow for an in-app purchase or subscription. + * @zh 启动应用内购买或订阅的计费流程。 + * @param productDetails @zh 产品详情。 @en product details. + * @param selectedOfferToken @zh 选择提供的token。 @en selected offer token. + */ + public launchBillingFlow (productDetails: google.ProductDetails[], selectedOfferToken: string | null): void { + jsb.googleBilling?.launchBillingFlow(productDetails, selectedOfferToken); + } + + /** + * @en Consumes a given in-app product. + * @zh 消费指定的应用内产品。 + * @param purchase @zh 已经购买的产品。 @en Purchased Products. + */ + public consumePurchases (purchase: google.Purchase[]): void { + jsb.googleBilling?.consumePurchases(purchase); + } + + /** + * @en Acknowledges in-app purchases. + * @zh 确认应用内购买。 + * @param purchase @zh 已经购买的产品。 @en Purchased Products. + */ + public acknowledgePurchase (purchase: google.Purchase[]): void { + jsb.googleBilling?.acknowledgePurchase(purchase); + } + + /** + * @en Returns purchases details for currently owned items bought within your app. + * @zh 返回您应用内当前拥有的购买商品的购买详情。 + * @param productType @zh 产品类型 @en Product type. + */ + public queryPurchasesAsync (productType: google.ProductType): void { + jsb.googleBilling?.queryPurchasesAsync(productType); + } + + /** + * @en Gets the billing config, which stores configuration used to perform billing operations. + * @zh 获取计费配置,其中存储用于执行计费操作的配置。 + */ + public getBillingConfigAsync (): void { + jsb.googleBilling?.getBillingConfigAsync(); + } + + /** + * @en Creates alternative billing only purchase details that can be used to report a transaction made + * via alternative billing without user choice to use Google Play billing. + * @zh 创建仅限替代结算的购买详情,可用于报告通过替代结算进行的交易,而无需用户选择使用 Google Play 结算。 + */ + public createAlternativeBillingOnlyReportingDetailsAsync (): void { + jsb.googleBilling?.createAlternativeBillingOnlyReportingDetailsAsync(); + } + + /** + * @en Checks the availability of offering alternative billing without user choice to use Google Play billing. + * @zh 检查是否可以提供替代结算方式,而无需用户选择使用 Google Play 结算方式。 + */ + public isAlternativeBillingOnlyAvailableAsync (): void { + jsb.googleBilling?.isAlternativeBillingOnlyAvailableAsync(); + } + + /** + * @en Creates purchase details that can be used to report a transaction made via external offer. + * @zh 创建可用于报告通过外部报价进行的交易的购买详情。 + */ + public createExternalOfferReportingDetailsAsync (): void { + jsb.googleBilling?.createExternalOfferReportingDetailsAsync(); + } + + /** + * @en Checks the availability of providing external offer. + * @zh 检查提供外部报价的可用性。 + */ + public isExternalOfferAvailableAsync (): void { + jsb.googleBilling?.isExternalOfferAvailableAsync(); + } + + /** + * @en Checks if the specified feature or capability is supported by the Play Store. + * @zh 检查 Play Store 是否支持指定的功能。 + * @param feature @zh 功能特性 @en feature. + */ + public isFeatureSupported (feature: string): google.BillingResult | null { + if (jsb.googleBilling) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.googleBilling.isFeatureSupported(feature); + } + return null; + } + + /** + * @en Shows the alternative billing only information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示仅显示备用计费信息对话框。 + */ + public showAlternativeBillingOnlyInformationDialog (): google.BillingResult | null { + if (jsb.googleBilling) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.googleBilling.showAlternativeBillingOnlyInformationDialog(); + } + return null; + } + + /** + * @en Shows the external offer information dialog on top of the calling app. + * @zh 在调用应用程序顶部显示外部优惠信息对话框。 + */ + public showExternalOfferInformationDialog (): google.BillingResult | null { + if (jsb.googleBilling) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.googleBilling.showExternalOfferInformationDialog(); + } + return null; + } + + /** + * @en Overlays billing related messages on top of the calling app. + * @zh 在调用应用程序上叠加与计费相关的消息。 + */ + public showInAppMessages (): google.BillingResult | null { + if (jsb.googleBilling) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return jsb.googleBilling.showInAppMessages(); + } + return null; + } + + public on (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { + this._eventTarget.on(type, callback, target); + return callback; + } + public once (type: K, callback: BillingEventMap[K], target?: unknown): BillingEventMap[K] { + this._eventTarget.once(type, callback, target); + return callback; + } + public off (eventType: K, callback?: BillingEventMap[K], target?: any): void { + this._eventTarget.off(eventType, callback, target); + } +} + +const gpBilling = new Billing(); + +export namespace google { + /** + * @en Google Play Billing event type + * @zh Google Play Billing事件类型 + */ + export enum BillingEventType { + /** + * @en + * Called to notify that setup is complete. + * + * @zh + * 当安装已经完成时触发。 + */ + BILLING_SETUP_FINISHED = 'billing_setup_finished', + /** + * @en + * Called to notify that the connection to the billing service was lost. + * + * @zh + * 当Billing服务连接断开时触发。 + */ + BILLING_SERVICE_DISCONNECTED = 'billing_service_disconnected', + /** + * @en + * Listen to this event to get notifications of purchase updates. + * + * @zh + * 监听这个事件可以获取购买更新。 + */ + PURCHASES_UPDATED = 'purchases_updated', + /** + * @en + * Called to notify that query product details operation has finished. + * + * @zh + * 查询产品详细信息操作完成时触发。 + */ + PRODUCT_DETAILS_RESPONSE = 'product_details_response', + /** + * @en + * Called to notify that the query purchases operation has finished. + * + * @zh + * 查询购买操作完成时触发。 + */ + QUERY_PURCHASES_RESPONSE = 'query_purchases_response', + /** + * @en + * Called to notify that a consume operation has finished. + * + * @zh + * 消费操作完成时触发。 + */ + CONSUME_RESPONSE = 'consume_response', + /** + * @en + * Called to notify that an acknowledge purchase operation has finished. + * + * @zh + * 确认购买操作完成时触发。 + */ + ACKNOWLEDGE_PURCHASES_RESPONSE = 'acknowledge_purchases_response', + /** + * @en + * Called to notify when the get billing config flow has finished. + * + * @zh + * 获取Billing配置流程完成时触发。 + */ + BILLING_CONFIG_RESPONSE = 'billing_config_response', + /** + * @en + * Called to receive the results from createAlternativeBillingOnlyReportingDetailsAsync when it is finished. + * + * @zh + * 当调用createAlternativeBillingOnlyReportingDetailsAsync接口完成时触发,可以接收调用结果。 + */ + ALTERNATIVE_BILLING_ONLY_TOKEN_RESPONSE = 'alternative_billing_only_token_response', + /** + * @en + * Called to receive the results from createExternalOfferReportingDetailsAsync when it is finished. + * + * @zh + * 当调用createExternalOfferReportingDetailsAsync接口完成时触发,可以接收调用结果。 + */ + EXTERNAL_OFFER_REPORTING_DETAILS_RESPONSE = 'external_offer_reporting_details_response', + /** + * @en + * Called to receive the results from BillingClient#isAlternativeBillingOnlyAvailableAsync when it is finished. + * + * @zh + * 当调用BillingClient#isAlternativeBillingOnlyAvailableAsync接口完成时触发,可以接收调用结果。 + */ + ALTERNATIVE_BILLING_ONLY_AVAILABILITY_RESPONSE = 'alternative_billing_only_availability_response', + /** + * @en + * Called to receive the results from BillingClient#isExternalOfferAvailableAsync when it is finished. + * + * @zh + * 当调用BillingClient#isExternalOfferAvailableAsync接口完成时触发,可以接收调用结果。 + */ + EXTERNAL_OFFER_AVAILABILITY_RESPONSE = 'external_offer_availability_response', + /** + * @en + * Called to notify that the alternative billing only dialog flow is finished. + * + * @zh + * 当仅替代Billing对话流程已完成时触发。 + */ + ALTERNATIVE_BILLING_ONLY_INFORMATION_DIALOG_RESPONSE = 'alternative_billing_only_information_dialog_response', + /** + * @en + * Called to notify that the external offer information dialog flow is finished. + * + * @zh + * 当外部报价信息对话流程已完成时触发。 + */ + EXTERNAL_OFFER_INFORMATION_DIALOG_RESPONSE = 'external_offer_information_dialog_response', + /** + * @en + * Called to notify when the in-app messaging flow has finished. + * + * @zh + * 当应用内消息流程完成时触发。 + */ + IN_APP_MESSAGE_RESPONSE = 'in_app_message_response', + + } + + /** + * @en + * Supported Product types. + * + * @zh + * 支持的产品类型。 + */ + export enum ProductType { + /** + * @en + * A Product type for Android apps in-app products. + * + * @zh + * Android 应用内产品的产品类型。 + */ + INAPP = 'inapp', + /** + * @en + * A Product type for Android apps subscriptions. + * + * @zh + * Android 应用程序订阅的产品类型。 + */ + SUBS = 'subs' + } + + /** + * @en + * Possible response codes. + * + * @zh + * 可能的响应代码。 + */ + export enum BillingResponseCode { + /** + * @en + * This field is deprecated. + * See SERVICE_UNAVAILABLE which will be used instead of this code. + * + * @zh + * 这个字段已经废弃。 + * 看看SERVICE_UNAVAILABLE将使用哪一个来代替此代码。 + */ + SERVICE_TIMEOUT = -3, + /** + * @en + * The requested feature is not supported by the Play Store on the current device. + * + * @zh + * 当前设备上的 Play Store 不支持所请求的功能。 + */ + FEATURE_NOT_SUPPORTED = -2, + /** + * @en + * The app is not connected to the Play Store service via the Google Play Billing Library. + * + * @zh + * 该应用未通过 Google Play Billing库连接到 Play Store 服务。 + */ + SERVICE_DISCONNECTED = -1, + /** + * @en + * Success. + * + * @zh + * 成功。 + */ + OK = 0, + /** + * @en + * Transaction was canceled by the user. + * + * @zh + * 交易已被用户取消。 + */ + USER_CANCELED = 1, + /** + * @en + * The service is currently unavailable. + * + * @zh + * 当前设备上的 Play Store 不支持所请求的功能。 + */ + SERVICE_UNAVAILABLE = 2, + /** + * @en + * A user billing error occurred during processing. + * + * @zh + * 处理过程中出现用户billing错误。 + */ + BILLING_UNAVAILABLE = 3, + /** + * @en + * The requested product is not available for purchase. + * + * @zh + * 所请求的产品无法购买。 + */ + ITEM_UNAVAILABLE = 4, + /** + * @en + * Error resulting from incorrect usage of the API. + * + * @zh + * 由于错误使用 API 而导致的错误。 + */ + DEVELOPER_ERROR = 5, + /** + * @en + * Fatal error during the API action. + * + * @zh + * API 操作期间发生致命错误。 + */ + ERROR = 6, + /** + * @en + * The purchase failed because the item is already owned. + * + * @zh + * 购买失败,因为该物品已被拥有。 + */ + ITEM_ALREADY_OWNED = 7, + /** + * @en + * Requested action on the item failed since it is not owned by the user. + * + * @zh + * 由于该项目不属于用户,因此对该项目请求的操作失败。 + */ + ITEM_NOT_OWNED = 8, + /** + * @en + * A network error occurred during the operation. + * + * @zh + * 操作期间发生网络错误。 + */ + NETWORK_ERROR = 12, + } + + /** + * @en + * Recurrence mode of the pricing phase. + * + * @zh + * 定价阶段的复现模式。 + */ + export enum RecurrenceMode { + /** + * @en + * The billing plan payment recurs for infinite billing periods unless cancelled. + * + * @zh + * 除非取消,否则billing计划付款将无限期地重复。 + */ + INFINITE_RECURRING = 1, + /** + * @en + * The billing plan payment recurs for a fixed number of billing period set in billingCycleCount. + * + * @zh + * Billing计划付款将在 billingCycleCount 中设置的固定计费周期内重复发生。 + */ + FINITE_RECURRING = 2, + /** + * @en + * The billing plan payment is a one time charge that does not repeat. + * + * @zh + * Billing计划付款是一次性费用,不会重复。 + */ + NON_RECURRING = 3, + } + + /** + * @en + * Connection state of billing client. + * + * @zh + * Billing client的连接状态 + */ + export enum ConnectionState { + /** + * @en + * This client was not yet connected to billing service or was already closed. + * + * @zh + * 此客户端尚未连接到Billing服务或已关闭。 + */ + DISCONNECTED = 0, + /** + * @en + * This client is currently in process of connecting to billing service. + * + * @zh + * 此客户端目前正在连接到Billing服务。 + */ + CONNECTING = 1, + /** + * @en + * This client is currently connected to billing service. + * + * @zh + * 此客户端当前已连接到Billing服务。 + */ + CONNECTED = 2, + /** + * @en + * This client was already closed and shouldn't be used again. + * + * @zh + * 该客户端已关闭,不应再次使用。 + */ + CLOSED = 3, + } + + /** + * @en + * Features/capabilities supported by isFeatureSupported. + * + * @zh + * 支持的特性/能力isFeatureSupported。 + */ + export enum FeatureType { + /** + * @en + * Alternative billing only. + * + * @zh + * 仅限替代Billing。 + */ + ALTERNATIVE_BILLING_ONLY = 'jjj', + /** + * @en + * Get billing config. + * + * @zh + * 获取计费配置。 + */ + BILLING_CONFIG = 'ggg', + /** + * @en + * Play billing library support for external offer. + * + * @zh + * Play billing库支持外部报价。 + */ + EXTERNAL_OFFER = 'kkk', + /** + * @en + * Show in-app messages. + * + * @zh + * 显示应用内消息。 + */ + IN_APP_MESSAGING = 'bbb', + /** + * @en + * Launch a price change confirmation flow. + * + * @zh + * 启动价格变动确认流程。 + */ + PRICE_CHANGE_CONFIRMATION = 'priceChangeConfirmation', + /** + * @en + * Play billing library support for querying and purchasing. + * + * @zh + * Play Billing库支持查询、购买。 + */ + PRODUCT_DETAILS = 'fff', + /** + * @en + * Purchase/query for subscriptions. + * + * @zh + * 购买/查询订阅。 + */ + SUBSCRIPTIONS = 'subscriptions', + /** + * @en + * Subscriptions update/replace. + * + * @zh + * 订阅更新/替换。 + */ + UBSCRIPTIONS_UPDATE = 'subscriptionsUpdate', + } + + /** + * @en + * Possible purchase states. + * + * @zh + * 可能的购买状态。 + */ + export enum PurchaseState { + /** + * @en + * Purchase is pending and not yet completed to be processed by your app. + * + * @zh + * 购买处于待处理状态且尚未完成,无法由您的应用程序处理。 + */ + PENDING = 2, + /** + * @en + * Purchase is completed.. + * + * @zh + * 购买完成。 + */ + PURCHASED = 1, + /** + * @en + * Purchase with unknown state. + * + * @zh + * 未知状态 + */ + UNSPECIFIED_STATE = 0, + } + + /** + * @en + * Possible response codes. + * + * @zh + * InAppMessage可能的影响代码。 + */ + export enum InAppMessageResponseCode { + /** + * @en + * The flow has finished and there is no action needed from developers. + * + * @zh + * 流程已完成,开发人员无需采取任何行动。 + */ + NO_ACTION_NEEDED = 0, + /** + * @en + * The subscription status changed. + * + * @zh + * 订阅状态已改变。 + */ + SUBSCRIPTION_STATUS_UPDATED = 1, + } + + export type BillingResult = jsb.BillingResult; + export type OneTimePurchaseOfferDetails = jsb.OneTimePurchaseOfferDetails; + export type InstallmentPlanDetails = jsb.InstallmentPlanDetails; + export type PricingPhase = jsb.PricingPhase; + export type SubscriptionOfferDetails = jsb.SubscriptionOfferDetails; + export type ProductDetails = jsb.ProductDetails; + export type AccountIdentifiers = jsb.AccountIdentifiers; + export type PendingPurchaseUpdate = jsb.PendingPurchaseUpdate; + export type Purchase = jsb.Purchase; + export type BillingConfig = jsb.BillingConfig; + export type AlternativeBillingOnlyReportingDetails = jsb.AlternativeBillingOnlyReportingDetails; + export type ExternalOfferReportingDetails = jsb.ExternalOfferReportingDetails; + export type InAppMessageResult = jsb.InAppMessageResult; + + /** + * @en + * Interface for Google Play blling module. + * + * @zh + * Google Play blling模块的接口。 + * + */ + export const billing = gpBilling; +} diff --git a/vendor/google/index.ts b/vendor/google/index.ts new file mode 100644 index 00000000000..51359d6f468 --- /dev/null +++ b/vendor/google/index.ts @@ -0,0 +1,25 @@ +/* + Copyright (c) 2024 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 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. +*/ + +export * from './billing/billing';