This commit is contained in:
puxuan 2025-08-05 17:29:22 +08:00
parent 6ef7de12af
commit eb10c29940
73 changed files with 6533 additions and 3508 deletions

View File

@ -18,7 +18,7 @@ namespace BFEditor.Build
private static int VersionCodeRU = 12;
private static string VersionNameRU = "0.4.9";
BFPlatformOptions platform = BFPlatformOptions.AndroidDev;
const string ANDROID_GP_PACKAGE_NAME = "com.c1.dev.android";
const string ANDROID_GP_PACKAGE_NAME = "com.fortune.td.game.global";
public BuildProjectWindow()
{

View File

@ -42,7 +42,7 @@ namespace BF
// 渠道对应的语言配置
public static Dictionary<string, BFLanguageInfo> languageInfos = new Dictionary<string, BFLanguageInfo>()
{
{"com.c1.dev.android", new BFLanguageInfo(new List<string>{"en", "cn", "zh", "th", "ru", "id", "vi"})},
{"com.fortune.td.game.global", new BFLanguageInfo(new List<string>{"en", "cn", "zh", "th", "ru", "id", "vi"})},
{"com.juzu.b6.dev.android", new BFLanguageInfo(new List<string>{"en", "cn", "zh", "th", "ru", "id", "vi"})},
{"com.juzu.b6.dev.ios", new BFLanguageInfo(new List<string>{"en", "cn"})},
{"com.juzu.b6.release.android", new BFLanguageInfo(new List<string>{"en"})},

2
BFVersions/android/dz_dev/build.gradle Normal file → Executable file
View File

@ -8,7 +8,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
// classpath 'com.google.gms:google-services:4.3.13'
classpath 'com.google.gms:google-services:4.3.13'
// classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1'
}
}

0
BFVersions/android/dz_dev/gradle.properties Normal file → Executable file
View File

54
BFVersions/android/dz_dev/unityLibrary/build.gradle Normal file → Executable file
View File

@ -10,16 +10,10 @@ apply plugin: 'com.android.library'
url "https://maven.google.com"
}
maven {
url "https://android-sdk.is.com/" // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:9, Assets/IronSource/Editor/ISAdColonyAdapterDependencies.xml:16, Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:16, Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:8, Assets/IronSource/Editor/ISChartboostAdapterDependencies.xml:8, Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:16, Assets/IronSource/Editor/ISLiftoffAdapterDependencies.xml:16, Assets/IronSource/Editor/ISPangleAdapterDependencies.xml:8, Assets/IronSource/Editor/ISTapJoyAdapterDependencies.xml:8, Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:8
url "https://android-sdk.is.com/" // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:9, Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:16, Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:8, Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:16, Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:8
}
maven {
url "https://maven.google.com/" // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:17, Assets/IronSource/Editor/IronSourceSDKDependencies.xml:25, Assets/IronSource/Editor/ISAdColonyAdapterDependencies.xml:8, Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:8, Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:15, Assets/IronSource/Editor/ISChartboostAdapterDependencies.xml:15, Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:8, Assets/IronSource/Editor/ISLiftoffAdapterDependencies.xml:8, Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:15
}
maven {
url "https://artifact.bytedance.com/repository/pangle/" // Assets/IronSource/Editor/ISPangleAdapterDependencies.xml:15
}
maven {
url "https://sdk.tapjoy.com/" // Assets/IronSource/Editor/ISTapJoyAdapterDependencies.xml:15
url "https://maven.google.com/" // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:17, Assets/IronSource/Editor/IronSourceSDKDependencies.xml:25, Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:8, Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:15, Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:8, Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:15
}
mavenLocal()
jcenter()
@ -38,47 +32,31 @@ dependencies {
implementation(name: 'facebook-android-wrapper-11.0.0', ext:'aar')
implementation 'com.google.android.gms:play-services-auth:20.1.0'
implementation "com.android.billingclient:billing:5.0.0"
// firebase
// implementation 'com.google.firebase:firebase-analytics:21.1.0'
// implementation 'com.google.firebase:firebase-crashlytics:18.2.12'
// implementation 'com.google.firebase:firebase-messaging:23.0.8'
// Android Resolver Dependencies Start
implementation 'com.adcolony:sdk:4.8.0' // Assets/IronSource/Editor/ISAdColonyAdapterDependencies.xml:8
// Android Resolver Dependencies Start
implementation 'com.android.installreferrer:installreferrer:2.1' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerDependencies.xml:10
implementation 'com.android.support:appcompat-v7:25.3.1' // Facebook.Unity.Editor.AndroidSupportLibraryResolver.addSupportLibraryDependency
implementation 'com.android.support:cardview-v7:25.3.1' // Facebook.Unity.Editor.AndroidSupportLibraryResolver.addSupportLibraryDependency
implementation 'com.android.support:customtabs:25.3.1' // Facebook.Unity.Editor.AndroidSupportLibraryResolver.addSupportLibraryDependency
implementation 'com.android.support:support-v4:25.3.1' // Facebook.Unity.Editor.AndroidSupportLibraryResolver.addSupportLibraryDependency
implementation 'com.applovin:applovin-sdk:11.7.1' // Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:15
implementation 'com.applovin:applovin-sdk:11.5.5' // Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:15
implementation 'com.appsflyer:af-android-sdk:6.4.1' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerDependencies.xml:6
implementation 'com.appsflyer:unity-wrapper:6.4.1' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerDependencies.xml:8
implementation 'com.chartboost:chartboost-sdk:9.2.0' // Assets/IronSource/Editor/ISChartboostAdapterDependencies.xml:15
implementation 'com.facebook.android:audience-network-sdk:6.12.0' // Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:8
implementation 'com.facebook.android:facebook-applinks:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:6
implementation 'com.facebook.android:facebook-core:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:5
implementation 'com.facebook.android:facebook-gamingservices:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:9
implementation 'com.facebook.android:facebook-login:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:7
implementation 'com.facebook.android:facebook-share:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:8
implementation 'com.google.android.gms:play-services-ads:21.5.0' // Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:8
implementation 'com.facebook.android:facebook-applinks:[11.0, 12)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:6
implementation 'com.facebook.android:facebook-core:[11.0, 12)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:5
implementation 'com.facebook.android:facebook-gamingservices:[11.0, 12)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:9
implementation 'com.facebook.android:facebook-login:[11.0, 12)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:7
implementation 'com.facebook.android:facebook-share:[11.0, 12)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:8
implementation 'com.google.android.gms:play-services-ads:21.3.0' // Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:8
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:17
implementation 'com.google.android.gms:play-services-basement:18.1.0' // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:25
implementation 'com.ironsource.adapters:adcolonyadapter:4.3.14' // Assets/IronSource/Editor/ISAdColonyAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:admobadapter:4.3.35' // Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:applovinadapter:4.3.37' // Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:chartboostadapter:4.3.11' // Assets/IronSource/Editor/ISChartboostAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:facebookadapter:4.3.39' // Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:liftoffadapter:4.3.5' // Assets/IronSource/Editor/ISLiftoffAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:pangleadapter:4.3.17' // Assets/IronSource/Editor/ISPangleAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:tapjoyadapter:4.1.24' // Assets/IronSource/Editor/ISTapJoyAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:unityadsadapter:4.3.26' // Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:8
implementation 'com.ironsource.sdk:mediationsdk:7.3.0' // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:9
implementation 'com.pangle.global:ads-sdk:5.0.0.8' // Assets/IronSource/Editor/ISPangleAdapterDependencies.xml:15
implementation 'com.ironsource.adapters:admobadapter:4.3.33' // Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:applovinadapter:4.3.35' // Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:facebookadapter:4.3.38' // Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:unityadsadapter:4.3.24' // Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:8
implementation 'com.ironsource.sdk:mediationsdk:7.2.6' // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:9
implementation 'com.parse.bolts:bolts-android:1.4.0' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:4
implementation 'com.tapjoy:tapjoy-android-sdk:12.11.1' // Assets/IronSource/Editor/ISTapJoyAdapterDependencies.xml:15
implementation 'com.unity3d.ads:unity-ads:4.6.0' // Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:15
implementation 'io.liftoff:liftoffads:1.9.1' // Assets/IronSource/Editor/ISLiftoffAdapterDependencies.xml:8
implementation 'com.unity3d.ads:unity-ads:4.4.1' // Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:15
// Android Resolver Dependencies End
}

View File

@ -31,13 +31,7 @@
<!-- Sample AdMob App ID: ca-app-pub-3940256099942544~3347511713 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-8252390069143459~7841203986"/>
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"
android:value="true"/>
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING"
android:value="true"/>
android:value="ca-app-pub-1136292565368915~8864142088"/>
<!-- <meta-data android:name="unity.build-id" android:value="49055670-6ef7-4715-954e-f4b0917c17e3" /> -->
<!-- 每次打包需要替换unity.build-id !-->
REPLACE_BUILD_ID
@ -51,10 +45,15 @@
<uses-feature android:name="android.hardware.vulkan.version" android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK”"/>
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location" android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />

View File

@ -12,11 +12,10 @@ import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.os.Process;
// import android.os.Build;
// import com.chartboost.sdk.Chartboost;
import com.juzu.dz.third.GooglePlugin;
import com.ironsource.mediationsdk.IronSource;
// import com.ironsource.mediationsdk.IronSource;
public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents
{
@ -115,7 +114,7 @@ public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecyc
return;
mUnityPlayer.pause();
IronSource.onPause(this);
// IronSource.onPause(this);
}
// Resume Unity
@ -127,17 +126,7 @@ public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecyc
return;
mUnityPlayer.resume();
IronSource.onResume(this);
}
@Override
public void onBackPressed() {
// If an interstitial is on screen, close it.
// if (Chartboost.onBackPressed()) {
// return;
// } else {
// super.onBackPressed();
// }
// IronSource.onResume(this);
}
// Low Memory Unity

0
BFVersions/android/dz_google_abb/build.gradle Normal file → Executable file
View File

0
BFVersions/android/dz_google_abb/gradle.properties Normal file → Executable file
View File

View File

0
BFVersions/android/dz_google_abb/launcher/build.gradle Normal file → Executable file
View File

View File

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -30,7 +30,7 @@ dependencies {
implementation(name: 'ThinkingSDK', ext:'aar')
implementation(name: 'UnityUtils-release', ext:'aar')
implementation 'com.google.android.gms:play-services-auth:20.1.0'
implementation "com.android.billingclient:billing:5.0.0"
implementation "com.android.billingclient:billing:4.0.0"
implementation platform('com.google.firebase:firebase-bom:29.2.1')
implementation 'com.google.firebase:firebase-analytics:21.1.0'
implementation 'com.google.firebase:firebase-messaging'

View File

@ -21,10 +21,10 @@
<activity android:name="com.facebook.unity.FBUnityGameRequestActivity" />
<activity android:name="com.facebook.unity.FBUnityCreateGameGroupActivity" />
<activity android:name="com.facebook.unity.FBUnityJoinGameGroupActivity" />
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="fb277827051329111" />
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="fb491773936518549" />
<meta-data android:name="com.facebook.sdk.AutoLogAppEventsEnabled" android:value="true" />
<meta-data android:name="com.facebook.sdk.AdvertiserIDCollectionEnabled" android:value="true" />
<provider android:name="com.facebook.FacebookContentProvider" android:authorities="com.facebook.app.FacebookContentProvider277827051329111" android:exported="true" />
<provider android:name="com.facebook.FacebookContentProvider" android:authorities="com.facebook.app.FacebookContentProvider491773936518549" android:exported="true" />
<!-- <uses-permission android:name="com.google.android.gms.permission.AD_ID" /> -->
<meta-data android:name="unity.splash-mode" android:value="0" />
<meta-data android:name="unity.splash-enable" android:value="True" />
@ -32,13 +32,7 @@
<!-- Sample AdMob App ID: ca-app-pub-3940256099942544~3347511713 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-8252390069143459~7841203986"/>
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"
android:value="true"/>
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING"
android:value="true"/>
android:value="ca-app-pub-1136292565368915~8864142088"/>
<!-- <meta-data android:name="unity.build-id" android:value="49055670-6ef7-4715-954e-f4b0917c17e3" /> -->
<!-- 每次打包需要替换unity.build-id !-->
REPLACE_BUILD_ID
@ -52,10 +46,15 @@
<uses-feature android:name="android.hardware.vulkan.version" android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK”"/>
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location" android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />

View File

@ -0,0 +1,30 @@
package com.juzu.dz.message;
/**
* 管理和前端的通信消息以及json字段
* */
public class BFMessage {
// Google登录
public static final int GOOGLE_LOGIN_SUCCESS = 1;
public static final int GOOGLE_LOGIN_FAILED = 2;
// Google登出成功
public static final int GOOGLE_LOGOUT_SUCCESS = 3;
// Google支付
public static final int GOOGLE_PAY_SUCCESS = 4;
public static final int GOOGLE_PAY_FAILED = 5;
public static final int GOOGLE_PAY_CANCEL = 6;
public static final int GOOGLE_CONNECT_SUCCESS = 7;
public static final int GOOGLE_CONNECT_FAILED = 8;
public static final int QUERY_PRODUCT_SUCCESS = 9;
public static final int QUERY_PRODUCT_FAILED = 10;
public static final int QUERY_UNCOMPLETE_ORDER_FINISH = 11;
// Google消耗
public static final int GOOGLE_CONSUME_SUCCESS = 12;
public static final int GOOGLE_CONSUME_FAILED = 13;
// Google登出失败
public static final int GOOGLE_LOGOUT_FAILED = 14;
// fireBaseToken
public static final int FIREBASE_TOKEN = 15;
// Google订阅
public static final int QUERY_SUBSCRIBE_FINISH = 16;
}

View File

@ -0,0 +1,459 @@
package com.juzu.dz.third;
import android.app.Activity;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.billingclient.api.AccountIdentifiers;
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.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.juzu.dz.message.BFMessage;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GoogleBilling {
private static volatile GoogleBilling sInstance;
private static final String LOG_TAG = "GoogleBilling";
private static BillingClient mGoogleBillingClient;
private Map<String, Purchase> mPurchase = new HashMap<String, Purchase>();
private List<SkuDetails> mSubList = new ArrayList<SkuDetails>();
private List<SkuDetails> mInAppList = new ArrayList<SkuDetails>();
final private static String BASE_64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2Vcz26Jz82oYAkr8Wsk0z0hgwBLDRbmnQyARqusiSaJWOt9vEaTgLtLaWYYIiWIeWXUaKq35xDHjC5Xh+KI/pp5N7ck1yB5+lyaSsVD0ESFC/qjDWgElaxxL9QvOyLEHd23q9TmlF1aWziSc6ryFX9HVEGLJT2bTjVEz/bk/WpfN41HZafqQTCIWh8+vRgr0CXa/JEqysjDYRpbaBViPE9F7aTo8lCv/VQpE1ycFiOoaf768aispY5cXb0qP00li95HAI+fEaYgxOuRmN0oc5BFpNRNYTMmRX1Oi5F2vknw+u3+mh+a6GkbwD6FCK+fWIo8hZAWnkULgrxa1ReobQIDAQAB";
public static GoogleBilling getInstance()
{
if (sInstance == null) {
synchronized (GoogleBilling.class) {
if (sInstance == null) {
sInstance = new GoogleBilling();
}
}
}
return sInstance;
}
public void init(Activity activity){
mGoogleBillingClient = BillingClient.newBuilder(activity).setListener(payListener).enablePendingPurchases().build();
connectGoogleStore();
}
public void connectGoogleStore(){
mGoogleBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_CONNECT_SUCCESS, "");
}else{
String msg = billingResult.getDebugMessage();
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_CONNECT_FAILED, msg);
}
}
@Override
public void onBillingServiceDisconnected() {
String msg = "disconnected";
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_CONNECT_FAILED, msg);
}
});
}
// 查询商品信息本地化用
public void queryProductsInfo(String payType, String productInfoJson){
Log.d(LOG_TAG, "google queryProductsInfo info from unity:" + productInfoJson);
try {
JSONArray array = new JSONArray(productInfoJson);
String[] proList = new String[array.length()];
for (int i = 0; i < array.length(); i++){
proList[i] = (String)array.get(i);
}
queryProducts(payType, proList);
} catch (JSONException e) {
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_PRODUCT_FAILED, e.toString());
}
}
//通过商品id查询商品详情
private void queryProducts(String payType, String [] products){
List<String> pList = Arrays.asList(products);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(pList).setType(payType);
mGoogleBillingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> list) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null){
Log.d(LOG_TAG, "queryProducts success count:" + list.size());
if (list.size() == 0){
String msg = "未查询到商品信息,请检查传入的商品id,或者配置是否生效";
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_PRODUCT_FAILED, msg);
return;
}
JSONArray array = new JSONArray();
List<SkuDetails> mList = new ArrayList<SkuDetails>();
for (SkuDetails skuDetails : list) {
mList.add(skuDetails);
String sku = skuDetails.getSku();
String price = skuDetails.getPrice();//实际价格
String originPrice = skuDetails.getOriginalPrice();//原价如果没折扣原价=实际价格
String description = skuDetails.getDescription();
String originalJson = skuDetails.getOriginalJson();
String title = skuDetails.getTitle();
String priceAmountMicros = String.valueOf(skuDetails.getPriceAmountMicros());
String currencyCode = skuDetails.getPriceCurrencyCode();
JSONObject info = new JSONObject();
try {
info.put("sku", sku);
info.put("price", price);
info.put("originPrice", originPrice);
info.put("description", description);
info.put("originalJson", originalJson);
info.put("title", title);
info.put("priceCurrencyCode", currencyCode);
info.put("priceAmountMicros", priceAmountMicros);
} catch (JSONException e) {
e.printStackTrace();
}
array.put(info);
}
if(payType.equals(BillingClient.SkuType.INAPP)){
mInAppList = mList;
}
if(payType.equals(BillingClient.SkuType.SUBS)){
mSubList = mList;
}
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_PRODUCT_SUCCESS, array.toString());
}else{
String msg = "queryProducts error:" + billingResult.getResponseCode();
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_PRODUCT_FAILED, msg);
}
}
});
}
// 支付
public void pay(Activity activity, String payType, String productId, String customMsg){
List<SkuDetails> skuList = null;
if(payType.equals(BillingClient.SkuType.INAPP)){
skuList = mInAppList;
}
if(payType.equals(BillingClient.SkuType.SUBS)){
skuList = mSubList;
}
if (skuList == null || skuList.size() == 0){
String msg = "no sku";
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, msg);
return ;
}
SkuDetails paySku = null;
for (SkuDetails details:skuList){
if (TextUtils.equals(productId, details.getSku())){
paySku = details;
break;
}
}
if (paySku == null){
String msg = "sku not found! please contact developer";
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, msg);
return;
}
// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(paySku)
.setObfuscatedAccountId(customMsg)
.build();
BillingResult result = mGoogleBillingClient.launchBillingFlow(activity, flowParams);
if (result.getResponseCode() != BillingClient.BillingResponseCode.OK){
String msg = "Billing failed: + " + result.getDebugMessage();
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, msg);
}
}
private PurchasesUpdatedListener payListener = new PurchasesUpdatedListener() {
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases) {
switch (billingResult.getResponseCode()) {
case BillingClient.BillingResponseCode.OK:
if (null != purchases) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else {
String msg = "Null Purchase List Returned from OK response!";
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, msg);
}
break;
case BillingClient.BillingResponseCode.USER_CANCELED:
Log.i(LOG_TAG, "onPurchasesUpdated: User canceled the purchase");
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_CANCEL, "");
break;
case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
String msg = "onPurchasesUpdated: The user already owns this item";
Log.i(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, msg);
break;
case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
String msg2 = "onPurchasesUpdated: Developer error means that Google Play " +
"does not recognize the configuration. If you are just getting started, " +
"make sure you have configured the application correctly in the " +
"Google Play Console. The SKU product ID must match and the APK you " +
"are using must be signed with release keys.";
Log.e(LOG_TAG, msg2);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, msg2);
break;
default:
String msg3 = "BillingResult [" + billingResult.getResponseCode() + "]: " + billingResult.getDebugMessage();
Log.d(LOG_TAG, msg3);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, msg3);
}
}
};
private void handlePurchase(Purchase purchase) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
// 支付完成
if(!purchase.isAcknowledged()){
mPurchase.put(purchase.getPurchaseToken(),purchase);
}
//验证签名
String orderId = purchase.getOrderId();
String originalJson = purchase.getOriginalJson();
String purchaseToken = purchase.getPurchaseToken();
ArrayList<String> skus = purchase.getSkus();
String signature = purchase.getSignature();
AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers();
String obfuscatedAccountId = accountIdentifiers.getObfuscatedAccountId();
boolean succ = verifyValidSignature(originalJson, signature);
JSONObject productInfo = new JSONObject();
try {
productInfo.put("orderId", orderId);
productInfo.put("purchaseToken", purchaseToken);
productInfo.put("obfuscatedAccountId", obfuscatedAccountId);
productInfo.put("signtureFlag", succ + "");
if (skus.size() > 0)
{
productInfo.put("productId", skus.get(0));
}
} catch (JSONException e) {
e.printStackTrace();
}
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_SUCCESS, productInfo.toString());
}else{
//未付款
String msg = "purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED";
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, msg);
}
}
//消耗
public void consumeAsync(String purchaseToken){
Purchase purchase = mPurchase.get(purchaseToken);
boolean isSub = false;
if(purchase != null){
if(mSubList != null){
for(SkuDetails Sku:mSubList){
for ( String purchaseSku : purchase.getSkus() ) {
if (purchaseSku.equals(Sku.getSku())) {
isSub = true;
break;
}
}
}
}
}
if(isSub){
handleSubsPurchase(purchase);
}else{
final ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken)
.build();
mGoogleBillingClient.consumeAsync(consumeParams, (billingResult, purchaseToken1) -> {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Log.d(LOG_TAG, "消耗成功...");
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_CONSUME_SUCCESS, "");
} else {
Log.d(LOG_TAG, "消耗失败...");
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_CONSUME_FAILED, "");
}
});
}
}
private void handleSubsPurchase(Purchase purchase) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken())
.build();
mGoogleBillingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Log.d(LOG_TAG, "订阅成功...");
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_CONSUME_SUCCESS, "");
} else {
Log.d(LOG_TAG, "订阅失败...");
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_CONSUME_FAILED, "");
}
}
});
}
}
}
//查询缓存的购买交易
public void queryUncompleteOrder(String payType){
if (mGoogleBillingClient == null){
String msg = "queryPurchases billingClient is null";
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_UNCOMPLETE_ORDER_FINISH, "");
return;
}
mGoogleBillingClient.queryPurchasesAsync(payType, (billingResult, list) -> {
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) {
Log.d(LOG_TAG, "Billing client was null or result code is:" + billingResult.getDebugMessage());
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_UNCOMPLETE_ORDER_FINISH, "");
} else {
//消耗
JSONArray jsonArray = new JSONArray();
for (Purchase purchase : list){
//商品购买成功系统还会生成购买令牌它是一个唯一标识符表示用户及其所购应用内商品的商品 I
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()){
mPurchase.put(purchase.getPurchaseToken(), purchase);
//验证签名
String orderId = purchase.getOrderId();
String originalJson = purchase.getOriginalJson();
String purchaseToken = purchase.getPurchaseToken();
ArrayList<String> skus = purchase.getSkus();
String signature = purchase.getSignature();
AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers();
String obfuscatedAccountId = accountIdentifiers.getObfuscatedAccountId();
JSONObject productInfo = new JSONObject();
try {
productInfo.put("payType", payType);
productInfo.put("orderId", orderId);
productInfo.put("purchaseToken", purchaseToken);
productInfo.put("obfuscatedAccountId", obfuscatedAccountId);
boolean succ = verifyValidSignature(originalJson,signature);
productInfo.put("signtureFlag", String.valueOf(succ));
productInfo.put("purchaseState", "1");
if (skus.size() > 0)
{
productInfo.put("productId", skus.get(0));
}
} catch (JSONException e) {
e.printStackTrace();
}
jsonArray.put(productInfo);
}
}else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING){
ArrayList<String> skus = purchase.getSkus();
JSONObject productInfo = new JSONObject();
try {
productInfo.put("purchaseState", "2");
if (skus.size() > 0)
{
productInfo.put("productId", skus.get(0));
}
} catch (JSONException e) {
e.printStackTrace();
}
jsonArray.put(productInfo);
}
}
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_UNCOMPLETE_ORDER_FINISH, jsonArray.toString());
}
});
}
//查询缓存的訂閲状态
public void querySubscribeOrder(String payType) {
if (mGoogleBillingClient == null) {
String msg = "queryPurchases billingClient is null";
Log.d(LOG_TAG, msg);
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_SUBSCRIBE_FINISH, "");
return;
}
mGoogleBillingClient.queryPurchasesAsync(payType, (billingResult, list) -> {
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) {
Log.d(LOG_TAG, "Billing client was null or result code is:" + billingResult.getDebugMessage());
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_SUBSCRIBE_FINISH, "");
} else {
//消耗
JSONArray jsonArray = new JSONArray();
for (Purchase purchase : list){
//商品购买成功系统还会生成购买令牌它是一个唯一标识符表示用户及其所购应用内商品的商品 I
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
String orderId = purchase.getOrderId();
String originalJson = purchase.getOriginalJson();
String purchaseToken = purchase.getPurchaseToken();
ArrayList<String> skus = purchase.getSkus();
String signature = purchase.getSignature();
AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers();
String obfuscatedAccountId = accountIdentifiers.getObfuscatedAccountId();
JSONObject productInfo = new JSONObject();
try {
productInfo.put("payType", payType);
productInfo.put("orderId", orderId);
productInfo.put("purchaseToken", purchaseToken);
productInfo.put("obfuscatedAccountId", obfuscatedAccountId);
boolean succ = verifyValidSignature(originalJson,signature);
productInfo.put("signtureFlag", String.valueOf(succ));
productInfo.put("purchaseState", "1");
if (skus.size() > 0)
{
productInfo.put("productId", skus.get(0));
}
} catch (JSONException e) {
e.printStackTrace();
}
jsonArray.put(productInfo);
}
}
Log.d(LOG_TAG, "订阅状态查询 " + jsonArray.toString());
GooglePlugin.sendMessageToUnity(BFMessage.QUERY_SUBSCRIBE_FINISH, jsonArray.toString());
}
});
}
private boolean verifyValidSignature(String signedData, String signature) {
return Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature);
}
public void onDestroy(){
if (mGoogleBillingClient != null && mGoogleBillingClient.isReady()){
mGoogleBillingClient.endConnection();
mGoogleBillingClient = null;
}
}
}

View File

@ -0,0 +1,96 @@
package com.juzu.dz.third;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.Scopes;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import org.json.JSONException;
import org.json.JSONObject;
import com.juzu.dz.message.BFMessage;
public class GoogleLogin {
private static volatile GoogleLogin sInstance;
private GoogleSignInClient mGoogleSignInClient;
private static final int REQUEST_CODE_GOOGLE_SIGN_IN = 1001; /* unique request id */
private static final String server_client_token = "526230333982-j465cnveghnr5el3701rtsc38s3l08cg.apps.googleusercontent.com";
public static GoogleLogin getInstance()
{
if (sInstance == null) {
synchronized (GoogleLogin.class) {
if (sInstance == null) {
sInstance = new GoogleLogin();
}
}
}
return sInstance;
}
public void init(Activity activity){
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestId()
.requestIdToken(server_client_token)
.requestServerAuthCode(server_client_token)
.requestScopes(new Scope(Scopes.PLUS_ME))
.build();
mGoogleSignInClient = GoogleSignIn.getClient(activity, gso);
}
public void login(Activity activity)
{
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
activity.startActivityForResult(signInIntent, REQUEST_CODE_GOOGLE_SIGN_IN);
}
public void logout(Activity activity)
{
mGoogleSignInClient.signOut().addOnCompleteListener(activity, new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_LOGOUT_SUCCESS, "");
}
});
}
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == REQUEST_CODE_GOOGLE_SIGN_IN) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
handleSignInResult(task);
}
}
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
try {
GoogleSignInAccount account = completedTask.getResult(ApiException.class);
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_LOGIN_SUCCESS, createLoginInfo(account));
} catch (ApiException e) {
GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_LOGIN_FAILED, "google login failed e:" + e.getStatusCode() + " msg:" + e.getMessage());
}
}
private String createLoginInfo(GoogleSignInAccount account){
String idToken = account.getIdToken();
String uid = account.getId();
JSONObject result = new JSONObject();
try {
result.put("Token", idToken);
result.put("UserId", uid);
} catch (JSONException e) {
e.printStackTrace();
}
return result.toString();
}
}

View File

@ -0,0 +1,148 @@
package com.juzu.dz.third;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
// import com.google.firebase.messaging.FirebaseMessaging;
import com.juzu.dz.message.BFMessage;
import com.unity3d.player.UnityPlayer;
import org.json.JSONException;
import org.json.JSONObject;
public class GooglePlugin {
private static final String LOG_TAG = "GooglePlugin";
private static GoogleLogin mGoogleLogin = null;
private static GoogleBilling mGoogleBilling = null;
private static Activity _activity;
public static void init(Activity activity){
_activity = activity;
}
public static void initLogin()
{
if (mGoogleLogin == null)
{
mGoogleLogin = GoogleLogin.getInstance();
mGoogleLogin.init(_activity);
}
}
public static void initBilling()
{
if (mGoogleBilling == null)
{
mGoogleBilling = GoogleBilling.getInstance();
mGoogleBilling.init(_activity);
}
}
// 登录
public static void login()
{
if (mGoogleLogin != null){
mGoogleLogin.login(_activity);
}else{
sendMessageToUnity(BFMessage.GOOGLE_LOGIN_FAILED, "google login failed: not init");
}
}
// 登出
public static void logout()
{
if (mGoogleLogin != null){
mGoogleLogin.logout(_activity);
}else{
sendMessageToUnity(BFMessage.GOOGLE_LOGOUT_FAILED, "");
}
}
public static void pay(String payType, String productId, String customMsg)
{
if (mGoogleBilling != null){
mGoogleBilling.pay(_activity, payType, productId, customMsg);
}else{
sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, "billing not init");
}
}
public static void queryProductsInfo(String payType, String productJson)
{
if (mGoogleBilling != null){
mGoogleBilling.queryProductsInfo(payType, productJson);
}else{
sendMessageToUnity(BFMessage.QUERY_PRODUCT_FAILED, "billing not init");
}
}
public static void consumeAsync(String purchaseToken)
{
if (mGoogleBilling != null){
mGoogleBilling.consumeAsync(purchaseToken);
}else{
sendMessageToUnity(BFMessage.GOOGLE_CONSUME_FAILED, "billing not init");
}
}
public static void connectGoogleStore()
{
if (mGoogleBilling != null){
mGoogleBilling.connectGoogleStore();
}else{
sendMessageToUnity(BFMessage.GOOGLE_CONNECT_FAILED, "billing not init");
}
}
public static void queryUncompleteOrder(String payType)
{
if (mGoogleBilling != null){
mGoogleBilling.queryUncompleteOrder(payType);
}else{
sendMessageToUnity(BFMessage.QUERY_UNCOMPLETE_ORDER_FINISH, "");
}
}
public static void querySubscribeOrder(String payType)
{
if (mGoogleBilling != null){
mGoogleBilling.querySubscribeOrder(payType);
}else{
sendMessageToUnity(BFMessage.QUERY_SUBSCRIBE_FINISH, "");
}
}
public static void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (mGoogleLogin != null){
mGoogleLogin.onActivityResult(requestCode, resultCode, data);
}
}
public static void onDestroy()
{
if (mGoogleBilling != null){
mGoogleBilling.onDestroy();
}
}
public static void getFirebaseToken()
{
}
public static void sendMessageToUnity(int code, String msg){
JSONObject data = new JSONObject();
try {
data.put("head", code);
data.put("body", msg);
} catch (JSONException e) {
e.printStackTrace();
}
Log.d(LOG_TAG, code + ":" + msg);
UnityPlayer.UnitySendMessage("SDKManager", "MsgFromAndroidOrIOS", data.toString());
}
}

View File

@ -0,0 +1,136 @@
/*
* Copyright (C) 2021 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.juzu.dz.third;
/*
* This class is an sample of how you can check to make sure your purchases on the device came
* from Google Play. Putting code like this on your server will provide additional protection.
* <p>
* One thing that you may also wish to consider doing is caching purchase IDs to make replay
* attacks harder. The reason this code isn't just part of the library is to allow
* you to customize it (and rename it!) to make generic patching exploits more difficult.
*/
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
/**
* Security-related methods. For a secure implementation, all of this code should be implemented on
* a server that communicates with the application on the device.
*/
class Security {
static final private String TAG = "IABUtil/Security";
static final private String KEY_FACTORY_ALGORITHM = "RSA";
static final private String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* BASE_64_ENCODED_PUBLIC_KEY should be YOUR APPLICATION PUBLIC KEY. You currently get this
* from the Google Play developer console under the "Monetization Setup" category in the
* Licensing area. This build has been setup so that if you define base64EncodedPublicKey in
* your local.properties, it will be echoed into BuildConfig.
*/
/**
* Verifies that the data was signed with the given signature
*
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
*/
static public boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if ((TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
|| TextUtils.isEmpty(signature))
) {
Log.w(TAG, "Purchase verification failed: missing data.");
return false;
}
try {
PublicKey key = generatePublicKey(base64PublicKey);
return verify(key, signedData, signature);
} catch (IOException e) {
Log.e(TAG, "Error generating PublicKey from encoded key: " + e.getMessage());
return false;
}
}
/**
* Generates a PublicKey instance from a string containing the Base64-encoded public key.
*
* @param encodedPublicKey Base64-encoded public key
* @throws IOException if encoding algorithm is not supported or key specification
* is invalid
*/
static private PublicKey generatePublicKey(String encodedPublicKey) throws IOException {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
// "RSA" is guaranteed to be available.
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
String msg = "Invalid key specification: " + e;
Log.w(TAG, msg);
throw new IOException(msg);
}
}
/**
* Verifies that the signature from the server matches the computed signature on the data.
* Returns true if the data is correctly signed.
*
* @param publicKey public key associated with the developer account
* @param signedData signed data from server
* @param signature server signature
* @return true if the data and signature match
*/
static private Boolean verify(PublicKey publicKey, String signedData, String signature) {
byte[] signatureBytes;
try {
signatureBytes = Base64.decode(signature, Base64.DEFAULT);
} catch (IllegalArgumentException e) {
Log.w(TAG, "Base64 decoding failed.");
return false;
}
try {
Signature signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM);
signatureAlgorithm.initVerify(publicKey);
signatureAlgorithm.update(signedData.getBytes());
if (!signatureAlgorithm.verify(signatureBytes)) {
Log.w(TAG, "Signature verification failed...");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
// "RSA" is guaranteed to be available.
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
}
return false;
}
}

2
BFVersions/android/dz_google_apk/build.gradle Normal file → Executable file
View File

@ -5,11 +5,13 @@ buildscript {
google()
jcenter()
mavenCentral()
maven { url 'https://artifacts.applovin.com/android'; content { includeGroupByRegex 'com.applovin.*' } }
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.3'
classpath 'com.google.gms:google-services:4.3.13'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1'
classpath 'com.applovin.quality:AppLovinQualityServiceGradlePlugin:+'
}
}

1
BFVersions/android/dz_google_apk/gradle.properties Normal file → Executable file
View File

@ -3,3 +3,4 @@ org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true
unityStreamingAssets=.unity3d, .bytes, .ab, UnityServicesProjectConfiguration.json
unityTemplateVersion=4

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ironsource.unity">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application>
<!-- AdMob -->
<!--As Requiered By Admob please add your App ID-->
<!--<meta-data-->
<!--android:name="com.google.android.gms.ads.APPLICATION_ID"-->
<!--android:value="YOUR_ADMOB_APP_ID"/>-->
</application>
</manifest>

View File

@ -1,28 +0,0 @@
apply plugin: 'android-library'
dependencies {
implementation fileTree(dir: 'bin', include: ['*.jar'])
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
//java.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
compileSdkVersion 34
buildToolsVersion '30.0.2'
defaultConfig {
targetSdkVersion 34
}
lintOptions {
abortOnError false
}
}

View File

@ -1,2 +0,0 @@
target=android-9
android.library=true

View File

@ -11,28 +11,26 @@ apply plugin: 'com.android.library'
url "https://maven.google.com"
}
maven {
url "https://android-sdk.is.com/" // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:9, Assets/IronSource/Editor/ISAdColonyAdapterDependencies.xml:16, Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:16, Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:8, Assets/IronSource/Editor/ISChartboostAdapterDependencies.xml:8, Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:16, Assets/IronSource/Editor/ISFyberAdapterDependencies.xml:16, Assets/IronSource/Editor/ISMintegralAdapterDependencies.xml:48, Assets/IronSource/Editor/ISPangleAdapterDependencies.xml:8, Assets/IronSource/Editor/ISTapJoyAdapterDependencies.xml:8, Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:8, Assets/IronSource/Editor/ISVungleAdapterDependencies.xml:16, Assets/IronSourceAdQuality/Editor/IronSourceAdQualityDependencies.xml:9, Assets/IronSourceAdQuality/Editor/IronSourceAdQualityDependencies.xml:17
url "https://verve.jfrog.io/artifactory/verve-gradle-release" // Assets/ThirdParty/MaxSdk/Mediation/Verve/Editor/Dependencies.xml:7
}
maven {
url "https://maven.google.com/" // Assets/IronSource/Editor/IronSourceSDKDependencies.xml:17, Assets/IronSource/Editor/IronSourceSDKDependencies.xml:25, Assets/IronSource/Editor/ISAdColonyAdapterDependencies.xml:8, Assets/IronSource/Editor/ISAdMobAdapterDependencies.xml:8, Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:15, Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:8, Assets/IronSource/Editor/ISMintegralAdapterDependencies.xml:40, Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:15
url "https://artifactory.bidmachine.io/bidmachine" // Assets/ThirdParty/MaxSdk/Mediation/BidMachine/Editor/Dependencies.xml:8
}
maven {
url "https://cboost.jfrog.io/artifactory/chartboost-ads/" // Assets/IronSource/Editor/ISChartboostAdapterDependencies.xml:15
url "https://cboost.jfrog.io/artifactory/chartboost-ads/" // Assets/ThirdParty/MaxSdk/Mediation/Chartboost/Editor/Dependencies.xml:8
}
maven {
url "https://repo.maven.apache.org/maven2/" // Assets/IronSource/Editor/ISFyberAdapterDependencies.xml:8
url "https://android-sdk.is.com/" // Assets/ThirdParty/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
}
maven {
url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea/" // Assets/IronSource/Editor/ISMintegralAdapterDependencies.xml:8, Assets/IronSource/Editor/ISMintegralAdapterDependencies.xml:16, Assets/IronSource/Editor/ISMintegralAdapterDependencies.xml:24, Assets/IronSource/Editor/ISMintegralAdapterDependencies.xml:32
url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea" // Assets/ThirdParty/MaxSdk/Mediation/Mintegral/Editor/Dependencies.xml:8
}
maven {
url "https://artifact.bytedance.com/repository/pangle/" // Assets/IronSource/Editor/ISPangleAdapterDependencies.xml:15
url "https://artifact.bytedance.com/repository/pangle" // Assets/ThirdParty/MaxSdk/Mediation/ByteDance/Editor/Dependencies.xml:8
}
maven { url "../Upload-TaurusX" }
maven {
url "https://sdk.tapjoy.com/" // Assets/IronSource/Editor/ISTapJoyAdapterDependencies.xml:15
}
maven {
url "https://jitpack.io/" // Assets/IronSource/Editor/ISVungleAdapterDependencies.xml:8
url "https://bitbucket.org/sdkcenter/sdkcenter/raw/release"
}
mavenLocal()
jcenter()
@ -44,78 +42,66 @@ apply plugin: 'com.android.library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:multidex:1.0.3'
// IAB TCF Decoder
implementation("com.iabtcf:iabtcf-decoder:2.0.10")
implementation(name: 'screenutils-release', ext:'aar')
implementation(name: 'ThinkingSDK', ext:'aar')
implementation(name: 'UnityUtils-release', ext:'aar')
implementation(name: 'LofeltHaptics', ext:'aar')
implementation(name: 'facebook-android-wrapper-15.1.0', ext:'aar')
implementation 'com.google.android.gms:play-services-auth:20.1.0'
implementation(name: 'applovin-max-unity-plugin', ext:'aar')
implementation "com.android.billingclient:billing:6.2.1"
implementation 'com.google.android.gms:play-services-auth:20.1.0'
implementation 'com.google.android.play:review:2.0.1'
// firebase
implementation 'com.google.firebase:firebase-analytics:21.1.0'
implementation 'com.google.firebase:firebase-crashlytics:18.2.12'
implementation 'com.google.firebase:firebase-messaging:23.0.8'
// adjust
implementation(name: 'adjust-android-signaturev2-v2.15.10-s2', ext:'aar')
implementation project('IronSource.androidlib')
// Android Resolver Dependencies Start
implementation 'androidx.recyclerview:recyclerview:1.2.1' // Assets/IronSource/Editor/ISMintegralAdapterDependencies.xml:40
implementation 'com.adcolony:sdk:4.8.0' // Assets/IronSource/Editor/ISAdColonyAdapterDependencies.xml:8
// Android Resolver Dependencies Start
implementation 'com.google.firebase:firebase-analytics:21.4.0'
implementation 'com.google.firebase:firebase-crashlytics:18.5.0'
implementation 'com.google.firebase:firebase-messaging:23.3.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1' // Assets/ThirdParty/MaxSdk/Mediation/Mintegral/Editor/Dependencies.xml:9
implementation 'com.android.installreferrer:installreferrer:2.1' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerDependencies.xml:10
implementation 'com.android.support:appcompat-v7:25.3.1' // Facebook.Unity.Editor.AndroidSupportLibraryResolver.addSupportLibraryDependency
implementation 'com.android.support:cardview-v7:25.3.1' // Facebook.Unity.Editor.AndroidSupportLibraryResolver.addSupportLibraryDependency
implementation 'com.android.support:customtabs:25.3.1' // Facebook.Unity.Editor.AndroidSupportLibraryResolver.addSupportLibraryDependency
implementation 'com.android.support:customtabs:28.+' // Assets/ThirdParty/MaxSdk/Mediation/InMobi/Editor/Dependencies.xml:7
implementation 'com.android.support:recyclerview-v7:28.+' // Assets/ThirdParty/MaxSdk/Mediation/InMobi/Editor/Dependencies.xml:6
implementation 'com.android.support:support-v4:25.3.1' // Facebook.Unity.Editor.AndroidSupportLibraryResolver.addSupportLibraryDependency
implementation 'com.applovin:applovin-sdk:11.10.1' // Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:15
implementation 'com.appsflyer:af-android-sdk:6.4.1' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerDependencies.xml:6
implementation 'com.appsflyer:unity-wrapper:6.4.1' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerDependencies.xml:8
implementation 'com.appsflyer:adrevenue:6.5.4' // Assets/ThirdParty/AppsFlyerAdrevenue/Editor/AppsFlyerAdRevenueDependencies.xml:4
implementation 'com.appsflyer:unity-adrevenue-generic-wrapper:6.5.4' // Assets/ThirdParty/AppsFlyerAdrevenue/Editor/AppsFlyerAdRevenueDependencies.xml:5
implementation 'com.chartboost:chartboost-sdk:9.3.1' // Assets/IronSource/Editor/ISChartboostAdapterDependencies.xml:15
implementation 'com.facebook.android:audience-network-sdk:6.16.0' // Assets/ThirdParty/IronSource/Editor/ISFacebookAdapterDependencies.xml:8
implementation 'com.applovin.mediation:bidmachine-adapter:3.2.1.0' // Assets/ThirdParty/MaxSdk/Mediation/BidMachine/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:bigoads-adapter:5.2.1.0' // Assets/ThirdParty/MaxSdk/Mediation/BigoAds/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:bytedance-adapter:6.5.0.8.1' // Assets/ThirdParty/MaxSdk/Mediation/ByteDance/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:chartboost-adapter:9.8.3.0' // Assets/ThirdParty/MaxSdk/Mediation/Chartboost/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:facebook-adapter:[6.19.0.2]' // Assets/ThirdParty/MaxSdk/Mediation/Facebook/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:fyber-adapter:8.3.6.1' // Assets/ThirdParty/MaxSdk/Mediation/Fyber/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:google-adapter:[24.2.0.0]' // Assets/ThirdParty/MaxSdk/Mediation/Google/Editor/Dependencies.xml:5
implementation 'com.applovin.mediation:google-ad-manager-adapter:[24.2.0.0]' // Assets/ThirdParty/MaxSdk/Mediation/GoogleAdManager/Editor/Dependencies.xml:5
implementation 'com.applovin.mediation:inmobi-adapter:10.8.2.0' // Assets/ThirdParty/MaxSdk/Mediation/InMobi/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:ironsource-adapter:8.7.0.0.0' // Assets/ThirdParty/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:line-adapter:2025.1.10.1' // Assets/ThirdParty/MaxSdk/Mediation/Line/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:mobilefuse-adapter:1.9.0.0' // Assets/ThirdParty/MaxSdk/Mediation/MobileFuse/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:moloco-adapter:3.8.0.0' // Assets/ThirdParty/MaxSdk/Mediation/Moloco/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:mytarget-adapter:5.27.1.2' // Assets/ThirdParty/MaxSdk/Mediation/MyTarget/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:unityads-adapter:4.14.1.0' // Assets/ThirdParty/MaxSdk/Mediation/UnityAds/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:verve-adapter:3.3.0.0' // Assets/ThirdParty/MaxSdk/Mediation/Verve/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:vungle-adapter:7.4.3.2' // Assets/ThirdParty/MaxSdk/Mediation/Vungle/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:mintegral-adapter:16.9.61.0' // Assets/ThirdParty/MaxSdk/Mediation/Mintegral/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:yandex-adapter:7.9.0.1' // Assets/ThirdParty/MaxSdk/Mediation/Yandex/Editor/Dependencies.xml:4
implementation "com.taurusx.tax:ads:1.5.4" // Assets/ThirdParty/MaxSdk/Mediation/TaurusX/Editor/Dependencies.xml:4
implementation "com.applovin.mediation:taurusXAdapters:1.3.0.1"
implementation 'com.applovin:applovin-sdk:13.1.0' // Assets/ThirdParty/MaxSdk/AppLovin/Editor/Dependencies.xml:4
implementation 'com.appsflyer:adrevenue:6.9.1' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerAdRevenueDependencies.xml:4
implementation 'com.appsflyer:unity-adrevenue-generic-wrapper:6.9.1' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerAdRevenueDependencies.xml:5
implementation 'com.appsflyer:af-android-sdk:6.13.0' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerDependencies.xml:6
implementation 'com.appsflyer:unity-wrapper:6.13.10' // Assets/ThirdParty/AppsFlyer/Editor/AppsFlyerDependencies.xml:8
implementation 'com.facebook.android:facebook-applinks:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:6
implementation 'com.facebook.android:facebook-core:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:5
implementation 'com.facebook.android:facebook-gamingservices:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:9
implementation 'com.facebook.android:facebook-login:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:7
implementation 'com.facebook.android:facebook-share:[15.1,16)' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:8
implementation 'com.fyber:marketplace-sdk:8.2.4' // Assets/ThirdParty/IronSource/Editor/ISFyberAdapterDependencies.xml:8
implementation 'com.google.android.gms:play-services-ads:22.2.0' // Assets/ThirdParty/IronSource/Editor/ISAdMobAdapterDependencies.xml:8
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' // Assets/ThirdParty/IronSource/Editor/IronSourceSDKDependencies.xml:17
implementation 'com.google.android.gms:play-services-basement:18.1.0' // Assets/ThirdParty/IronSource/Editor/IronSourceSDKDependencies.xml:25
implementation 'com.ironsource.adapters:adcolonyadapter:4.3.15' // Assets/ThirdParty/IronSource/Editor/ISAdColonyAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:admobadapter:4.3.39' // Assets/ThirdParty/IronSource/Editor/ISAdMobAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:applovinadapter:4.3.39' // Assets/ThirdParty/IronSource/Editor/ISAppLovinAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:chartboostadapter:4.3.12' // Assets/ThirdParty/IronSource/Editor/ISChartboostAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:facebookadapter:4.3.45' // Assets/ThirdParty/IronSource/Editor/ISFacebookAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:fyberadapter:4.3.28' // Assets/ThirdParty/IronSource/Editor/ISFyberAdapterDependencies.xml:16
implementation 'com.ironsource.adapters:mintegraladapter:4.3.19' // Assets/ThirdParty/IronSource/Editor/ISMintegralAdapterDependencies.xml:48
implementation 'com.ironsource.adapters:pangleadapter:4.3.22' // Assets/ThirdParty/IronSource/Editor/ISPangleAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:tapjoyadapter:4.1.25' // Assets/ThirdParty/IronSource/Editor/ISTapJoyAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:unityadsadapter:4.3.33' // Assets/ThirdParty/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:8
implementation 'com.ironsource.adapters:vungleadapter:4.3.22' // Assets/ThirdParty/IronSource/Editor/ISVungleAdapterDependencies.xml:16
implementation 'com.ironsource.sdk:mediationsdk:7.5.1' // Assets/ThirdParty/IronSource/Editor/IronSourceSDKDependencies.xml:9
implementation 'com.mbridge.msdk.oversea:mbbanner:16.5.21' // Assets/ThirdParty/IronSource/Editor/ISMintegralAdapterDependencies.xml:24
implementation 'com.mbridge.msdk.oversea:mbbid:16.5.21' // Assets/ThirdParty/IronSource/Editor/ISMintegralAdapterDependencies.xml:32
implementation 'com.mbridge.msdk.oversea:newinterstitial:16.5.21' // Assets/ThirdParty/IronSource/Editor/ISMintegralAdapterDependencies.xml:8
implementation 'com.mbridge.msdk.oversea:reward:16.5.21' // Assets/ThirdParty/IronSource/Editor/ISMintegralAdapterDependencies.xml:16
implementation 'com.pangle.global:ads-sdk:5.5.0.5' // Assets/ThirdParty/IronSource/Editor/ISPangleAdapterDependencies.xml:15
implementation 'com.google.android.gms:play-services-base:16.1.0' // Assets/ThirdParty/MaxSdk/Mediation/Chartboost/Editor/Dependencies.xml:9
implementation 'com.parse.bolts:bolts-android:1.4.0' // Assets/ThirdParty/FacebookSDK/Plugins/Editor/Dependencies.xml:4
implementation 'com.tapjoy:tapjoy-android-sdk:13.0.1' // Assets/ThirdParty/IronSource/Editor/ISTapJoyAdapterDependencies.xml:15
implementation 'com.unity3d.ads:unity-ads:4.9.1' // Assets/ThirdParty/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:15
implementation 'com.vungle:vungle-ads:7.0.0' // Assets/ThirdParty/IronSource/Editor/ISVungleAdapterDependencies.xml:8
implementation 'com.squareup.picasso:picasso:2.71828' // Assets/ThirdParty/MaxSdk/Mediation/InMobi/Editor/Dependencies.xml:5
implementation 'com.google.android.ump:user-messaging-platform:2.1.0'
// Android Resolver Dependencies End
constraints {
implementation('androidx.work:work-runtime:2.7.0') {
because '''androidx.work:work-runtime:2.1.0 pulled from
play-services-ads has a bug using PendingIntent without
FLAG_IMMUTABLE or FLAG_MUTABLE and will fail in Apps
targeting S+.'''
}
}
}
android {
@ -159,34 +145,45 @@ def getSdkDir() {
return local.getProperty('sdk.dir')
}
def BuildIl2Cpp(String workingDir, String targetDirectory, String architecture, String abi, String configuration) {
def BuildIl2Cpp(String workingDir, String configuration, String architecture, String abi, String[] staticLibraries) {
def commandLineArgs = []
commandLineArgs.add("--compile-cpp")
commandLineArgs.add("--platform=Android")
commandLineArgs.add("--architecture=" + architecture)
commandLineArgs.add("--outputpath=" + workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.so")
commandLineArgs.add("--libil2cpp-static")
commandLineArgs.add("--baselib-directory=" + workingDir + "/src/main/jniStaticLibs/" + abi)
commandLineArgs.add("--incremental-g-c-time-slice=3")
commandLineArgs.add("--configuration=" + configuration)
commandLineArgs.add("--dotnetprofile=unityaot-linux")
commandLineArgs.add("--profiler-report")
commandLineArgs.add("--profiler-output-file=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_conv.traceevents")
commandLineArgs.add("--print-command-line")
commandLineArgs.add("--generatedcppdir=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput")
commandLineArgs.add("--cachedirectory=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_cache")
commandLineArgs.add("--tool-chain-path=" + android.ndkDirectory)
staticLibraries.eachWithIndex {fileName, i->
commandLineArgs.add("--additional-libraries=" + workingDir + "/src/main/jniStaticLibs/" + abi + "/" + fileName)
}
def executableExtension = ""
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
executableExtension = ".exe"
commandLineArgs = commandLineArgs*.replace('\"', '\\\"')
}
exec {
commandLine(workingDir + "/src/main/Il2CppOutputProject/IL2CPP/build/deploy/netcoreapp3.1/il2cpp",
"--compile-cpp",
"--libil2cpp-static",
"--platform=Android",
"--architecture=" + architecture,
"--configuration=" + configuration,
"--outputpath=" + workingDir + targetDirectory + abi + "/libil2cpp.so",
"--cachedirectory=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_cache",
"--additional-include-directories=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/external/bdwgc/include",
"--additional-include-directories=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/libil2cpp/include",
"--tool-chain-path=" + android.ndkDirectory,
"--map-file-parser=" + workingDir + "/src/main/Il2CppOutputProject/IL2CPP/MapFileParser/MapFileParser.exe",
"--generatedcppdir=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput",
"--baselib-directory=" + workingDir + "/src/main/jniStaticLibs/" + abi,
"--dotnetprofile=unityaot")
executable workingDir + "/src/main/Il2CppOutputProject/IL2CPP/build/deploy/il2cpp" + executableExtension
args commandLineArgs
environment "ANDROID_SDK_ROOT", getSdkDir()
}
delete workingDir + targetDirectory + abi + "/libil2cpp.sym.so"
ant.move(file: workingDir + targetDirectory + abi + "/libil2cpp.dbg.so", tofile: workingDir + "/symbols/" + abi + "/libil2cpp.so")
delete workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.sym.so"
ant.move(file: workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.dbg.so", tofile: workingDir + "/symbols/" + abi + "/libil2cpp.so")
}
android {
task BuildIl2CppTask {
doLast {
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'ARMv7', 'armeabi-v7a', 'Release');
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'ARM64', 'arm64-v8a', 'Release');
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), 'Release', 'armv7', 'armeabi-v7a', [ ] as String[]);
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), 'Release', 'arm64', 'arm64-v8a', [ ] as String[]);
}
}
afterEvaluate {

View File

@ -3,8 +3,6 @@
<supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
<application
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:label="@string/app_name"
android:icon="@mipmap/app_icon"
android:networkSecurityConfig="@xml/network_security_config">
<activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:launchMode="singleTask" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" android:resizeableActivity="false" android:hardwareAccelerated="false" android:exported="true">
<intent-filter>
@ -14,7 +12,7 @@
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
<!-- [START firebase_service] -->
<meta-data
<!-- <meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/firebase_icon" />
<service
@ -23,7 +21,7 @@
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</service> -->
<!-- [END firebase_service] -->
<activity android:name="com.facebook.unity.FBUnityLoginActivity" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />
<activity android:name="com.facebook.unity.FBUnityDialogsActivity" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />
@ -33,26 +31,35 @@
<activity android:name="com.facebook.unity.FBUnityGameRequestActivity" />
<activity android:name="com.facebook.unity.FBUnityCreateGameGroupActivity" />
<activity android:name="com.facebook.unity.FBUnityJoinGameGroupActivity" />
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="fb277827051329111" />
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="cd6be8cc57dff789f3476ee1b25e2410"/>
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="fb491773936518549" />
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="2d2cb45aeb5ddb18189e8b7378ddcaa5"/>
<meta-data android:name="com.facebook.sdk.AutoLogAppEventsEnabled" android:value="true" />
<meta-data android:name="com.facebook.sdk.AdvertiserIDCollectionEnabled" android:value="true" />
<provider android:name="com.facebook.FacebookContentProvider" android:authorities="com.facebook.app.FacebookContentProvider277827051329111" android:exported="true" />
<provider android:name="com.facebook.FacebookContentProvider" android:authorities="com.facebook.app.FacebookContentProvider491773936518549" android:exported="true" />
<meta-data android:name="unity.splash-mode" android:value="0" />
<meta-data android:name="unity.splash-enable" android:value="True" />
<meta-data android:name="unity.allow-resizable-window" android:value="False" />
<!--本地推送-->
<receiver android:name="com.unity.androidnotifications.UnityNotificationManager" android:exported="false" />
<receiver android:name="com.unity.androidnotifications.UnityNotificationRestartOnBootReceiver" android:enabled="false" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<meta-data android:name="com.unity.androidnotifications.exact_scheduling" android:value="0" />
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-8252390069143459~7841203986"/>
android:value="ca-app-pub-8252390069143459~5042842686"/>
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"
android:value="true"/>
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING"
android:value="true"/>
<meta-data android:name="applovin.sdk.key" android:value="9uHgeBwag3NXva9MC23ToO3q11Ve59bF1uwg4qGltdGmCQ7OSByFZ_3b1ZF7krMlkHQo5gXzIokVDsvg1rwbr-" />
<!-- <meta-data android:name="unity.build-id" android:value="49055670-6ef7-4715-954e-f4b0917c17e3" /> -->
<!-- 每次打包需要替换unity.build-id !-->
REPLACE_BUILD_ID
<!-- REPLACE_BUILD_ID -->
<!-- 刘海屏相关 -->
<!--华为-->
<meta-data android:name="android.notch_support" android:value="true" />
@ -64,8 +71,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name='android.permission.POST_NOTIFICATIONS'/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location" android:required="false" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
@ -76,4 +83,5 @@
<uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
<uses-permission android:name="android.permission.VIBRATE" />
</manifest>

View File

@ -12,11 +12,10 @@ import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.os.Process;
// import android.os.Build;
// import com.chartboost.sdk.Chartboost;
import com.juzu.dz.third.GooglePlugin;
import com.ironsource.mediationsdk.IronSource;
// import com.ironsource.mediationsdk.IronSource;
public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents
{
@ -111,11 +110,13 @@ public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecyc
{
super.onPause();
MultiWindowSupport.saveMultiWindowMode(this);
if (MultiWindowSupport.getAllowResizableWindow(this))
return;
mUnityPlayer.pause();
IronSource.onPause(this);
// IronSource.onPause(this);
}
// Resume Unity
@ -123,21 +124,11 @@ public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecyc
{
super.onResume();
if (MultiWindowSupport.getAllowResizableWindow(this))
if (MultiWindowSupport.getAllowResizableWindow(this) && !MultiWindowSupport.isMultiWindowModeChangedToTrue(this))
return;
mUnityPlayer.resume();
IronSource.onResume(this);
}
@Override
public void onBackPressed() {
// If an interstitial is on screen, close it.
// if (Chartboost.onBackPressed()) {
// return;
// } else {
// super.onBackPressed();
// }
// IronSource.onResume(this);
}
// Low Memory Unity

0
BFVersions/android/dz_release/build.gradle Normal file → Executable file
View File

0
BFVersions/android/dz_release/gradle.properties Normal file → Executable file
View File

View File

@ -30,7 +30,7 @@ dependencies {
implementation(name: 'ThinkingSDK', ext:'aar')
implementation(name: 'UnityUtils-release', ext:'aar')
implementation 'com.google.android.gms:play-services-auth:20.1.0'
implementation "com.android.billingclient:billing:5.0.0"
implementation "com.android.billingclient:billing:4.0.0"
implementation platform('com.google.firebase:firebase-bom:29.2.1')
implementation 'com.google.firebase:firebase-analytics:21.1.0'
implementation 'com.google.firebase:firebase-messaging'

View File

@ -19,13 +19,7 @@
<!-- Sample AdMob App ID: ca-app-pub-3940256099942544~3347511713 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-8252390069143459~7841203986"/>
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"
android:value="true"/>
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING"
android:value="true"/>
android:value="ca-app-pub-8252390069143459~1801140942"/>
<!-- <meta-data android:name="unity.build-id" android:value="49055670-6ef7-4715-954e-f4b0917c17e3" /> -->
<!-- 每次打包需要替换unity.build-id !-->
REPLACE_BUILD_ID
@ -39,10 +33,15 @@
<uses-feature android:name="android.hardware.vulkan.version" android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK”"/>
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location" android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />

View File

@ -14,7 +14,7 @@ import android.view.WindowManager;
import android.os.Process;
// import android.os.Build;
// import com.chartboost.sdk.Chartboost;
import com.chartboost.sdk.Chartboost;
import com.juzu.dz.third.GooglePlugin;
import com.ironsource.mediationsdk.IronSource;
@ -133,11 +133,11 @@ public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecyc
@Override
public void onBackPressed() {
// If an interstitial is on screen, close it.
// if (Chartboost.onBackPressed()) {
// return;
// } else {
// super.onBackPressed();
// }
if (Chartboost.onBackPressed()) {
return;
} else {
super.onBackPressed();
}
}
// Low Memory Unity

25
BFVersions/android/google_common/launcher/build.gradle Normal file → Executable file
View File

@ -1,9 +1,15 @@
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN
apply plugin: 'com.android.application'
apply plugin: 'applovin-quality-service'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
applovin {
// NOTE: DO NOT CHANGE - this is NOT your AppLovin MAX SDK key - this is a derived key.
apiKey 'c88aa3b89fe7f00054cac82dfdd1621aa02d691dd1d08b9da536e61cccf411fc362937666323b3df646456'
}
dependencies {
implementation project(':unityLibrary')
@ -18,6 +24,16 @@ android {
targetCompatibility JavaVersion.VERSION_11
}
signingConfigs {
release {
storePassword="REPLACE_PASSWORD"
keyPassword="REPLACE_PASSWORD"
keyAlias="juzu"
storeFile=file("../../keystore/dz_keystore.jks")
}
}
defaultConfig {
minSdkVersion 24
targetSdkVersion 34
@ -31,7 +47,7 @@ android {
}
aaptOptions {
noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ')
noCompress = ['.unity3d', '.ress', '.resource', '.obb', '.bundle', '.unityexp'] + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~"
}
@ -43,19 +59,22 @@ android {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt')
signingConfig signingConfigs.debug
signingConfig signingConfigs.release
jniDebuggable true
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt')
signingConfig signingConfigs.debug
signingConfig signingConfigs.release
}
}
packagingOptions {
doNotStrip '*/armeabi-v7a/*.so'
doNotStrip '*/arm64-v8a/*.so'
jniLibs {
useLegacyPackaging = true
}
}
bundle {

View File

@ -1,60 +1,26 @@
{
"project_info": {
"project_number": "1008416471093",
"project_id": "knights-combo",
"storage_bucket": "knights-combo.appspot.com"
"project_number": "22951947163",
"project_id": "pull-pull-pull-heroes",
"storage_bucket": "pull-pull-pull-heroes.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1008416471093:android:121c54160b7045e499d97c",
"mobilesdk_app_id": "1:22951947163:android:378ebaf483bc3fb9304f3e",
"android_client_info": {
"package_name": "com.combo.heroes.puzzle.rpg"
"package_name": "com.fortune.td.game.global"
}
},
"oauth_client": [
{
"client_id": "1008416471093-e1a8gso0q6mpangmi7lltjilfmqeqp6u.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.combo.heroes.puzzle.rpg",
"certificate_hash": "3d9f0e5ebcb906418204e1a41cd40968a36c71cc"
}
},
{
"client_id": "1008416471093-eolgs4t98pog1q3oltg4vh726vpggqbd.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.combo.heroes.puzzle.rpg",
"certificate_hash": "dd7bea95dbb468b776ad9e979535689371113698"
}
},
{
"client_id": "1008416471093-e47s7u8a7v31ulr2f7e9j1mdm9llepum.apps.googleusercontent.com",
"client_type": 3
}
],
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDtkUzjjkNiAZszdepIDIYss0ioNNbjncA"
"current_key": "AIzaSyAmIdtI6ZwxXfCVILmtZEr9y4CP_e9JpFE"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "1008416471093-e47s7u8a7v31ulr2f7e9j1mdm9llepum.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "1008416471093-78jluae6d1tdl8l4qkul3hut7lckd1kc.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.combo.heroes.puzzle.rpg",
"app_store_id": "6450101181"
}
}
]
"other_platform_oauth_client": []
}
}
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 B

After

Width:  |  Height:  |  Size: 710 B

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Combo de Jinetes</string>
<string name="app_name">Pull Pull Pull Heroes</string>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Ksatria Kombo</string>
<string name="app_name">Pull Pull Pull Heroes</string>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ナイト戦線</string>
<string name="app_name">プルせよ!ヒーローズ</string>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">워리어 콤보</string>
<string name="app_name">픽미픽미 영웅</string>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Cavaleiros do Combo</string>
<string name="app_name">Pull Pull Pull Heroes</string>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">อัศวินคอมโบ</string>
<string name="app_name">Pull Pull Pull Heroes</string>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Kỵ Sĩ Liên Hoàn</string>
<string name="app_name">Phiêu Lưu Sinh Tồn - VTC Game</string>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">賽賽軍團</string>
<string name="app_name">抽抽抽英雄</string>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">赛赛军团</string>
<string name="app_name">抽抽抽英雄</string>
</resources>

View File

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Knights Combo</string>
<string name="app_name">Pull Pull Pull Heroes</string>
<string name="game_view_content_description">Game view</string>
</resources>

View File

@ -27,13 +27,15 @@
// import java.util.Map;
// public class GoogleAdmobRewardedVideo {
// private final String AD_UNIT_ID = "ca-app-pub-1136292565368915/9438857158";
// // 这是b2的id不能直接用
// // private final String AD_UNIT_ID = "ca-app-pub-1136292565368915/9438857158";
// private final String AD_UNIT_ID = "";
// private final String LOG_TAG = "RewardedVideo";
// private static volatile GoogleAdmobRewardedVideo sInstance;
// private RewardedAd rewardedAd;
// private String adPlacement = "";
// private boolean isLoading = false;
// boolean isLoading;
// public static GoogleAdmobRewardedVideo getInstance()
// {
@ -64,6 +66,8 @@
// });
// loadRewardedAd(activity);
// // startGame(activity);
// }
// private void pauseGame() {
@ -73,10 +77,6 @@
// }
// private void loadRewardedAd(Activity activity) {
// if (isLoading)
// {
// return;
// }
// if (rewardedAd == null) {
// isLoading = true;
// Bundle bundle = new Bundle();
@ -94,7 +94,7 @@
// @Override
// public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
// // Handle the error.
// Log.w(LOG_TAG, "loadAdError:" + loadAdError.getMessage());
// Log.w("RewardedVideo", loadAdError.getMessage());
// rewardedAd = null;
// isLoading = false;
// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_LOADED_FAILED, "");
@ -109,7 +109,7 @@
// @Override
// public void onAdLoaded(@NonNull RewardedAd newRewardedAd) {
// Log.w(LOG_TAG, "onAdLoaded");
// Log.w("RewardedVideo", "onAdLoaded");
// rewardedAd = newRewardedAd;
// isLoading = false;
// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_LOADED, "");
@ -138,9 +138,19 @@
// }
// }
// // private void startGame(Activity activity) {
// // Hide the retry button, load the ad, and start the timer.
// // if (rewardedAd != null && !isLoading) {
// // loadRewardedAd(activity);
// // }
// // createTimer(COUNTER_TIME);
// // gamePaused = false;
// // gameOver = false;
// // }
// private void showRewardedVideo(Activity activity) {
// if (rewardedAd == null) {
// Log.w(LOG_TAG, "The rewarded ad wasn't ready yet.");
// Log.w("TAG", "The rewarded ad wasn't ready yet.");
// return;
// }
@ -243,11 +253,11 @@
// @Override
// public void onUserEarnedReward(@NonNull RewardItem rewardItem) {
// // Handle the reward.
// Log.w(LOG_TAG, "The user earned the reward.");
// // int rewardAmount = rewardItem.getAmount();
// // String rewardType = rewardItem.getType();
// // Log.w(LOG_TAG, "onUserEarnedReward rewardAmount = " + rewardAmount);
// // Log.w(LOG_TAG, "onUserEarnedReward rewardType = " + rewardType);
// Log.w("TAG", "The user earned the reward.");
// int rewardAmount = rewardItem.getAmount();
// String rewardType = rewardItem.getType();
// Log.w(LOG_TAG, "onUserEarnedReward rewardAmount = " + rewardAmount);
// Log.w(LOG_TAG, "onUserEarnedReward rewardType = " + rewardType);
// // JSONObject json = new JSONObject();
// // json.put("status", 0);
// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_EARNED_REWARD, "");
@ -276,6 +286,7 @@
// }
// public void showFullScreenAds(Activity activity) {
// Log.w(LOG_TAG, "showFullScreenAds");
// showRewardedVideo(activity);
// }

View File

@ -40,7 +40,7 @@ public class GoogleBilling {
private Map<String, Purchase> mPurchase = new HashMap<String, Purchase>();
private List<SkuDetails> mSubList = new ArrayList<SkuDetails>();
private List<SkuDetails> mInAppList = new ArrayList<SkuDetails>();
final private static String BASE_64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq3fn02CUbrnNkF1PYXk1XHuJdJsnUMsUHXaYR83+ZPE2a3NgGHpVsrVakZK28RARQSz8E2x8qy+4bFPji1TTLq+MRY9CAcGzP4HB5eGKhRmsHABJNnfWgMiXxZ/mmW/yQawTY0zmeDX6Z/GSN3SeZo0PmlpM7ZFJpN42vYO6Fs5zgR05SAUDx3uaPwkhZ0Z2bIIhbWaVyxoMy2pDnCfCL5ym1nwdDa8tRMhZ1yWaDdY4KkJ92W0kbyMntjtL9QWFCGcRTGHVEoir4E2nc2bugaOkw0qegng00L65qWnfZ1GFgPW3uNexWsbEc2A5g//bL2/+yDVQWqiJtkdQMKyIKwIDAQAB";
final private static String BASE_64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtIC/9macCZdKcRamY6XOf+J+ncHwUzrRx18696PPH0kixqIAovJKAISUeZCH28NTBb/t4dwgnPRMjNsviH71BVDOK7ZSm2xkbhi4aQ8lB5afGso9nj0+RaaONQeHMW17sIkqd2DIjiMWlPwt65pdonwXZ4NqQJwNabXMhzHggI9fjHH9mJodqqrKkwwIQtFyDe6OYC9kv6DIHhsVVDTfwKbq5QLJEHnsJKJbhuemI0aN6qwSf8tUxLWRJN+P6g88+tfRo5mI7A+slLdcom8Yf+OSQoXGPQJ6xplrfABTB1V7JymfiZg7JHLql65SwG6zk/GFAD0VcIWTfeogvyElkQIDAQAB";
public static GoogleBilling getInstance()
{

View File

@ -24,7 +24,8 @@ public class GoogleLogin {
private static volatile GoogleLogin sInstance;
private GoogleSignInClient mGoogleSignInClient;
private static final int REQUEST_CODE_GOOGLE_SIGN_IN = 1001; /* unique request id */
private static final String server_client_token = "1008416471093-e47s7u8a7v31ulr2f7e9j1mdm9llepum.apps.googleusercontent.com";
private static final String server_client_token = "22951947163-20850c0vsdknh7ts3nai4bs6j85dj5gl.apps.googleusercontent.com";
public static GoogleLogin getInstance()
{

View File

@ -2,25 +2,51 @@ package com.juzu.dz.third;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.android.play.core.review.ReviewInfo;
import com.google.android.play.core.review.ReviewManager;
import com.google.android.play.core.review.ReviewManagerFactory;
import com.google.android.play.core.review.testing.FakeReviewManager;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.analytics.FirebaseAnalytics;
// import com.google.firebase.crashlytics.FirebaseCrashlytics;
import com.google.firebase.crashlytics.FirebaseCrashlytics;
import com.juzu.dz.message.BFMessage;
import com.unity3d.player.UnityPlayer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.json.JSONException;
import org.json.JSONObject;
import static com.google.firebase.crashlytics.internal.Logger.TAG;
import com.iabtcf.decoder.TCString;
import com.google.android.ump.ConsentDebugSettings;
import com.google.android.ump.ConsentForm.OnConsentFormDismissedListener;
import com.google.android.ump.ConsentInformation;
import com.google.android.ump.ConsentInformation.PrivacyOptionsRequirementStatus;
import com.google.android.ump.ConsentRequestParameters;
import com.google.android.ump.FormError;
import com.google.android.ump.UserMessagingPlatform;
public class GooglePlugin {
private static final String LOG_TAG = "GooglePlugin";
private static GoogleLogin mGoogleLogin = null;
@ -30,6 +56,9 @@ public class GooglePlugin {
public static FirebaseAnalytics mFirebaseAnalytics;
// private FirebaseCrashlytics mFirebaseCrashlytics;
private static ConsentInformation consentInformation;
private final AtomicBoolean isMobileAdsInitializeCalled = new AtomicBoolean(false);
public static void init(Activity activity){
_activity = activity;
mFirebaseAnalytics = FirebaseAnalytics.getInstance(_activity);
@ -62,6 +91,11 @@ public class GooglePlugin {
// }
}
public static void initGDPR()
{
initConsentInformation(_activity);
}
// 登录
public static void login()
{
@ -197,7 +231,7 @@ public class GooglePlugin {
@Override
public void onComplete(@NonNull Task<String> task) {
if (!task.isSuccessful()) {
Log.w(LOG_TAG, "Fetching FCM registration token failed", task.getException());
Log.w(TAG, "Fetching FCM registration token failed", task.getException());
return;
}
@ -253,10 +287,10 @@ public class GooglePlugin {
// log crash
public static void logCrash(String key, String stack) {
// this.mFirebaseCrashlytics = FirebaseCrashlytics.getInstance();
// if (this.mFirebaseCrashlytics == null)
// return;
// this.mFirebaseCrashlytics.setCustomKey(key, stack);
// this.mFirebaseCrashlytics = FirebaseCrashlytics.getInstance();
// if (this.mFirebaseCrashlytics == null)
// return;
// this.mFirebaseCrashlytics.setCustomKey(key, stack);
}
// set bundle to type
@ -296,4 +330,170 @@ public class GooglePlugin {
// mGoogleAdmobRewardedVideo.setAdPlacement(placement);
// }
}
private static String getSign(String algorithm) {
try {
PackageInfo info = _activity.getPackageManager().getPackageInfo(
_activity.getPackageName(), PackageManager.GET_SIGNATURES);
byte[] cert = info.signatures[0].toByteArray();
MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] publicKey = md.digest(cert);
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < publicKey.length; i++) {
String appendString = Integer.toHexString(0xFF & publicKey[i])
.toUpperCase(Locale.US);
if (appendString.length() == 1)
hexString.append("0");
hexString.append(appendString);
hexString.append(":");
}
String result = hexString.toString();
return result.substring(0, result.length() - 1);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
// 初始化GDPR
public static void initConsentInformation(Activity activity)
{
// ConsentDebugSettings debugSettings = new ConsentDebugSettings.Builder(activity)
// .setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA)
// .addTestDeviceHashedId("F760B5027ECC69D34A80D3957A9841A9") // mumu:D77B7887E3BC1B47009579C304F39BE4 vivo:F760B5027ECC69D34A80D3957A9841A9
// .build();
// ConsentRequestParameters params = new ConsentRequestParameters
// .Builder()
// .setConsentDebugSettings(debugSettings)
// .build();
ConsentRequestParameters params = new ConsentRequestParameters
.Builder()
.setTagForUnderAgeOfConsent(false)
.build();
// Requesting an update to consent information should be called on every app launch.
consentInformation = UserMessagingPlatform.getConsentInformation(activity);
consentInformation.requestConsentInfoUpdate(
activity,
params,
() -> {
UserMessagingPlatform.loadAndShowConsentFormIfRequired(
activity,
loadAndShowError -> {
if (loadAndShowError != null) {
// Consent gathering failed.
Log.w(TAG, String.format("%s: %s",
loadAndShowError.getErrorCode(),
loadAndShowError.getMessage()));
}
// Consent has been gathered.
GooglePlugin.monitorConsentString();
}
);
},
requestConsentError -> {
// Consent gathering failed.
Log.w(TAG, String.format("%s: %s",
requestConsentError.getErrorCode(),
requestConsentError.getMessage()));
});
// Check if you can initialize the Google Mobile Ads SDK in parallel
// while checking for new consent information. Consent obtained in
// the previous session can be used to request ads.
// 不管结果,直接初始化广告
// init(activity);
}
public static boolean isPrivacyOptionsRequired()
{
return consentInformation.getPrivacyOptionsRequirementStatus() == PrivacyOptionsRequirementStatus.REQUIRED;
}
public static void showPrivacyOptionsForm()
{
_activity.runOnUiThread(new Runnable() {
public void run() {
UserMessagingPlatform.showPrivacyOptionsForm(
_activity,
formError -> {
if (formError != null) {
// Handle the error.
}
}
);
}
});
}
public static void resetGDPR()
{
consentInformation.reset();
}
// GDPR IAB TCF
public static void monitorConsentString() {
if (mFirebaseAnalytics == null)
return;
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(UnityPlayer.currentActivity.getApplicationContext());
if (sharedPrefs == null)
return;
String iabKey = "IABTCF_TCString";
String tcString = sharedPrefs.getString(iabKey, "");
if (tcString == "" || tcString.isEmpty())
return;
// ad_storage
boolean adStorageAllowed = TCString.decode(tcString).getPurposesConsent().contains(1);
// Consent toggle in vendor setting (i.e. Google is not blocked from using consented data at vendor level)
boolean googleConsent = TCString.decode(tcString).getVendorConsent().contains(755);
// Legitimate interest toggle in vendor setting (i.e. Google is not blocked from their legitimate interest)
// boolean googleInterest = TCString.decode(tcString).getVendorLegitimateInterest().contains(755);
// We are checking if consent for ad_storage was given and that Google as vendor has not been excluded from using consented data
FirebaseAnalytics.ConsentStatus consentStatus = adStorageAllowed && googleConsent ? FirebaseAnalytics.ConsentStatus.GRANTED : FirebaseAnalytics.ConsentStatus.DENIED;
//
// Set consent types.
Map<FirebaseAnalytics.ConsentType, FirebaseAnalytics.ConsentStatus> consentMap = new EnumMap<>(FirebaseAnalytics.ConsentType.class);
consentMap.put(FirebaseAnalytics.ConsentType.ANALYTICS_STORAGE, consentStatus);
consentMap.put(FirebaseAnalytics.ConsentType.AD_STORAGE, consentStatus);
consentMap.put(FirebaseAnalytics.ConsentType.AD_USER_DATA, consentStatus);
consentMap.put(FirebaseAnalytics.ConsentType.AD_PERSONALIZATION, consentStatus);
mFirebaseAnalytics.setConsent(consentMap);
}
public static void requestStoreReview()
{
Activity activity = UnityPlayer.currentActivity;
// 未实例Activity
if (activity == null)
{
return;
}
ReviewManager manager = ReviewManagerFactory.create(activity);
// ReviewManager manager = new FakeReviewManager(activity); // 使用FakeReviewManager进行测试
// 实例 Review Manager 失败
if (manager == null)
{
return;
}
Task<ReviewInfo> request = manager.requestReviewFlow();
// 实例Review Request失败
if (request == null)
{
return;
}
request.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
// We can get the ReviewInfo object
ReviewInfo reviewInfo = task.getResult();
// launch review
manager.launchReviewFlow(activity, reviewInfo);
}
});
}
}

View File

@ -0,0 +1,187 @@
package com.unity.androidnotifications;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_ID;
import static com.unity.androidnotifications.UnityNotificationManager.TAG_UNITY;
import android.app.Notification;
import android.util.Log;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedTransferQueue;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class UnityNotificationBackgroundThread extends Thread {
private static abstract class Task {
// returns true if notificationIds was modified (needs to be saved)
public abstract boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications);
}
private static class ScheduleNotificationTask extends Task {
private int notificationId;
private Notification.Builder notificationBuilder;
private boolean isCustomized;
private boolean isNew;
public ScheduleNotificationTask(int id, Notification.Builder builder, boolean customized, boolean addedNew) {
notificationId = id;
notificationBuilder = builder;
isCustomized = customized;
isNew = addedNew;
}
@Override
public boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
String id = String.valueOf(notificationId);
Integer ID = Integer.valueOf(notificationId);
boolean didSchedule = false;
try {
UnityNotificationManager.mUnityNotificationManager.performNotificationScheduling(notificationId, notificationBuilder, isCustomized);
didSchedule = true;
} finally {
// if failed to schedule or replace, remove
if (!didSchedule) {
notifications.remove(notificationId);
manager.cancelPendingNotificationIntent(notificationId);
manager.deleteExpiredNotificationIntent(id);
}
}
return isNew;
}
}
private static class CancelNotificationTask extends Task {
private int notificationId;
public CancelNotificationTask(int id) {
notificationId = id;
}
@Override
public boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
manager.cancelPendingNotificationIntent(notificationId);
if (notifications.remove(notificationId) != null) {
manager.deleteExpiredNotificationIntent(String.valueOf(notificationId));
return true;
}
return false;
}
}
private static class CancelAllNotificationsTask extends Task {
@Override
public boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
if (notifications.isEmpty())
return false;
Enumeration<Integer> ids = notifications.keys();
while (ids.hasMoreElements()) {
Integer notificationId = ids.nextElement();
manager.cancelPendingNotificationIntent(notificationId);
manager.deleteExpiredNotificationIntent(String.valueOf(notificationId));
}
notifications.clear();
return true;
}
}
private static class HousekeepingTask extends Task {
UnityNotificationBackgroundThread thread;
public HousekeepingTask(UnityNotificationBackgroundThread th) {
thread = th;
}
@Override
public boolean run(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
HashSet<String> notificationIds = new HashSet<>();
Enumeration<Integer> ids = notifications.keys();
while (ids.hasMoreElements()) {
notificationIds.add(String.valueOf(ids.nextElement()));
}
thread.performHousekeeping(notificationIds);
return false;
}
}
private static final int TASKS_FOR_HOUSEKEEPING = 50;
private LinkedTransferQueue<Task> mTasks = new LinkedTransferQueue();
private ConcurrentHashMap<Integer, Notification.Builder> mScheduledNotifications;
private UnityNotificationManager mManager;
private int mTasksSinceHousekeeping = TASKS_FOR_HOUSEKEEPING; // we want hoursekeeping at the start
public UnityNotificationBackgroundThread(UnityNotificationManager manager, ConcurrentHashMap<Integer, Notification.Builder> scheduledNotifications) {
mManager = manager;
mScheduledNotifications = scheduledNotifications;
// rescheduling after reboot may have loaded, otherwise load here
if (mScheduledNotifications.size() == 0)
loadNotifications();
}
public void enqueueNotification(int id, Notification.Builder notificationBuilder, boolean customized, boolean addedNew) {
mTasks.add(new UnityNotificationBackgroundThread.ScheduleNotificationTask(id, notificationBuilder, customized, addedNew));
}
public void enqueueCancelNotification(int id) {
mTasks.add(new CancelNotificationTask(id));
}
public void enqueueCancelAllNotifications() {
mTasks.add(new CancelAllNotificationsTask());
}
private void enqueueHousekeeping() {
mTasks.add(new HousekeepingTask(this));
}
@Override
public void run() {
boolean haveChanges = false;
while (true) {
try {
Task task = mTasks.take();
haveChanges |= executeTask(mManager, task, mScheduledNotifications);
if (!(task instanceof HousekeepingTask))
++mTasksSinceHousekeeping;
if (mTasks.size() == 0 && haveChanges) {
haveChanges = false;
enqueueHousekeeping();
}
} catch (InterruptedException e) {
if (mTasks.isEmpty())
break;
}
}
}
private boolean executeTask(UnityNotificationManager manager, Task task, ConcurrentHashMap<Integer, Notification.Builder> notifications) {
try {
return task.run(manager, notifications);
} catch (Exception e) {
Log.e(TAG_UNITY, "Exception executing notification task", e);
return false;
}
}
private void performHousekeeping(Set<String> notificationIds) {
// don't do housekeeping if last task we did was housekeeping (other=1)
boolean performHousekeeping = mTasksSinceHousekeeping >= TASKS_FOR_HOUSEKEEPING;
mTasksSinceHousekeeping = 0;
if (performHousekeeping)
mManager.performNotificationHousekeeping(notificationIds);
mManager.saveScheduledNotificationIDs(notificationIds);
}
private void loadNotifications() {
List<Notification.Builder> notifications = mManager.loadSavedNotifications();
for (Notification.Builder builder : notifications) {
int id = builder.getExtras().getInt(KEY_ID, -1);
mScheduledNotifications.put(id, builder);
}
}
}

View File

@ -0,0 +1,67 @@
package com.unity.androidnotifications;
import android.app.Notification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_FIRE_TIME;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_ID;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_REPEAT_INTERVAL;
import static com.unity.androidnotifications.UnityNotificationManager.TAG_UNITY;
public class UnityNotificationRestartOnBootReceiver extends BroadcastReceiver {
private static final long EXPIRATION_TRESHOLD = 600000; // 10 minutes
@Override
public void onReceive(Context context, Intent received_intent) {
Log.d(TAG_UNITY, "Rescheduling notifications after restart");
if (Intent.ACTION_BOOT_COMPLETED.equals(received_intent.getAction())) {
AsyncTask.execute(() -> { rescheduleSavedNotifications(context); });
}
}
private static void rescheduleSavedNotifications(Context context) {
UnityNotificationManager manager = UnityNotificationManager.getNotificationManagerImpl(context);
List<Notification.Builder> saved_notifications = manager.loadSavedNotifications();
Date currentDate = Calendar.getInstance().getTime();
for (Notification.Builder notificationBuilder : saved_notifications) {
rescheduleNotification(manager, currentDate, notificationBuilder);
}
}
private static boolean rescheduleNotification(UnityNotificationManager manager, Date currentDate, Notification.Builder notificationBuilder) {
try {
Bundle extras = notificationBuilder.getExtras();
long repeatInterval = extras.getLong(KEY_REPEAT_INTERVAL, 0L);
long fireTime = extras.getLong(KEY_FIRE_TIME, 0L);
Date fireTimeDate = new Date(fireTime);
boolean isRepeatable = repeatInterval > 0;
if (fireTimeDate.after(currentDate) || isRepeatable) {
manager.scheduleAlarmWithNotification(notificationBuilder);
return true;
} else if (currentDate.getTime() - fireTime < EXPIRATION_TRESHOLD) {
// notification is in the past, but not by much, send now
int id = extras.getInt(KEY_ID);
manager.notify(id, notificationBuilder);
return true;
} else {
Log.d(TAG_UNITY, "Notification expired, not rescheduling, ID: " + extras.getInt(KEY_ID, -1));
return false;
}
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to reschedule notification", e);
return false;
}
}
}

View File

@ -0,0 +1,654 @@
package com.unity.androidnotifications;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import android.app.Notification;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Base64;
import android.util.Log;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_CHANNEL_ID;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_FIRE_TIME;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_ID;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_INTENT_DATA;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_LARGE_ICON;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_NOTIFICATION;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_REPEAT_INTERVAL;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_SMALL_ICON;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_SHOW_IN_FOREGROUND;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_LARGE_ICON;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_PICTURE;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_CONTENT_TITLE;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_SUMMARY_TEXT;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_CONTENT_DESCRIPTION;
import static com.unity.androidnotifications.UnityNotificationManager.KEY_BIG_SHOW_WHEN_COLLAPSED;
import static com.unity.androidnotifications.UnityNotificationManager.TAG_UNITY;
class UnityNotificationUtilities {
/*
We serialize notifications and save them to shared prefs, so that if app is killed, we can recreate them.
The serialized BLOB starts with a four byte magic number descibing serialization type, followed by an integer version.
IMPORTANT: IF YOU DO A CHANGE THAT AFFECTS THE LAYOUT, BUMP THE VERSION, AND ENSURE OLD VERSION STILL DESERIALIZES. ADD TEST.
In real life app can get updated having old serialized notifications present, so we should be able to deserialize them.
*/
// magic stands for "Unity Mobile Notifications Notification"
static final byte[] UNITY_MAGIC_NUMBER = new byte[] { 'U', 'M', 'N', 'N'};
private static final byte[] UNITY_MAGIC_NUMBER_PARCELLED = new byte[] { 'U', 'M', 'N', 'P'};
private static final int NOTIFICATION_SERIALIZATION_VERSION = 3;
private static final int INTENT_SERIALIZATION_VERSION = 0;
static final String SAVED_NOTIFICATION_PRIMARY_KEY = "data";
static final String SAVED_NOTIFICATION_FALLBACK_KEY = "fallback.data";
protected static int findResourceIdInContextByName(Context context, String name) {
if (name == null)
return 0;
try {
Resources res = context.getResources();
if (res != null) {
int id = res.getIdentifier(name, "mipmap", context.getPackageName());
if (id == 0)
return res.getIdentifier(name, "drawable", context.getPackageName());
else
return id;
}
return 0;
} catch (Resources.NotFoundException e) {
return 0;
}
}
/* Originally we used to serialize a bundle with predefined list of values.
After we exposed entire Notification.Builder to users, this is not sufficient anymore.
Unfortunately, while Notification itself is Parcelable and can be marshalled to bytes,
it's contents are not guaranteed to be (Binder objects).
Hence what we try to do here is:
- serialize as is if notification is possibly customized by user
- otherwise serialize our stuff, since there is nothing more
*/
protected static void serializeNotification(SharedPreferences prefs, Notification notification, boolean serializeParcel) {
try {
String serialized;
ByteArrayOutputStream data = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(data);
if (serializeParcel) {
Intent intent = new Intent();
intent.putExtra(KEY_NOTIFICATION, notification);
if (serializeNotificationParcel(intent, out)) {
out.close();
byte[] bytes = data.toByteArray();
serialized = Base64.encodeToString(bytes, 0, bytes.length, 0);
} else {
return; // failed
}
}
else {
if (serializeNotificationCustom(notification, out)) {
out.flush();
byte[] bytes = data.toByteArray();
serialized = Base64.encodeToString(bytes, 0, bytes.length, 0);
} else {
return; // failed
}
}
SharedPreferences.Editor editor = prefs.edit().clear();
editor.putString(SAVED_NOTIFICATION_PRIMARY_KEY, serialized);
editor.apply();
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to serialize notification", e);
}
}
static boolean serializeNotificationParcel(Intent intent, DataOutputStream out) {
try {
byte[] bytes = serializeParcelable(intent);
if (bytes == null || bytes.length == 0)
return false;
out.write(UNITY_MAGIC_NUMBER_PARCELLED);
out.writeInt(INTENT_SERIALIZATION_VERSION);
out.writeInt(bytes.length);
out.write(bytes);
return true;
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to serialize notification as Parcel", e);
} catch (OutOfMemoryError e) {
Log.e(TAG_UNITY, "Failed to serialize notification as Parcel", e);
}
return false;
}
private static boolean serializeNotificationCustom(Notification notification, DataOutputStream out) {
try {
out.write(UNITY_MAGIC_NUMBER);
out.writeInt(NOTIFICATION_SERIALIZATION_VERSION);
// serialize extras
boolean showWhen = notification.extras.getBoolean(Notification.EXTRA_SHOW_WHEN, false);
out.writeInt(notification.extras.getInt(KEY_ID));
serializeString(out, notification.extras.getString(Notification.EXTRA_TITLE));
serializeString(out, notification.extras.getString(Notification.EXTRA_TEXT));
serializeString(out, notification.extras.getString(KEY_SMALL_ICON));
serializeString(out, notification.extras.getString(KEY_LARGE_ICON));
out.writeLong(notification.extras.getLong(KEY_FIRE_TIME, -1));
out.writeLong(notification.extras.getLong(KEY_REPEAT_INTERVAL, -1));
serializeString(out, Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ? null : notification.extras.getString(Notification.EXTRA_BIG_TEXT));
out.writeBoolean(notification.extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER, false));
out.writeBoolean(showWhen);
serializeString(out, notification.extras.getString(KEY_INTENT_DATA));
out.writeBoolean(notification.extras.getBoolean(KEY_SHOW_IN_FOREGROUND, true));
String bigPicture = notification.extras.getString(KEY_BIG_PICTURE);
serializeString(out, bigPicture);
if (bigPicture != null && bigPicture.length() > 0) {
// the following only need to be put in if big picture is there
serializeString(out, notification.extras.getString(KEY_BIG_LARGE_ICON));
serializeString(out, notification.extras.getString(KEY_BIG_CONTENT_TITLE));
serializeString(out, notification.extras.getString(KEY_BIG_CONTENT_DESCRIPTION));
serializeString(out, notification.extras.getString(KEY_BIG_SUMMARY_TEXT));
out.writeBoolean(notification.extras.getBoolean(KEY_BIG_SHOW_WHEN_COLLAPSED, false));
}
serializeString(out, Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? null : notification.getChannelId());
Integer color = UnityNotificationManager.getNotificationColor(notification);
out.writeBoolean(color != null);
if (color != null)
out.writeInt(color);
out.writeInt(notification.number);
out.writeBoolean(0 != (notification.flags & Notification.FLAG_AUTO_CANCEL));
serializeString(out, notification.getGroup());
out.writeBoolean(0 != (notification.flags & Notification.FLAG_GROUP_SUMMARY));
out.writeInt(UnityNotificationManager.getNotificationGroupAlertBehavior(notification));
serializeString(out, notification.getSortKey());
if (showWhen)
out.writeLong(notification.when);
return true;
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to serialize notification", e);
return false;
}
}
static void serializeString(DataOutputStream out, String s) throws IOException {
if (s == null || s.length() == 0)
out.writeInt(0);
else {
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
out.writeInt(bytes.length);
out.write(bytes);
}
}
static byte[] serializeParcelable(Parcelable obj) {
try {
Parcel p = Parcel.obtain();
Bundle b = new Bundle();
b.putParcelable("obj", obj);
p.writeParcelable(b, 0);
byte[] result = p.marshall();
p.recycle();
return result;
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to serialize Parcelable", e);
} catch (OutOfMemoryError e) {
Log.e(TAG_UNITY, "Failed to serialize Parcelable", e);
}
return null;
}
protected static Object deserializeNotification(Context context, SharedPreferences prefs) {
String serializedIntentData = prefs.getString(SAVED_NOTIFICATION_PRIMARY_KEY, "");
if (null == serializedIntentData || serializedIntentData.length() <= 0)
return null;
byte[] bytes = Base64.decode(serializedIntentData, 0);
Object notification = deserializeNotification(context, bytes);
if (notification != null)
return notification;
serializedIntentData = prefs.getString(SAVED_NOTIFICATION_FALLBACK_KEY, "");
if (null == serializedIntentData || serializedIntentData.length() <= 0)
return null;
bytes = Base64.decode(serializedIntentData, 0);
return deserializeNotification(context, bytes);
}
/* See serialization method above for explaination of fallbacks.
This one matches it with one additional fallback: support for "old" bundle serialization.
*/
private static Object deserializeNotification(Context context, byte[] bytes) {
ByteArrayInputStream data = new ByteArrayInputStream(bytes);
DataInputStream in = new DataInputStream(data);
Notification notification = deserializeNotificationParcelable(in);
if (notification != null)
return notification;
data.reset();
Notification.Builder builder = deserializeNotificationCustom(context, in);
if (builder == null) {
builder = deserializedFromOldIntent(context, bytes);
}
return builder;
}
private static boolean readAndCheckMagicNumber(DataInputStream in, byte[] magic) {
try {
boolean magicNumberMatch = true;
for (int i = 0; i < magic.length; ++i) {
byte b = in.readByte();
if (b != magic[i]) {
magicNumberMatch = false;
break;
}
}
return magicNumberMatch;
} catch (Exception e) {
return false;
}
}
private static Notification deserializeNotificationParcelable(DataInputStream in) {
try {
if (!readAndCheckMagicNumber(in, UNITY_MAGIC_NUMBER_PARCELLED))
return null;
int version = in.readInt();
if (version < 0 || version > INTENT_SERIALIZATION_VERSION)
return null;
Intent intent = deserializeParcelable(in);
Notification notification = intent.getParcelableExtra(KEY_NOTIFICATION);
return notification;
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to deserialize notification intent", e);
} catch (OutOfMemoryError e) {
Log.e(TAG_UNITY, "Failed to deserialize notification intent", e);
}
return null;
}
private static Notification.Builder deserializeNotificationCustom(Context context, DataInputStream in) {
try {
if (!readAndCheckMagicNumber(in, UNITY_MAGIC_NUMBER))
return null;
int version = in.readInt();
if (version < 0 || version > NOTIFICATION_SERIALIZATION_VERSION)
return null;
// deserialize extras
int id = 0;
String title, text, smallIcon, largeIcon, bigText, intentData;
long fireTime, repeatInterval;
boolean usesStopWatch, showWhen, showInForeground = true;
Bundle extras = null;
String bigPicture = null, bigLargeIcon = null, bigContentTitle = null, bigSummary = null, bigContentDesc = null;
boolean bigShowWhenCollapsed = false;
if (version < 2) {
// no longer serialized since v2
extras = deserializeParcelable(in);
}
// before v2 it was extras or variables, since 2 always variables
if (extras == null) {
// extras serialized manually
id = in.readInt();
title = deserializeString(in);
text = deserializeString(in);
smallIcon = deserializeString(in);
largeIcon = deserializeString(in);
fireTime = in.readLong();
repeatInterval = in.readLong();
bigText = deserializeString(in);
usesStopWatch = in.readBoolean();
showWhen = in.readBoolean();
intentData = deserializeString(in);
if (version > 0)
showInForeground = in.readBoolean();
if (version >= 3) {
bigPicture = deserializeString(in);
if (bigPicture != null && bigPicture.length() > 0) {
// the following only need to be put in if big picture is there
bigLargeIcon = deserializeString(in);
bigContentTitle = deserializeString(in);
bigContentDesc = deserializeString(in);
bigSummary = deserializeString(in);
bigShowWhenCollapsed = in.readBoolean();
}
}
} else {
title = extras.getString(Notification.EXTRA_TITLE);
text = extras.getString(Notification.EXTRA_TEXT);
smallIcon = extras.getString(KEY_SMALL_ICON);
largeIcon = extras.getString(KEY_LARGE_ICON);
fireTime = extras.getLong(KEY_FIRE_TIME, -1);
repeatInterval = extras.getLong(KEY_REPEAT_INTERVAL, -1);
bigText = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ? null : extras.getString(Notification.EXTRA_BIG_TEXT);
usesStopWatch = extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER, false);
showWhen = extras.getBoolean(Notification.EXTRA_SHOW_WHEN, false);
intentData = extras.getString(KEY_INTENT_DATA);
}
String channelId = deserializeString(in);
boolean haveColor = in.readBoolean();
int color = 0;
if (haveColor)
color = in.readInt();
int number = in.readInt();
boolean shouldAutoCancel = in.readBoolean();
String group = deserializeString(in);
boolean groupSummary = in.readBoolean();
int groupAlertBehavior = in.readInt();
String sortKey = deserializeString(in);
long when = showWhen ? in.readLong() : 0;
UnityNotificationManager manager = UnityNotificationManager.getNotificationManagerImpl(context);
Notification.Builder builder = manager.createNotificationBuilder(channelId);
if (extras != null)
builder.setExtras(extras);
else {
builder.getExtras().putInt(KEY_ID, id);
UnityNotificationManager.setNotificationIcon(builder, KEY_SMALL_ICON, smallIcon);
UnityNotificationManager.setNotificationIcon(builder, KEY_LARGE_ICON, largeIcon);
if (fireTime != -1)
builder.getExtras().putLong(KEY_FIRE_TIME, fireTime);
if (repeatInterval != -1)
builder.getExtras().putLong(KEY_REPEAT_INTERVAL, repeatInterval);
if (intentData != null)
builder.getExtras().putString(KEY_INTENT_DATA, intentData);
builder.getExtras().putBoolean(KEY_SHOW_IN_FOREGROUND, showInForeground);
}
if (title != null)
builder.setContentTitle(title);
if (text != null)
builder.setContentText(text);
if (bigText != null)
builder.setStyle(new Notification.BigTextStyle().bigText(bigText));
else if (bigPicture != null)
manager.setupBigPictureStyle(builder, bigLargeIcon, bigPicture, bigContentTitle, bigContentDesc, bigSummary, bigShowWhenCollapsed);
if (haveColor)
UnityNotificationManager.setNotificationColor(builder, color);
if (number >= 0)
builder.setNumber(number);
builder.setAutoCancel(shouldAutoCancel);
UnityNotificationManager.setNotificationUsesChronometer(builder, usesStopWatch);
if (group != null && group.length() > 0)
builder.setGroup(group);
builder.setGroupSummary(groupSummary);
UnityNotificationManager.setNotificationGroupAlertBehavior(builder, groupAlertBehavior);
if (sortKey != null && sortKey.length() > 0)
builder.setSortKey(sortKey);
if (showWhen) {
builder.setShowWhen(true);
builder.setWhen(when);
}
return builder;
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to deserialize notification", e);
} catch (OutOfMemoryError e) {
Log.e(TAG_UNITY, "Failed to deserialize notification", e);
}
return null;
}
private static Notification.Builder deserializedFromOldIntent(Context context, byte[] bytes) {
try {
Parcel p = Parcel.obtain();
p.unmarshall(bytes, 0, bytes.length);
p.setDataPosition(0);
Bundle bundle = new Bundle();
bundle.readFromParcel(p);
int id = bundle.getInt(KEY_ID, -1);
String channelId = bundle.getString("channelID");
String textTitle = bundle.getString("textTitle");
String textContent = bundle.getString("textContent");
String smallIcon = bundle.getString("smallIconStr");
boolean autoCancel = bundle.getBoolean("autoCancel", false);
boolean usesChronometer = bundle.getBoolean("usesChronometer", false);
long fireTime = bundle.getLong(KEY_FIRE_TIME, -1);
long repeatInterval = bundle.getLong(KEY_REPEAT_INTERVAL, -1);
String largeIcon = bundle.getString("largeIconStr");
int style = bundle.getInt("style", -1);
int color = bundle.getInt("color", 0);
int number = bundle.getInt("number", 0);
String intentData = bundle.getString(KEY_INTENT_DATA);
String group = bundle.getString("group");
boolean groupSummary = bundle.getBoolean("groupSummary", false);
String sortKey = bundle.getString("sortKey");
int groupAlertBehaviour = bundle.getInt("groupAlertBehaviour", -1);
boolean showTimestamp = bundle.getBoolean("showTimestamp", false);
Notification.Builder builder = UnityNotificationManager.getNotificationManagerImpl(context).createNotificationBuilder(channelId);
builder.getExtras().putInt(KEY_ID, id);
builder.setContentTitle(textTitle);
builder.setContentText(textContent);
UnityNotificationManager.setNotificationIcon(builder, KEY_SMALL_ICON, smallIcon);
builder.setAutoCancel(autoCancel);
builder.setUsesChronometer(usesChronometer);
builder.getExtras().putLong(KEY_FIRE_TIME, fireTime);
builder.getExtras().putLong(KEY_REPEAT_INTERVAL, repeatInterval);
UnityNotificationManager.setNotificationIcon(builder, KEY_LARGE_ICON, largeIcon);
if (style == 2)
builder.setStyle(new Notification.BigTextStyle().bigText(textContent));
if (color != 0)
UnityNotificationManager.setNotificationColor(builder, color);
if (number >= 0)
builder.setNumber(number);
if (intentData != null)
builder.getExtras().putString(KEY_INTENT_DATA, intentData);
if (null != group && group.length() > 0)
builder.setGroup(group);
builder.setGroupSummary(groupSummary);
if (null != sortKey && sortKey.length() > 0)
builder.setSortKey(sortKey);
UnityNotificationManager.setNotificationGroupAlertBehavior(builder, groupAlertBehaviour);
builder.setShowWhen(showTimestamp);
return builder;
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to deserialize old style notification", e);
} catch (OutOfMemoryError e) {
Log.e(TAG_UNITY, "Failed to deserialize old style notification", e);
}
return null;
}
private static String deserializeString(DataInputStream in) throws IOException {
int length = in.readInt();
if (length <= 0)
return null;
byte[] bytes = new byte[length];
int didRead = in.read(bytes);
if (didRead != bytes.length)
throw new IOException("Insufficient amount of bytes read");
return new String(bytes, StandardCharsets.UTF_8);
}
private static <T extends Parcelable> T deserializeParcelable(DataInputStream in) throws IOException {
int length = in.readInt();
if (length <= 0)
return null;
byte[] bytes = new byte[length];
int didRead = in.read(bytes);
if (didRead != bytes.length)
throw new IOException("Insufficient amount of bytes read");
try {
Parcel p = Parcel.obtain();
p.unmarshall(bytes, 0, bytes.length);
p.setDataPosition(0);
Bundle b = p.readParcelable(UnityNotificationUtilities.class.getClassLoader());
p.recycle();
if (b != null) {
return b.getParcelable("obj");
}
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to deserialize parcelable", e);
} catch (OutOfMemoryError e) {
Log.e(TAG_UNITY, "Failed to deserialize parcelable", e);
}
return null;
}
// Returns Activity class to be opened when notification is tapped
// Search is done in this order:
// * class specified in meta-data key custom_notification_android_activity
// * the only enabled activity with name ending in either .UnityPlayerActivity or .UnityPlayerGameActivity
// * the only enabled activity in the package
protected static Class<?> getOpenAppActivity(Context context) {
try {
PackageManager pm = context.getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
if (bundle.containsKey("custom_notification_android_activity")) {
try {
return Class.forName(bundle.getString("custom_notification_android_activity"));
} catch (ClassNotFoundException e) {
Log.e(TAG_UNITY, "Specified activity class for notifications not found: " + e.getMessage());
}
}
Log.w(TAG_UNITY, "No custom_notification_android_activity found, attempting to find app activity class");
ActivityInfo[] aInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES).activities;
if (aInfo == null) {
Log.e(TAG_UNITY, "Could not get package activities");
return null;
}
String activityClassName = null;
boolean activityIsUnity = false, activityConflict = false;
for (ActivityInfo info : aInfo) {
// activity alias not supported
if (!info.enabled || info.targetActivity != null)
continue;
boolean candidateIsUnity = isUnityActivity(info.name);
if (activityClassName == null) {
activityClassName = info.name;
activityIsUnity = candidateIsUnity;
continue;
}
// two Unity activities is a hard conflict
// two non-Unity activities is a conflict unless we find a Unity activity later on
if (activityIsUnity == candidateIsUnity) {
activityConflict = true;
if (activityIsUnity && candidateIsUnity)
break;
continue;
}
if (candidateIsUnity) {
activityClassName = info.name;
activityIsUnity = candidateIsUnity;
activityConflict = false;
}
}
if (activityConflict) {
Log.e(TAG_UNITY, "Multiple choices for activity for notifications, set activity explicitly in Notification Settings");
return null;
}
if (activityClassName == null) {
Log.e(TAG_UNITY, "Activity class for notifications not found");
return null;
}
return Class.forName(activityClassName);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
Log.e(TAG_UNITY, "Failed to find activity class: " + e.getMessage());
}
return null;
}
private static boolean isUnityActivity(String name) {
return name.endsWith(".UnityPlayerActivity") || name.endsWith(".UnityPlayerGameActivity");
}
protected static Notification.Builder recoverBuilder(Context context, Notification notification) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Notification.Builder builder = Notification.Builder.recoverBuilder(context, notification);
// extras not recovered, transfer manually
builder.setExtras(notification.extras);
return builder;
}
} catch (Exception e) {
Log.e(TAG_UNITY, "Failed to recover builder for notification!", e);
} catch (OutOfMemoryError e) {
Log.e(TAG_UNITY, "Failed to recover builder for notification!", e);
}
return recoverBuilderCustom(context, notification);
}
private static Notification.Builder recoverBuilderCustom(Context context, Notification notification) {
String channelID = notification.extras.getString(KEY_CHANNEL_ID);
Notification.Builder builder = UnityNotificationManager.getNotificationManagerImpl(context).createNotificationBuilder(channelID);
UnityNotificationManager.setNotificationIcon(builder, KEY_SMALL_ICON, notification.extras.getString(KEY_SMALL_ICON));
String largeIcon = notification.extras.getString(KEY_LARGE_ICON);
if (largeIcon != null && !largeIcon.isEmpty())
UnityNotificationManager.setNotificationIcon(builder, KEY_LARGE_ICON, largeIcon);
builder.setContentTitle(notification.extras.getString(Notification.EXTRA_TITLE));
builder.setContentText(notification.extras.getString(Notification.EXTRA_TEXT));
builder.setAutoCancel(0 != (notification.flags & Notification.FLAG_AUTO_CANCEL));
if (notification.number >= 0)
builder.setNumber(notification.number);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String bigText = notification.extras.getString(Notification.EXTRA_BIG_TEXT);
if (bigText != null)
builder.setStyle(new Notification.BigTextStyle().bigText(bigText));
}
builder.setWhen(notification.when);
String group = notification.getGroup();
if (group != null && !group.isEmpty())
builder.setGroup(group);
builder.setGroupSummary(0 != (notification.flags & Notification.FLAG_GROUP_SUMMARY));
String sortKey = notification.getSortKey();
if (sortKey != null && !sortKey.isEmpty())
builder.setSortKey(sortKey);
builder.setShowWhen(notification.extras.getBoolean(Notification.EXTRA_SHOW_WHEN, false));
Integer color = UnityNotificationManager.getNotificationColor(notification);
if (color != null)
UnityNotificationManager.setNotificationColor(builder, color);
UnityNotificationManager.setNotificationUsesChronometer(builder, notification.extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER, false));
UnityNotificationManager.setNotificationGroupAlertBehavior(builder, UnityNotificationManager.getNotificationGroupAlertBehavior(notification));
builder.getExtras().putInt(KEY_ID, notification.extras.getInt(KEY_ID, 0));
builder.getExtras().putLong(KEY_REPEAT_INTERVAL, notification.extras.getLong(KEY_REPEAT_INTERVAL, 0));
builder.getExtras().putLong(KEY_FIRE_TIME, notification.extras.getLong(KEY_FIRE_TIME, 0));
String intentData = notification.extras.getString(KEY_INTENT_DATA);
if (intentData != null && !intentData.isEmpty())
builder.getExtras().putString(KEY_INTENT_DATA, intentData);
return builder;
}
}

Binary file not shown.

View File

@ -0,0 +1 @@
juzu_dz0!