From eb10c2994011146bfe11734a86f60a211b7cbf11 Mon Sep 17 00:00:00 2001 From: puxuan <413323644@qq.com> Date: Tue, 5 Aug 2025 17:29:22 +0800 Subject: [PATCH] android --- .../BuildProjectWindow/BuildProjectWindow.cs | 2 +- Assets/Scripts/Const/BFPlatform.Language.cs | 2 +- BFVersions/android/dz_dev/build.gradle | 80 +- BFVersions/android/dz_dev/gradle.properties | 10 +- .../android/dz_dev/unityLibrary/build.gradle | 216 ++-- .../unityLibrary/src/main/AndroidManifest.xml | 129 +- .../unity3d/player/UnityPlayerActivity.java | 365 +++--- .../main/res/xml/network_security_config.xml | 18 +- BFVersions/android/dz_google_abb/build.gradle | 74 +- .../android/dz_google_abb/gradle.properties | 10 +- .../install_time_pack/build.gradle | 12 +- .../dz_google_abb/launcher/build.gradle | 148 +-- .../launcher/src/main/AndroidManifest.xml | 12 +- .../src/main/res/drawable/firebase_icon.png | Bin .../dz_google_abb/unityLibrary/build.gradle | 248 ++-- .../unityLibrary/src/main/AndroidManifest.xml | 131 +- .../java/com/juzu/dz/message/BFMessage.java | 30 + .../java/com/juzu/dz/third/GoogleBilling.java | 459 +++++++ .../java/com/juzu/dz/third/GoogleLogin.java | 96 ++ .../java/com/juzu/dz/third/GooglePlugin.java | 148 +++ .../main/java/com/juzu/dz/third/Security.java | 136 ++ .../unity3d/player/UnityPlayerActivity.java | 348 ++--- .../main/res/xml/network_security_config.xml | 18 +- BFVersions/android/dz_google_apk/build.gradle | 82 +- .../android/dz_google_apk/gradle.properties | 11 +- .../gradle/wrapper/gradle-wrapper.properties | 5 + .../IronSource.androidlib/AndroidManifest.xml | 12 - .../IronSource.androidlib/build.gradle | 28 - .../libs/IronSource-bridge.jar | Bin 86692 -> 0 bytes .../IronSource.androidlib/project.properties | 2 - .../dz_google_apk/unityLibrary/build.gradle | 409 +++--- .../unityLibrary/src/main/AndroidManifest.xml | 164 +-- .../unity3d/player/UnityPlayerActivity.java | 367 +++--- .../main/res/xml/network_security_config.xml | 18 +- BFVersions/android/dz_release/build.gradle | 80 +- .../android/dz_release/gradle.properties | 10 +- .../dz_release/unityLibrary/build.gradle | 256 ++-- .../unityLibrary/src/main/AndroidManifest.xml | 105 +- .../unity3d/player/UnityPlayerActivity.java | 376 +++--- .../main/res/xml/network_security_config.xml | 18 +- .../google_common/launcher/build.gradle | 163 +-- .../launcher/google-services.json | 90 +- .../launcher/src/main/AndroidManifest.xml | 12 +- .../src/main/res/drawable/firebase_icon.png | Bin 581 -> 710 bytes .../src/main/res/values-es/strings.xml | 6 +- .../src/main/res/values-in/strings.xml | 6 +- .../src/main/res/values-ja/strings.xml | 6 +- .../src/main/res/values-ko/strings.xml | 6 +- .../src/main/res/values-pt/strings.xml | 6 +- .../src/main/res/values-th/strings.xml | 6 +- .../src/main/res/values-v21/styles.xml | 10 +- .../src/main/res/values-v28/styles.xml | 12 +- .../main/res/values-v30/freeformwindow.xml | 16 +- .../src/main/res/values-vi/strings.xml | 6 +- .../src/main/res/values-zh-rTW/strings.xml | 6 +- .../src/main/res/values-zh/strings.xml | 6 +- .../src/main/res/values/freeformwindow.xml | 16 +- .../launcher/src/main/res/values/ids.xml | 8 +- .../launcher/src/main/res/values/strings.xml | 8 +- .../launcher/src/main/res/values/styles.xml | 24 +- .../java/com/juzu/dz/message/BFMessage.java | 88 +- .../dz/third/BFFirebaseMessagingService.java | 100 +- .../dz/third/GoogleAdmobRewardedVideo.java | 591 ++++----- .../java/com/juzu/dz/third/GoogleBilling.java | 918 +++++++------- .../java/com/juzu/dz/third/GoogleLogin.java | 195 +-- .../java/com/juzu/dz/third/GooglePlugin.java | 796 +++++++----- .../main/java/com/juzu/dz/third/Security.java | 272 ++-- .../UnityNotificationBackgroundThread.java | 187 +++ .../UnityNotificationManager.java | 1124 +++++++++++++++++ ...nityNotificationRestartOnBootReceiver.java | 67 + .../UnityNotificationUtilities.java | 654 ++++++++++ BFVersions/android/keystore/dz_keystore.jks | Bin 0 -> 2395 bytes BFVersions/android/keystore/dz_keystore.txt | 1 + 73 files changed, 6533 insertions(+), 3508 deletions(-) mode change 100644 => 100755 BFVersions/android/dz_dev/build.gradle mode change 100644 => 100755 BFVersions/android/dz_dev/gradle.properties mode change 100644 => 100755 BFVersions/android/dz_dev/unityLibrary/build.gradle mode change 100644 => 100755 BFVersions/android/dz_dev/unityLibrary/src/main/AndroidManifest.xml mode change 100644 => 100755 BFVersions/android/dz_dev/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java mode change 100644 => 100755 BFVersions/android/dz_dev/unityLibrary/src/main/res/xml/network_security_config.xml mode change 100644 => 100755 BFVersions/android/dz_google_abb/build.gradle mode change 100644 => 100755 BFVersions/android/dz_google_abb/gradle.properties mode change 100644 => 100755 BFVersions/android/dz_google_abb/install_time_pack/build.gradle mode change 100644 => 100755 BFVersions/android/dz_google_abb/launcher/build.gradle mode change 100644 => 100755 BFVersions/android/dz_google_abb/launcher/src/main/AndroidManifest.xml mode change 100644 => 100755 BFVersions/android/dz_google_abb/launcher/src/main/res/drawable/firebase_icon.png mode change 100644 => 100755 BFVersions/android/dz_google_abb/unityLibrary/build.gradle mode change 100644 => 100755 BFVersions/android/dz_google_abb/unityLibrary/src/main/AndroidManifest.xml create mode 100755 BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/message/BFMessage.java create mode 100755 BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GoogleBilling.java create mode 100755 BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GoogleLogin.java create mode 100755 BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GooglePlugin.java create mode 100755 BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/Security.java mode change 100644 => 100755 BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java mode change 100644 => 100755 BFVersions/android/dz_google_abb/unityLibrary/src/main/res/xml/network_security_config.xml mode change 100644 => 100755 BFVersions/android/dz_google_apk/build.gradle mode change 100644 => 100755 BFVersions/android/dz_google_apk/gradle.properties create mode 100755 BFVersions/android/dz_google_apk/gradle/wrapper/gradle-wrapper.properties delete mode 100644 BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/AndroidManifest.xml delete mode 100644 BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/build.gradle delete mode 100644 BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/libs/IronSource-bridge.jar delete mode 100644 BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/project.properties mode change 100644 => 100755 BFVersions/android/dz_google_apk/unityLibrary/build.gradle mode change 100644 => 100755 BFVersions/android/dz_google_apk/unityLibrary/src/main/AndroidManifest.xml mode change 100644 => 100755 BFVersions/android/dz_google_apk/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java mode change 100644 => 100755 BFVersions/android/dz_google_apk/unityLibrary/src/main/res/xml/network_security_config.xml mode change 100644 => 100755 BFVersions/android/dz_release/build.gradle mode change 100644 => 100755 BFVersions/android/dz_release/gradle.properties mode change 100644 => 100755 BFVersions/android/dz_release/unityLibrary/build.gradle mode change 100644 => 100755 BFVersions/android/dz_release/unityLibrary/src/main/AndroidManifest.xml mode change 100644 => 100755 BFVersions/android/dz_release/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java mode change 100644 => 100755 BFVersions/android/dz_release/unityLibrary/src/main/res/xml/network_security_config.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/build.gradle mode change 100644 => 100755 BFVersions/android/google_common/launcher/google-services.json mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/AndroidManifest.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/drawable/firebase_icon.png mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-es/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-in/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-ja/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-ko/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-pt/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-th/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-v21/styles.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-v28/styles.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-v30/freeformwindow.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-vi/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-zh-rTW/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values-zh/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values/freeformwindow.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values/ids.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values/strings.xml mode change 100644 => 100755 BFVersions/android/google_common/launcher/src/main/res/values/styles.xml mode change 100644 => 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/message/BFMessage.java mode change 100644 => 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/BFFirebaseMessagingService.java mode change 100644 => 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleAdmobRewardedVideo.java mode change 100644 => 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleBilling.java mode change 100644 => 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleLogin.java mode change 100644 => 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GooglePlugin.java mode change 100644 => 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/Security.java create mode 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationBackgroundThread.java create mode 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationManager.java create mode 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationRestartOnBootReceiver.java create mode 100755 BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationUtilities.java create mode 100755 BFVersions/android/keystore/dz_keystore.jks create mode 100755 BFVersions/android/keystore/dz_keystore.txt diff --git a/Assets/Editor/BFBuildProjectTools/BuildProjectWindow/BuildProjectWindow.cs b/Assets/Editor/BFBuildProjectTools/BuildProjectWindow/BuildProjectWindow.cs index 8fa567654..fd09eded9 100644 --- a/Assets/Editor/BFBuildProjectTools/BuildProjectWindow/BuildProjectWindow.cs +++ b/Assets/Editor/BFBuildProjectTools/BuildProjectWindow/BuildProjectWindow.cs @@ -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() { diff --git a/Assets/Scripts/Const/BFPlatform.Language.cs b/Assets/Scripts/Const/BFPlatform.Language.cs index abc1d8bc2..e2fbba33f 100644 --- a/Assets/Scripts/Const/BFPlatform.Language.cs +++ b/Assets/Scripts/Const/BFPlatform.Language.cs @@ -42,7 +42,7 @@ namespace BF // 渠道对应的语言配置 public static Dictionary languageInfos = new Dictionary() { - {"com.c1.dev.android", new BFLanguageInfo(new List{"en", "cn", "zh", "th", "ru", "id", "vi"})}, + {"com.fortune.td.game.global", new BFLanguageInfo(new List{"en", "cn", "zh", "th", "ru", "id", "vi"})}, {"com.juzu.b6.dev.android", new BFLanguageInfo(new List{"en", "cn", "zh", "th", "ru", "id", "vi"})}, {"com.juzu.b6.dev.ios", new BFLanguageInfo(new List{"en", "cn"})}, {"com.juzu.b6.release.android", new BFLanguageInfo(new List{"en"})}, diff --git a/BFVersions/android/dz_dev/build.gradle b/BFVersions/android/dz_dev/build.gradle old mode 100644 new mode 100755 index 455b2415e..2e1ead0e0 --- a/BFVersions/android/dz_dev/build.gradle +++ b/BFVersions/android/dz_dev/build.gradle @@ -1,40 +1,40 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN - -buildscript { - repositories { - google() - jcenter() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' - // classpath 'com.google.gms:google-services:4.3.13' - // classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1' - } -} - -allprojects { - buildscript { - repositories { - google() - jcenter() - } - -// dependencies { -// classpath 'com.android.tools.build:gradle:4.0.1' -// -// } - } - - repositories { - google() - jcenter() - flatDir { - dirs "${project(':unityLibrary').projectDir}/libs" - } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + jcenter() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.google.gms:google-services:4.3.13' + // classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1' + } +} + +allprojects { + buildscript { + repositories { + google() + jcenter() + } + +// dependencies { +// classpath 'com.android.tools.build:gradle:4.0.1' +// +// } + } + + repositories { + google() + jcenter() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/BFVersions/android/dz_dev/gradle.properties b/BFVersions/android/dz_dev/gradle.properties old mode 100644 new mode 100755 index 58e8481f1..0521300d1 --- a/BFVersions/android/dz_dev/gradle.properties +++ b/BFVersions/android/dz_dev/gradle.properties @@ -1,6 +1,6 @@ -org.gradle.jvmargs=-Xmx4096M -org.gradle.parallel=true -android.enableR8=false -android.useAndroidX=true -android.enableJetifier=true +org.gradle.jvmargs=-Xmx4096M +org.gradle.parallel=true +android.enableR8=false +android.useAndroidX=true +android.enableJetifier=true unityStreamingAssets=.unity3d, .bytes, .ab \ No newline at end of file diff --git a/BFVersions/android/dz_dev/unityLibrary/build.gradle b/BFVersions/android/dz_dev/unityLibrary/build.gradle old mode 100644 new mode 100755 index 89045132b..e61b2d3a5 --- a/BFVersions/android/dz_dev/unityLibrary/build.gradle +++ b/BFVersions/android/dz_dev/unityLibrary/build.gradle @@ -1,119 +1,97 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN - -apply plugin: 'com.android.library' - -// Android Resolver Repos Start -([rootProject] + (rootProject.subprojects as List)).each { project -> - project.repositories { - def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") - maven { - 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 - } - 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 - } - mavenLocal() - jcenter() - mavenCentral() - } -} -// Android Resolver Repos End - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:multidex:1.0.3' - - implementation(name: 'screenutils-release', ext:'aar') - implementation(name: 'ThinkingSDK', ext:'aar') - implementation(name: 'UnityUtils-release', ext:'aar') - 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 - 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.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.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.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 -// Android Resolver Dependencies End -} - -android { - compileSdkVersion 30 - buildToolsVersion '30.0.2' - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - defaultConfig { - minSdkVersion 19 - targetSdkVersion 30 - ndk { - abiFilters 'armeabi-v7a' - } - versionCode 1 - versionName '0.1.0' - consumerProguardFiles 'proguard-unity.txt' - } - - lintOptions { - abortOnError false - } - - aaptOptions { - noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') - ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" - } - - packagingOptions { - doNotStrip '*/armeabi-v7a/*.so' - } -} - - +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +apply plugin: 'com.android.library' + +// Android Resolver Repos Start +([rootProject] + (rootProject.subprojects as List)).each { project -> + project.repositories { + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "https://maven.google.com" + } + maven { + 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/ISAdMobAdapterDependencies.xml:8, Assets/IronSource/Editor/ISAppLovinAdapterDependencies.xml:15, Assets/IronSource/Editor/ISFacebookAdapterDependencies.xml:8, Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:15 + } + mavenLocal() + jcenter() + mavenCentral() + } +} +// Android Resolver Repos End + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:multidex:1.0.3' + + implementation(name: 'screenutils-release', ext:'aar') + implementation(name: 'ThinkingSDK', ext:'aar') + implementation(name: 'UnityUtils-release', ext:'aar') + 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" +// 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.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.facebook.android:audience-network-sdk:6.12.0' // Assets/IronSource/Editor/ISFacebookAdapterDependencies.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: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.unity3d.ads:unity-ads:4.4.1' // Assets/IronSource/Editor/ISUnityAdsAdapterDependencies.xml:15 +// Android Resolver Dependencies End +} + +android { + compileSdkVersion 30 + buildToolsVersion '30.0.2' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 30 + ndk { + abiFilters 'armeabi-v7a' + } + versionCode 1 + versionName '0.1.0' + consumerProguardFiles 'proguard-unity.txt' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') + ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" + } + + packagingOptions { + doNotStrip '*/armeabi-v7a/*.so' + } +} + + diff --git a/BFVersions/android/dz_dev/unityLibrary/src/main/AndroidManifest.xml b/BFVersions/android/dz_dev/unityLibrary/src/main/AndroidManifest.xml old mode 100644 new mode 100755 index 95adb4109..6bee77a4b --- a/BFVersions/android/dz_dev/unityLibrary/src/main/AndroidManifest.xml +++ b/BFVersions/android/dz_dev/unityLibrary/src/main/AndroidManifest.xml @@ -1,66 +1,65 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - REPLACE_BUILD_ID - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + REPLACE_BUILD_ID + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BFVersions/android/dz_dev/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java b/BFVersions/android/dz_dev/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java old mode 100644 new mode 100755 index ca3396058..734058448 --- a/BFVersions/android/dz_dev/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java +++ b/BFVersions/android/dz_dev/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java @@ -1,188 +1,177 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN -package com.unity3d.player; - -import android.app.Activity; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.MotionEvent; -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; - -public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents -{ - protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code - - // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player - // The command line arguments are passed as a string, separated by spaces - // UnityPlayerActivity calls this from 'onCreate' - // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan - // See https://docs.unity3d.com/Manual/CommandLineArguments.html - // @param cmdLine the current command line arguments, may be null - // @return the modified command line string or null - protected String updateUnityCommandLineArguments(String cmdLine) - { - return cmdLine; - } - - // Setup activity layout - @Override protected void onCreate(Bundle savedInstanceState) - { - requestWindowFeature(Window.FEATURE_NO_TITLE); - super.onCreate(savedInstanceState); - - String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity")); - getIntent().putExtra("unity", cmdLine); - - mUnityPlayer = new UnityPlayer(this, this); - setContentView(mUnityPlayer); - mUnityPlayer.requestFocus(); - - GooglePlugin.init(this); - } - - // When Unity player unloaded move task to background - @Override public void onUnityPlayerUnloaded() { - moveTaskToBack(true); - } - - // Callback before Unity player process is killed - @Override public void onUnityPlayerQuitted() { - } - - @Override protected void onNewIntent(Intent intent) - { - // To support deep linking, we need to make sure that the client can get access to - // the last sent intent. The clients access this through a JNI api that allows them - // to get the intent set on launch. To update that after launch we have to manually - // replace the intent with the one caught here. - setIntent(intent); - mUnityPlayer.newIntent(intent); - } - - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - GooglePlugin.onActivityResult(requestCode, resultCode, data); - } - - // Quit Unity - @Override protected void onDestroy () - { - GooglePlugin.onDestroy(); - mUnityPlayer.destroy(); - super.onDestroy(); - } - - // If the activity is in multi window mode or resizing the activity is allowed we will use - // onStart/onStop (the visibility callbacks) to determine when to pause/resume. - // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve - // existing behavior. - @Override protected void onStop() - { - super.onStop(); - - if (!MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.pause(); - } - - @Override protected void onStart() - { - super.onStart(); - - if (!MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.resume(); - } - - // Pause Unity - @Override protected void onPause() - { - super.onPause(); - - if (MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.pause(); - IronSource.onPause(this); - } - - // Resume Unity - @Override protected void onResume() - { - super.onResume(); - - if (MultiWindowSupport.getAllowResizableWindow(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(); - // } - } - - // Low Memory Unity - @Override public void onLowMemory() - { - super.onLowMemory(); - mUnityPlayer.lowMemory(); - } - - // Trim Memory Unity - @Override public void onTrimMemory(int level) - { - super.onTrimMemory(level); - if (level == TRIM_MEMORY_RUNNING_CRITICAL) - { - mUnityPlayer.lowMemory(); - } - } - - // This ensures the layout will be correct. - @Override public void onConfigurationChanged(Configuration newConfig) - { - super.onConfigurationChanged(newConfig); - mUnityPlayer.configurationChanged(newConfig); - } - - // Notify Unity of the focus change. - @Override public void onWindowFocusChanged(boolean hasFocus) - { - super.onWindowFocusChanged(hasFocus); - mUnityPlayer.windowFocusChanged(hasFocus); - } - - // For some reason the multiple keyevent type is not supported by the ndk. - // Force event injection by overriding dispatchKeyEvent(). - @Override public boolean dispatchKeyEvent(KeyEvent event) - { - if (event.getAction() == KeyEvent.ACTION_MULTIPLE) - return mUnityPlayer.injectEvent(event); - return super.dispatchKeyEvent(event); - } - - // Pass any events not handled by (unfocused) views straight to UnityPlayer - @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } - @Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } - /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } -} +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN +package com.unity3d.player; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.os.Process; + +// import com.chartboost.sdk.Chartboost; +import com.juzu.dz.third.GooglePlugin; +// import com.ironsource.mediationsdk.IronSource; + +public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents +{ + protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code + + // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player + // The command line arguments are passed as a string, separated by spaces + // UnityPlayerActivity calls this from 'onCreate' + // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan + // See https://docs.unity3d.com/Manual/CommandLineArguments.html + // @param cmdLine the current command line arguments, may be null + // @return the modified command line string or null + protected String updateUnityCommandLineArguments(String cmdLine) + { + return cmdLine; + } + + // Setup activity layout + @Override protected void onCreate(Bundle savedInstanceState) + { + requestWindowFeature(Window.FEATURE_NO_TITLE); + super.onCreate(savedInstanceState); + + String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity")); + getIntent().putExtra("unity", cmdLine); + + mUnityPlayer = new UnityPlayer(this, this); + setContentView(mUnityPlayer); + mUnityPlayer.requestFocus(); + + GooglePlugin.init(this); + } + + // When Unity player unloaded move task to background + @Override public void onUnityPlayerUnloaded() { + moveTaskToBack(true); + } + + // Callback before Unity player process is killed + @Override public void onUnityPlayerQuitted() { + } + + @Override protected void onNewIntent(Intent intent) + { + // To support deep linking, we need to make sure that the client can get access to + // the last sent intent. The clients access this through a JNI api that allows them + // to get the intent set on launch. To update that after launch we have to manually + // replace the intent with the one caught here. + setIntent(intent); + mUnityPlayer.newIntent(intent); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + GooglePlugin.onActivityResult(requestCode, resultCode, data); + } + + // Quit Unity + @Override protected void onDestroy () + { + GooglePlugin.onDestroy(); + mUnityPlayer.destroy(); + super.onDestroy(); + } + + // If the activity is in multi window mode or resizing the activity is allowed we will use + // onStart/onStop (the visibility callbacks) to determine when to pause/resume. + // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve + // existing behavior. + @Override protected void onStop() + { + super.onStop(); + + if (!MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.pause(); + } + + @Override protected void onStart() + { + super.onStart(); + + if (!MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.resume(); + } + + // Pause Unity + @Override protected void onPause() + { + super.onPause(); + + if (MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.pause(); + // IronSource.onPause(this); + } + + // Resume Unity + @Override protected void onResume() + { + super.onResume(); + + if (MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.resume(); + // IronSource.onResume(this); + } + + // Low Memory Unity + @Override public void onLowMemory() + { + super.onLowMemory(); + mUnityPlayer.lowMemory(); + } + + // Trim Memory Unity + @Override public void onTrimMemory(int level) + { + super.onTrimMemory(level); + if (level == TRIM_MEMORY_RUNNING_CRITICAL) + { + mUnityPlayer.lowMemory(); + } + } + + // This ensures the layout will be correct. + @Override public void onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + mUnityPlayer.configurationChanged(newConfig); + } + + // Notify Unity of the focus change. + @Override public void onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + mUnityPlayer.windowFocusChanged(hasFocus); + } + + // For some reason the multiple keyevent type is not supported by the ndk. + // Force event injection by overriding dispatchKeyEvent(). + @Override public boolean dispatchKeyEvent(KeyEvent event) + { + if (event.getAction() == KeyEvent.ACTION_MULTIPLE) + return mUnityPlayer.injectEvent(event); + return super.dispatchKeyEvent(event); + } + + // Pass any events not handled by (unfocused) views straight to UnityPlayer + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } + @Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } + /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } +} diff --git a/BFVersions/android/dz_dev/unityLibrary/src/main/res/xml/network_security_config.xml b/BFVersions/android/dz_dev/unityLibrary/src/main/res/xml/network_security_config.xml old mode 100644 new mode 100755 index 3f9b9c92f..0edfdf604 --- a/BFVersions/android/dz_dev/unityLibrary/src/main/res/xml/network_security_config.xml +++ b/BFVersions/android/dz_dev/unityLibrary/src/main/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/BFVersions/android/dz_google_abb/build.gradle b/BFVersions/android/dz_google_abb/build.gradle old mode 100644 new mode 100755 index 245f232d9..99cfaa109 --- a/BFVersions/android/dz_google_abb/build.gradle +++ b/BFVersions/android/dz_google_abb/build.gradle @@ -1,37 +1,37 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN - -buildscript { - repositories { - google() - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' - } -} - -allprojects { - buildscript { - repositories { - google() - jcenter() - } - -// dependencies { -// classpath 'com.android.tools.build:gradle:4.0.1' -// -// } - } - - repositories { - google() - jcenter() - flatDir { - dirs "${project(':unityLibrary').projectDir}/libs" - } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.0.1' + } +} + +allprojects { + buildscript { + repositories { + google() + jcenter() + } + +// dependencies { +// classpath 'com.android.tools.build:gradle:4.0.1' +// +// } + } + + repositories { + google() + jcenter() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/BFVersions/android/dz_google_abb/gradle.properties b/BFVersions/android/dz_google_abb/gradle.properties old mode 100644 new mode 100755 index 58e8481f1..0521300d1 --- a/BFVersions/android/dz_google_abb/gradle.properties +++ b/BFVersions/android/dz_google_abb/gradle.properties @@ -1,6 +1,6 @@ -org.gradle.jvmargs=-Xmx4096M -org.gradle.parallel=true -android.enableR8=false -android.useAndroidX=true -android.enableJetifier=true +org.gradle.jvmargs=-Xmx4096M +org.gradle.parallel=true +android.enableR8=false +android.useAndroidX=true +android.enableJetifier=true unityStreamingAssets=.unity3d, .bytes, .ab \ No newline at end of file diff --git a/BFVersions/android/dz_google_abb/install_time_pack/build.gradle b/BFVersions/android/dz_google_abb/install_time_pack/build.gradle old mode 100644 new mode 100755 index 1c9945271..50e09a19c --- a/BFVersions/android/dz_google_abb/install_time_pack/build.gradle +++ b/BFVersions/android/dz_google_abb/install_time_pack/build.gradle @@ -1,7 +1,7 @@ -apply plugin: 'com.android.asset-pack' -assetPack{ - packName = "install_time_pack" - dynamicDelivery { - deliveryType = "install-time" - } +apply plugin: 'com.android.asset-pack' +assetPack{ + packName = "install_time_pack" + dynamicDelivery { + deliveryType = "install-time" + } } \ No newline at end of file diff --git a/BFVersions/android/dz_google_abb/launcher/build.gradle b/BFVersions/android/dz_google_abb/launcher/build.gradle old mode 100644 new mode 100755 index 3bf656b34..b282ea96e --- a/BFVersions/android/dz_google_abb/launcher/build.gradle +++ b/BFVersions/android/dz_google_abb/launcher/build.gradle @@ -1,74 +1,74 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN - -apply plugin: 'com.android.application' - -dependencies { - implementation platform('com.google.firebase:firebase-bom:29.2.1') - implementation project(':unityLibrary') - implementation 'com.google.android.play:core:1.10.0' - } - -android { - assetPacks = [":install_time_pack"] - - compileSdkVersion 30 - buildToolsVersion '30.0.2' - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - defaultConfig { - minSdkVersion 19 - targetSdkVersion 30 - applicationId 'com.idlelegend.arenaclash.battlefire' - ndk { - abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' - } - versionCode 1 - versionName '0.1.0' - } - - aaptOptions { - noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') - ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" - } - - lintOptions { - abortOnError false - } - - buildTypes { - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt') - signingConfig signingConfigs.debug - jniDebuggable true - } - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt') - signingConfig signingConfigs.debug - } - } - - packagingOptions { - doNotStrip '*/armeabi-v7a/*.so' - doNotStrip '*/arm64-v8a/*.so' - doNotStrip '*/x86/*.so' - doNotStrip '*/x86_64/*.so' - } - - bundle { - language { - enableSplit = false - } - density { - enableSplit = false - } - abi { - enableSplit = true - } - } -} +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +apply plugin: 'com.android.application' + +dependencies { + implementation platform('com.google.firebase:firebase-bom:29.2.1') + implementation project(':unityLibrary') + implementation 'com.google.android.play:core:1.10.0' + } + +android { + assetPacks = [":install_time_pack"] + + compileSdkVersion 30 + buildToolsVersion '30.0.2' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 30 + applicationId 'com.idlelegend.arenaclash.battlefire' + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + versionCode 1 + versionName '0.1.0' + } + + aaptOptions { + noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') + ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" + } + + lintOptions { + abortOnError false + } + + buildTypes { + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt') + signingConfig signingConfigs.debug + jniDebuggable true + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt') + signingConfig signingConfigs.debug + } + } + + packagingOptions { + doNotStrip '*/armeabi-v7a/*.so' + doNotStrip '*/arm64-v8a/*.so' + doNotStrip '*/x86/*.so' + doNotStrip '*/x86_64/*.so' + } + + bundle { + language { + enableSplit = false + } + density { + enableSplit = false + } + abi { + enableSplit = true + } + } +} diff --git a/BFVersions/android/dz_google_abb/launcher/src/main/AndroidManifest.xml b/BFVersions/android/dz_google_abb/launcher/src/main/AndroidManifest.xml old mode 100644 new mode 100755 index 3239553be..579b7548c --- a/BFVersions/android/dz_google_abb/launcher/src/main/AndroidManifest.xml +++ b/BFVersions/android/dz_google_abb/launcher/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/BFVersions/android/dz_google_abb/launcher/src/main/res/drawable/firebase_icon.png b/BFVersions/android/dz_google_abb/launcher/src/main/res/drawable/firebase_icon.png old mode 100644 new mode 100755 diff --git a/BFVersions/android/dz_google_abb/unityLibrary/build.gradle b/BFVersions/android/dz_google_abb/unityLibrary/build.gradle old mode 100644 new mode 100755 index 83a20ea5e..06a480bee --- a/BFVersions/android/dz_google_abb/unityLibrary/build.gradle +++ b/BFVersions/android/dz_google_abb/unityLibrary/build.gradle @@ -1,125 +1,125 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN - -apply plugin: 'com.android.library' - - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation(name: 'androidx.activity.activity-1.0.0', ext:'aar') - implementation(name: 'androidx.appcompat.appcompat-1.1.0', ext:'aar') - implementation(name: 'androidx.appcompat.appcompat-resources-1.1.0', ext:'aar') - implementation(name: 'androidx.browser.browser-1.0.0', ext:'aar') - implementation(name: 'androidx.cardview.cardview-1.0.0', ext:'aar') - implementation(name: 'androidx.core.core-ktx-1.3.2', ext:'aar') - implementation(name: 'androidx.legacy.legacy-support-v4-1.0.0', ext:'aar') - implementation(name: 'androidx.media.media-1.0.0', ext:'aar') - implementation(name: 'androidx.savedstate.savedstate-1.0.0', ext:'aar') - implementation(name: 'androidx.vectordrawable.vectordrawable-animated-1.1.0', ext:'aar') - implementation(name: 'com.android.installreferrer.installreferrer-2.1', ext:'aar') - implementation(name: 'com.appsflyer.af-android-sdk-6.4.1', ext:'aar') - implementation(name: 'com.appsflyer.oaid-6.2.4', ext:'aar') - implementation(name: 'com.appsflyer.unity-wrapper-6.4.1', ext:'aar') - implementation(name: 'com.facebook.android.facebook-applinks-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-common-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-core-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-gamingservices-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-login-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-share-11.3.0', ext:'aar') - implementation(name: 'facebook-android-wrapper-11.0.0', ext:'aar') - implementation(name: 'screenutils-release', ext:'aar') - 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 platform('com.google.firebase:firebase-bom:29.2.1') - implementation 'com.google.firebase:firebase-analytics:21.1.0' - implementation 'com.google.firebase:firebase-messaging' -} - -android { - compileSdkVersion 30 - buildToolsVersion '30.0.2' - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - defaultConfig { - minSdkVersion 19 - targetSdkVersion 30 - ndk { - abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' - } - versionCode 1 - versionName '0.1.0' - consumerProguardFiles 'proguard-unity.txt' - } - - lintOptions { - abortOnError false - } - - aaptOptions { - noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') - ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" - } - - packagingOptions { - doNotStrip '*/armeabi-v7a/*.so' - doNotStrip '*/arm64-v8a/*.so' - doNotStrip '*/x86/*.so' - doNotStrip '*/x86_64/*.so' - } -} - -def getSdkDir() { - Properties local = new Properties() - local.load(new FileInputStream("${rootDir}/local.properties")) - return local.getProperty('sdk.dir') -} - -def BuildIl2Cpp(String workingDir, String targetDirectory, String architecture, String abi, String configuration) { - 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") - 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") -} - -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('\\\\', '/'), '/src/main/jniLibs/', 'x86', 'x86', 'Release'); - BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'x64', 'x86_64', 'Release'); - } - } - afterEvaluate { - if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders')) - project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask - if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders')) - project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask - } - sourceSets { - main { - jni.srcDirs = ["src/main/Il2CppOutputProject"] - } - } +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +apply plugin: 'com.android.library' + + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation(name: 'androidx.activity.activity-1.0.0', ext:'aar') + implementation(name: 'androidx.appcompat.appcompat-1.1.0', ext:'aar') + implementation(name: 'androidx.appcompat.appcompat-resources-1.1.0', ext:'aar') + implementation(name: 'androidx.browser.browser-1.0.0', ext:'aar') + implementation(name: 'androidx.cardview.cardview-1.0.0', ext:'aar') + implementation(name: 'androidx.core.core-ktx-1.3.2', ext:'aar') + implementation(name: 'androidx.legacy.legacy-support-v4-1.0.0', ext:'aar') + implementation(name: 'androidx.media.media-1.0.0', ext:'aar') + implementation(name: 'androidx.savedstate.savedstate-1.0.0', ext:'aar') + implementation(name: 'androidx.vectordrawable.vectordrawable-animated-1.1.0', ext:'aar') + implementation(name: 'com.android.installreferrer.installreferrer-2.1', ext:'aar') + implementation(name: 'com.appsflyer.af-android-sdk-6.4.1', ext:'aar') + implementation(name: 'com.appsflyer.oaid-6.2.4', ext:'aar') + implementation(name: 'com.appsflyer.unity-wrapper-6.4.1', ext:'aar') + implementation(name: 'com.facebook.android.facebook-applinks-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-common-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-core-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-gamingservices-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-login-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-share-11.3.0', ext:'aar') + implementation(name: 'facebook-android-wrapper-11.0.0', ext:'aar') + implementation(name: 'screenutils-release', ext:'aar') + 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: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' +} + +android { + compileSdkVersion 30 + buildToolsVersion '30.0.2' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 30 + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + versionCode 1 + versionName '0.1.0' + consumerProguardFiles 'proguard-unity.txt' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') + ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" + } + + packagingOptions { + doNotStrip '*/armeabi-v7a/*.so' + doNotStrip '*/arm64-v8a/*.so' + doNotStrip '*/x86/*.so' + doNotStrip '*/x86_64/*.so' + } +} + +def getSdkDir() { + Properties local = new Properties() + local.load(new FileInputStream("${rootDir}/local.properties")) + return local.getProperty('sdk.dir') +} + +def BuildIl2Cpp(String workingDir, String targetDirectory, String architecture, String abi, String configuration) { + 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") + 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") +} + +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('\\\\', '/'), '/src/main/jniLibs/', 'x86', 'x86', 'Release'); + BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'x64', 'x86_64', 'Release'); + } + } + afterEvaluate { + if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders')) + project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask + if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders')) + project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask + } + sourceSets { + main { + jni.srcDirs = ["src/main/Il2CppOutputProject"] + } + } } \ No newline at end of file diff --git a/BFVersions/android/dz_google_abb/unityLibrary/src/main/AndroidManifest.xml b/BFVersions/android/dz_google_abb/unityLibrary/src/main/AndroidManifest.xml old mode 100644 new mode 100755 index 93e2fcce3..fbc021c38 --- a/BFVersions/android/dz_google_abb/unityLibrary/src/main/AndroidManifest.xml +++ b/BFVersions/android/dz_google_abb/unityLibrary/src/main/AndroidManifest.xml @@ -1,67 +1,66 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - REPLACE_BUILD_ID - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + REPLACE_BUILD_ID + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/message/BFMessage.java b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/message/BFMessage.java new file mode 100755 index 000000000..d4783688f --- /dev/null +++ b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/message/BFMessage.java @@ -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; +} diff --git a/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GoogleBilling.java b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GoogleBilling.java new file mode 100755 index 000000000..ba6174633 --- /dev/null +++ b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GoogleBilling.java @@ -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 mPurchase = new HashMap(); + private List mSubList = new ArrayList(); + private List mInAppList = new ArrayList(); + 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 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 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 mList = new ArrayList(); + 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 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 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 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 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 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 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; + } + } +} diff --git a/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GoogleLogin.java b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GoogleLogin.java new file mode 100755 index 000000000..e26ee01be --- /dev/null +++ b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GoogleLogin.java @@ -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() { + @Override + public void onComplete(@NonNull Task task) { + GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_LOGOUT_SUCCESS, ""); + } + }); + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + if (requestCode == REQUEST_CODE_GOOGLE_SIGN_IN) { + Task task = GoogleSignIn.getSignedInAccountFromIntent(data); + handleSignInResult(task); + } + } + + private void handleSignInResult(Task 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(); + } +} diff --git a/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GooglePlugin.java b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GooglePlugin.java new file mode 100755 index 000000000..4842382ca --- /dev/null +++ b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/GooglePlugin.java @@ -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()); + } +} \ No newline at end of file diff --git a/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/Security.java b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/Security.java new file mode 100755 index 000000000..1dd7fa7d6 --- /dev/null +++ b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/juzu/dz/third/Security.java @@ -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. + *

+ * 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; + } +} diff --git a/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java old mode 100644 new mode 100755 index cb9614b45..9a9eb71ca --- a/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java +++ b/BFVersions/android/dz_google_abb/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java @@ -1,174 +1,174 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN -package com.unity3d.player; - -import android.app.Activity; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.os.Process; -// import android.os.Build; - -import com.juzu.dz.third.GooglePlugin; - -public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents -{ - protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code - - // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player - // The command line arguments are passed as a string, separated by spaces - // UnityPlayerActivity calls this from 'onCreate' - // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan - // See https://docs.unity3d.com/Manual/CommandLineArguments.html - // @param cmdLine the current command line arguments, may be null - // @return the modified command line string or null - protected String updateUnityCommandLineArguments(String cmdLine) - { - return cmdLine; - } - - // Setup activity layout - @Override protected void onCreate(Bundle savedInstanceState) - { - requestWindowFeature(Window.FEATURE_NO_TITLE); - super.onCreate(savedInstanceState); - - String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity")); - getIntent().putExtra("unity", cmdLine); - - mUnityPlayer = new UnityPlayer(this, this); - setContentView(mUnityPlayer); - mUnityPlayer.requestFocus(); - - GooglePlugin.init(this); - } - - // When Unity player unloaded move task to background - @Override public void onUnityPlayerUnloaded() { - moveTaskToBack(true); - } - - // Callback before Unity player process is killed - @Override public void onUnityPlayerQuitted() { - } - - @Override protected void onNewIntent(Intent intent) - { - // To support deep linking, we need to make sure that the client can get access to - // the last sent intent. The clients access this through a JNI api that allows them - // to get the intent set on launch. To update that after launch we have to manually - // replace the intent with the one caught here. - setIntent(intent); - mUnityPlayer.newIntent(intent); - } - - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - GooglePlugin.onActivityResult(requestCode, resultCode, data); - } - - // Quit Unity - @Override protected void onDestroy () - { - GooglePlugin.onDestroy(); - mUnityPlayer.destroy(); - super.onDestroy(); - } - - // If the activity is in multi window mode or resizing the activity is allowed we will use - // onStart/onStop (the visibility callbacks) to determine when to pause/resume. - // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve - // existing behavior. - @Override protected void onStop() - { - super.onStop(); - - if (!MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.pause(); - } - - @Override protected void onStart() - { - super.onStart(); - - if (!MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.resume(); - } - - // Pause Unity - @Override protected void onPause() - { - super.onPause(); - - if (MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.pause(); - } - - // Resume Unity - @Override protected void onResume() - { - super.onResume(); - - if (MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.resume(); - } - - // Low Memory Unity - @Override public void onLowMemory() - { - super.onLowMemory(); - mUnityPlayer.lowMemory(); - } - - // Trim Memory Unity - @Override public void onTrimMemory(int level) - { - super.onTrimMemory(level); - if (level == TRIM_MEMORY_RUNNING_CRITICAL) - { - mUnityPlayer.lowMemory(); - } - } - - // This ensures the layout will be correct. - @Override public void onConfigurationChanged(Configuration newConfig) - { - super.onConfigurationChanged(newConfig); - mUnityPlayer.configurationChanged(newConfig); - } - - // Notify Unity of the focus change. - @Override public void onWindowFocusChanged(boolean hasFocus) - { - super.onWindowFocusChanged(hasFocus); - mUnityPlayer.windowFocusChanged(hasFocus); - } - - // For some reason the multiple keyevent type is not supported by the ndk. - // Force event injection by overriding dispatchKeyEvent(). - @Override public boolean dispatchKeyEvent(KeyEvent event) - { - if (event.getAction() == KeyEvent.ACTION_MULTIPLE) - return mUnityPlayer.injectEvent(event); - return super.dispatchKeyEvent(event); - } - - // Pass any events not handled by (unfocused) views straight to UnityPlayer - @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } - @Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } - /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } -} +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN +package com.unity3d.player; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.os.Process; +// import android.os.Build; + +import com.juzu.dz.third.GooglePlugin; + +public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents +{ + protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code + + // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player + // The command line arguments are passed as a string, separated by spaces + // UnityPlayerActivity calls this from 'onCreate' + // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan + // See https://docs.unity3d.com/Manual/CommandLineArguments.html + // @param cmdLine the current command line arguments, may be null + // @return the modified command line string or null + protected String updateUnityCommandLineArguments(String cmdLine) + { + return cmdLine; + } + + // Setup activity layout + @Override protected void onCreate(Bundle savedInstanceState) + { + requestWindowFeature(Window.FEATURE_NO_TITLE); + super.onCreate(savedInstanceState); + + String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity")); + getIntent().putExtra("unity", cmdLine); + + mUnityPlayer = new UnityPlayer(this, this); + setContentView(mUnityPlayer); + mUnityPlayer.requestFocus(); + + GooglePlugin.init(this); + } + + // When Unity player unloaded move task to background + @Override public void onUnityPlayerUnloaded() { + moveTaskToBack(true); + } + + // Callback before Unity player process is killed + @Override public void onUnityPlayerQuitted() { + } + + @Override protected void onNewIntent(Intent intent) + { + // To support deep linking, we need to make sure that the client can get access to + // the last sent intent. The clients access this through a JNI api that allows them + // to get the intent set on launch. To update that after launch we have to manually + // replace the intent with the one caught here. + setIntent(intent); + mUnityPlayer.newIntent(intent); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + GooglePlugin.onActivityResult(requestCode, resultCode, data); + } + + // Quit Unity + @Override protected void onDestroy () + { + GooglePlugin.onDestroy(); + mUnityPlayer.destroy(); + super.onDestroy(); + } + + // If the activity is in multi window mode or resizing the activity is allowed we will use + // onStart/onStop (the visibility callbacks) to determine when to pause/resume. + // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve + // existing behavior. + @Override protected void onStop() + { + super.onStop(); + + if (!MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.pause(); + } + + @Override protected void onStart() + { + super.onStart(); + + if (!MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.resume(); + } + + // Pause Unity + @Override protected void onPause() + { + super.onPause(); + + if (MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.pause(); + } + + // Resume Unity + @Override protected void onResume() + { + super.onResume(); + + if (MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.resume(); + } + + // Low Memory Unity + @Override public void onLowMemory() + { + super.onLowMemory(); + mUnityPlayer.lowMemory(); + } + + // Trim Memory Unity + @Override public void onTrimMemory(int level) + { + super.onTrimMemory(level); + if (level == TRIM_MEMORY_RUNNING_CRITICAL) + { + mUnityPlayer.lowMemory(); + } + } + + // This ensures the layout will be correct. + @Override public void onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + mUnityPlayer.configurationChanged(newConfig); + } + + // Notify Unity of the focus change. + @Override public void onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + mUnityPlayer.windowFocusChanged(hasFocus); + } + + // For some reason the multiple keyevent type is not supported by the ndk. + // Force event injection by overriding dispatchKeyEvent(). + @Override public boolean dispatchKeyEvent(KeyEvent event) + { + if (event.getAction() == KeyEvent.ACTION_MULTIPLE) + return mUnityPlayer.injectEvent(event); + return super.dispatchKeyEvent(event); + } + + // Pass any events not handled by (unfocused) views straight to UnityPlayer + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } + @Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } + /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } +} diff --git a/BFVersions/android/dz_google_abb/unityLibrary/src/main/res/xml/network_security_config.xml b/BFVersions/android/dz_google_abb/unityLibrary/src/main/res/xml/network_security_config.xml old mode 100644 new mode 100755 index 3f9b9c92f..0edfdf604 --- a/BFVersions/android/dz_google_abb/unityLibrary/src/main/res/xml/network_security_config.xml +++ b/BFVersions/android/dz_google_abb/unityLibrary/src/main/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/BFVersions/android/dz_google_apk/build.gradle b/BFVersions/android/dz_google_apk/build.gradle old mode 100644 new mode 100755 index 0b06040b2..f00e46780 --- a/BFVersions/android/dz_google_apk/build.gradle +++ b/BFVersions/android/dz_google_apk/build.gradle @@ -1,40 +1,42 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN - -buildscript { - repositories { - google() - jcenter() - mavenCentral() - } - 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' - } -} - -allprojects { - buildscript { - repositories { - google() - jcenter() - } - -// dependencies { -// classpath 'com.android.tools.build:gradle:4.0.1' -// -// } - } - - repositories { - google() - jcenter() - flatDir { - dirs "${project(':unityLibrary').projectDir}/libs" - } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + 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:+' + } +} + +allprojects { + buildscript { + repositories { + google() + jcenter() + } + +// dependencies { +// classpath 'com.android.tools.build:gradle:4.0.1' +// +// } + } + + repositories { + google() + jcenter() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/BFVersions/android/dz_google_apk/gradle.properties b/BFVersions/android/dz_google_apk/gradle.properties old mode 100644 new mode 100755 index 39b7fdd1d..130c0e6f8 --- a/BFVersions/android/dz_google_apk/gradle.properties +++ b/BFVersions/android/dz_google_apk/gradle.properties @@ -1,5 +1,6 @@ -org.gradle.jvmargs=-Xmx4096M -org.gradle.parallel=true -android.useAndroidX=true -android.enableJetifier=true -unityStreamingAssets=.unity3d, .bytes, .ab, UnityServicesProjectConfiguration.json \ No newline at end of file +org.gradle.jvmargs=-Xmx4096M +org.gradle.parallel=true +android.useAndroidX=true +android.enableJetifier=true +unityStreamingAssets=.unity3d, .bytes, .ab, UnityServicesProjectConfiguration.json +unityTemplateVersion=4 \ No newline at end of file diff --git a/BFVersions/android/dz_google_apk/gradle/wrapper/gradle-wrapper.properties b/BFVersions/android/dz_google_apk/gradle/wrapper/gradle-wrapper.properties new file mode 100755 index 000000000..abff0a8d3 --- /dev/null +++ b/BFVersions/android/dz_google_apk/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/AndroidManifest.xml b/BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/AndroidManifest.xml deleted file mode 100644 index 6ad08dd83..000000000 --- a/BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/AndroidManifest.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/build.gradle b/BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/build.gradle deleted file mode 100644 index c4321566e..000000000 --- a/BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/build.gradle +++ /dev/null @@ -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 - } -} diff --git a/BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/libs/IronSource-bridge.jar b/BFVersions/android/dz_google_apk/unityLibrary/IronSource.androidlib/libs/IronSource-bridge.jar deleted file mode 100644 index 44dcadb64492dda1e3be8e0ff252f35c1b13d908..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86692 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 - project.repositories { - def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") - maven { - 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 - } - 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 - } - maven { - url "https://cboost.jfrog.io/artifactory/chartboost-ads/" // Assets/IronSource/Editor/ISChartboostAdapterDependencies.xml:15 - } - maven { - url "https://repo.maven.apache.org/maven2/" // Assets/IronSource/Editor/ISFyberAdapterDependencies.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 - } - 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 - } - maven { - url "https://jitpack.io/" // Assets/IronSource/Editor/ISVungleAdapterDependencies.xml:8 - } - mavenLocal() - jcenter() - mavenCentral() - } -} -// Android Resolver Repos End - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:multidex:1.0.3' - - implementation(name: 'screenutils-release', ext:'aar') - implementation(name: 'ThinkingSDK', ext:'aar') - implementation(name: 'UnityUtils-release', ext:'aar') - implementation(name: 'facebook-android-wrapper-15.1.0', ext:'aar') - implementation 'com.google.android.gms:play-services-auth:20.1.0' - implementation "com.android.billingclient:billing:6.2.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 - 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.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.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.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 -// 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 { - compileSdkVersion 34 - buildToolsVersion '30.0.2' - - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - - defaultConfig { - minSdkVersion 24 - targetSdkVersion 34 - ndk { - abiFilters 'armeabi-v7a', 'arm64-v8a' - } - versionCode 1 - versionName '0.1.0' - consumerProguardFiles 'proguard-unity.txt' - } - - lintOptions { - abortOnError false - } - - aaptOptions { - noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') - ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" - } - - packagingOptions { - doNotStrip '*/armeabi-v7a/*.so' - doNotStrip '*/arm64-v8a/*.so' - } -} - -def getSdkDir() { - Properties local = new Properties() - local.load(new FileInputStream("${rootDir}/local.properties")) - return local.getProperty('sdk.dir') -} - -def BuildIl2Cpp(String workingDir, String targetDirectory, String architecture, String abi, String configuration) { - 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") - 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") -} - -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'); - } - } - afterEvaluate { - if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders')) - project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask - if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders')) - project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask - } - sourceSets { - main { - jni.srcDirs = ["src/main/Il2CppOutputProject"] - } - } -} - - - +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +apply plugin: 'com.android.library' + + +// Android Resolver Repos Start +([rootProject] + (rootProject.subprojects as List)).each { project -> + project.repositories { + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "https://maven.google.com" + } + maven { + url "https://verve.jfrog.io/artifactory/verve-gradle-release" // Assets/ThirdParty/MaxSdk/Mediation/Verve/Editor/Dependencies.xml:7 + } + maven { + 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/ThirdParty/MaxSdk/Mediation/Chartboost/Editor/Dependencies.xml:8 + } + maven { + 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/ThirdParty/MaxSdk/Mediation/Mintegral/Editor/Dependencies.xml:8 + } + maven { + url "https://artifact.bytedance.com/repository/pangle" // Assets/ThirdParty/MaxSdk/Mediation/ByteDance/Editor/Dependencies.xml:8 + } + maven { url "../Upload-TaurusX" } + maven { + url "https://bitbucket.org/sdkcenter/sdkcenter/raw/release" + } + mavenLocal() + jcenter() + mavenCentral() + } +} +// Android Resolver Repos End + +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(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' + +// 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: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.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.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.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 +} + +android { + compileSdkVersion 34 + buildToolsVersion '30.0.2' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 34 + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a' + } + versionCode 1 + versionName '0.1.0' + consumerProguardFiles 'proguard-unity.txt' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') + ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" + } + + packagingOptions { + doNotStrip '*/armeabi-v7a/*.so' + doNotStrip '*/arm64-v8a/*.so' + } +} + +def getSdkDir() { + Properties local = new Properties() + local.load(new FileInputStream("${rootDir}/local.properties")) + return local.getProperty('sdk.dir') +} + +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 { + executable workingDir + "/src/main/Il2CppOutputProject/IL2CPP/build/deploy/il2cpp" + executableExtension + args commandLineArgs + environment "ANDROID_SDK_ROOT", getSdkDir() + } + 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('\\\\', '/'), 'Release', 'armv7', 'armeabi-v7a', [ ] as String[]); + BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), 'Release', 'arm64', 'arm64-v8a', [ ] as String[]); + } + } + afterEvaluate { + if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders')) + project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask + if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders')) + project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask + } + sourceSets { + main { + jni.srcDirs = ["src/main/Il2CppOutputProject"] + } + } +} + + + diff --git a/BFVersions/android/dz_google_apk/unityLibrary/src/main/AndroidManifest.xml b/BFVersions/android/dz_google_apk/unityLibrary/src/main/AndroidManifest.xml old mode 100644 new mode 100755 index 095438cb6..cb32332d8 --- a/BFVersions/android/dz_google_apk/unityLibrary/src/main/AndroidManifest.xml +++ b/BFVersions/android/dz_google_apk/unityLibrary/src/main/AndroidManifest.xml @@ -1,79 +1,87 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - REPLACE_BUILD_ID - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BFVersions/android/dz_google_apk/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java b/BFVersions/android/dz_google_apk/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java old mode 100644 new mode 100755 index ca3396058..e08cb5c0c --- a/BFVersions/android/dz_google_apk/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java +++ b/BFVersions/android/dz_google_apk/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java @@ -1,188 +1,179 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN -package com.unity3d.player; - -import android.app.Activity; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.MotionEvent; -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; - -public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents -{ - protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code - - // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player - // The command line arguments are passed as a string, separated by spaces - // UnityPlayerActivity calls this from 'onCreate' - // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan - // See https://docs.unity3d.com/Manual/CommandLineArguments.html - // @param cmdLine the current command line arguments, may be null - // @return the modified command line string or null - protected String updateUnityCommandLineArguments(String cmdLine) - { - return cmdLine; - } - - // Setup activity layout - @Override protected void onCreate(Bundle savedInstanceState) - { - requestWindowFeature(Window.FEATURE_NO_TITLE); - super.onCreate(savedInstanceState); - - String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity")); - getIntent().putExtra("unity", cmdLine); - - mUnityPlayer = new UnityPlayer(this, this); - setContentView(mUnityPlayer); - mUnityPlayer.requestFocus(); - - GooglePlugin.init(this); - } - - // When Unity player unloaded move task to background - @Override public void onUnityPlayerUnloaded() { - moveTaskToBack(true); - } - - // Callback before Unity player process is killed - @Override public void onUnityPlayerQuitted() { - } - - @Override protected void onNewIntent(Intent intent) - { - // To support deep linking, we need to make sure that the client can get access to - // the last sent intent. The clients access this through a JNI api that allows them - // to get the intent set on launch. To update that after launch we have to manually - // replace the intent with the one caught here. - setIntent(intent); - mUnityPlayer.newIntent(intent); - } - - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - GooglePlugin.onActivityResult(requestCode, resultCode, data); - } - - // Quit Unity - @Override protected void onDestroy () - { - GooglePlugin.onDestroy(); - mUnityPlayer.destroy(); - super.onDestroy(); - } - - // If the activity is in multi window mode or resizing the activity is allowed we will use - // onStart/onStop (the visibility callbacks) to determine when to pause/resume. - // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve - // existing behavior. - @Override protected void onStop() - { - super.onStop(); - - if (!MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.pause(); - } - - @Override protected void onStart() - { - super.onStart(); - - if (!MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.resume(); - } - - // Pause Unity - @Override protected void onPause() - { - super.onPause(); - - if (MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.pause(); - IronSource.onPause(this); - } - - // Resume Unity - @Override protected void onResume() - { - super.onResume(); - - if (MultiWindowSupport.getAllowResizableWindow(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(); - // } - } - - // Low Memory Unity - @Override public void onLowMemory() - { - super.onLowMemory(); - mUnityPlayer.lowMemory(); - } - - // Trim Memory Unity - @Override public void onTrimMemory(int level) - { - super.onTrimMemory(level); - if (level == TRIM_MEMORY_RUNNING_CRITICAL) - { - mUnityPlayer.lowMemory(); - } - } - - // This ensures the layout will be correct. - @Override public void onConfigurationChanged(Configuration newConfig) - { - super.onConfigurationChanged(newConfig); - mUnityPlayer.configurationChanged(newConfig); - } - - // Notify Unity of the focus change. - @Override public void onWindowFocusChanged(boolean hasFocus) - { - super.onWindowFocusChanged(hasFocus); - mUnityPlayer.windowFocusChanged(hasFocus); - } - - // For some reason the multiple keyevent type is not supported by the ndk. - // Force event injection by overriding dispatchKeyEvent(). - @Override public boolean dispatchKeyEvent(KeyEvent event) - { - if (event.getAction() == KeyEvent.ACTION_MULTIPLE) - return mUnityPlayer.injectEvent(event); - return super.dispatchKeyEvent(event); - } - - // Pass any events not handled by (unfocused) views straight to UnityPlayer - @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } - @Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } - /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } -} +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN +package com.unity3d.player; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.os.Process; + +// import com.chartboost.sdk.Chartboost; +import com.juzu.dz.third.GooglePlugin; +// import com.ironsource.mediationsdk.IronSource; + +public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents +{ + protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code + + // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player + // The command line arguments are passed as a string, separated by spaces + // UnityPlayerActivity calls this from 'onCreate' + // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan + // See https://docs.unity3d.com/Manual/CommandLineArguments.html + // @param cmdLine the current command line arguments, may be null + // @return the modified command line string or null + protected String updateUnityCommandLineArguments(String cmdLine) + { + return cmdLine; + } + + // Setup activity layout + @Override protected void onCreate(Bundle savedInstanceState) + { + requestWindowFeature(Window.FEATURE_NO_TITLE); + super.onCreate(savedInstanceState); + + String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity")); + getIntent().putExtra("unity", cmdLine); + + mUnityPlayer = new UnityPlayer(this, this); + setContentView(mUnityPlayer); + mUnityPlayer.requestFocus(); + + GooglePlugin.init(this); + } + + // When Unity player unloaded move task to background + @Override public void onUnityPlayerUnloaded() { + moveTaskToBack(true); + } + + // Callback before Unity player process is killed + @Override public void onUnityPlayerQuitted() { + } + + @Override protected void onNewIntent(Intent intent) + { + // To support deep linking, we need to make sure that the client can get access to + // the last sent intent. The clients access this through a JNI api that allows them + // to get the intent set on launch. To update that after launch we have to manually + // replace the intent with the one caught here. + setIntent(intent); + mUnityPlayer.newIntent(intent); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + GooglePlugin.onActivityResult(requestCode, resultCode, data); + } + + // Quit Unity + @Override protected void onDestroy () + { + GooglePlugin.onDestroy(); + mUnityPlayer.destroy(); + super.onDestroy(); + } + + // If the activity is in multi window mode or resizing the activity is allowed we will use + // onStart/onStop (the visibility callbacks) to determine when to pause/resume. + // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve + // existing behavior. + @Override protected void onStop() + { + super.onStop(); + + if (!MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.pause(); + } + + @Override protected void onStart() + { + super.onStart(); + + if (!MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.resume(); + } + + // Pause Unity + @Override protected void onPause() + { + super.onPause(); + + MultiWindowSupport.saveMultiWindowMode(this); + + if (MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.pause(); + // IronSource.onPause(this); + } + + // Resume Unity + @Override protected void onResume() + { + super.onResume(); + + if (MultiWindowSupport.getAllowResizableWindow(this) && !MultiWindowSupport.isMultiWindowModeChangedToTrue(this)) + return; + + mUnityPlayer.resume(); + // IronSource.onResume(this); + } + + // Low Memory Unity + @Override public void onLowMemory() + { + super.onLowMemory(); + mUnityPlayer.lowMemory(); + } + + // Trim Memory Unity + @Override public void onTrimMemory(int level) + { + super.onTrimMemory(level); + if (level == TRIM_MEMORY_RUNNING_CRITICAL) + { + mUnityPlayer.lowMemory(); + } + } + + // This ensures the layout will be correct. + @Override public void onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + mUnityPlayer.configurationChanged(newConfig); + } + + // Notify Unity of the focus change. + @Override public void onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + mUnityPlayer.windowFocusChanged(hasFocus); + } + + // For some reason the multiple keyevent type is not supported by the ndk. + // Force event injection by overriding dispatchKeyEvent(). + @Override public boolean dispatchKeyEvent(KeyEvent event) + { + if (event.getAction() == KeyEvent.ACTION_MULTIPLE) + return mUnityPlayer.injectEvent(event); + return super.dispatchKeyEvent(event); + } + + // Pass any events not handled by (unfocused) views straight to UnityPlayer + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } + @Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } + /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } +} diff --git a/BFVersions/android/dz_google_apk/unityLibrary/src/main/res/xml/network_security_config.xml b/BFVersions/android/dz_google_apk/unityLibrary/src/main/res/xml/network_security_config.xml old mode 100644 new mode 100755 index 3f9b9c92f..0edfdf604 --- a/BFVersions/android/dz_google_apk/unityLibrary/src/main/res/xml/network_security_config.xml +++ b/BFVersions/android/dz_google_apk/unityLibrary/src/main/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/BFVersions/android/dz_release/build.gradle b/BFVersions/android/dz_release/build.gradle old mode 100644 new mode 100755 index 2f61422bd..2e1ead0e0 --- a/BFVersions/android/dz_release/build.gradle +++ b/BFVersions/android/dz_release/build.gradle @@ -1,40 +1,40 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN - -buildscript { - repositories { - google() - jcenter() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' - classpath 'com.google.gms:google-services:4.3.13' - // classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1' - } -} - -allprojects { - buildscript { - repositories { - google() - jcenter() - } - -// dependencies { -// classpath 'com.android.tools.build:gradle:4.0.1' -// -// } - } - - repositories { - google() - jcenter() - flatDir { - dirs "${project(':unityLibrary').projectDir}/libs" - } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + jcenter() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.google.gms:google-services:4.3.13' + // classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1' + } +} + +allprojects { + buildscript { + repositories { + google() + jcenter() + } + +// dependencies { +// classpath 'com.android.tools.build:gradle:4.0.1' +// +// } + } + + repositories { + google() + jcenter() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/BFVersions/android/dz_release/gradle.properties b/BFVersions/android/dz_release/gradle.properties old mode 100644 new mode 100755 index 58e8481f1..0521300d1 --- a/BFVersions/android/dz_release/gradle.properties +++ b/BFVersions/android/dz_release/gradle.properties @@ -1,6 +1,6 @@ -org.gradle.jvmargs=-Xmx4096M -org.gradle.parallel=true -android.enableR8=false -android.useAndroidX=true -android.enableJetifier=true +org.gradle.jvmargs=-Xmx4096M +org.gradle.parallel=true +android.enableR8=false +android.useAndroidX=true +android.enableJetifier=true unityStreamingAssets=.unity3d, .bytes, .ab \ No newline at end of file diff --git a/BFVersions/android/dz_release/unityLibrary/build.gradle b/BFVersions/android/dz_release/unityLibrary/build.gradle old mode 100644 new mode 100755 index e01f9a38b..30a1149be --- a/BFVersions/android/dz_release/unityLibrary/build.gradle +++ b/BFVersions/android/dz_release/unityLibrary/build.gradle @@ -1,128 +1,128 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN - -apply plugin: 'com.android.library' - - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:multidex:1.0.3' - implementation(name: 'androidx.activity.activity-1.0.0', ext:'aar') - implementation(name: 'androidx.appcompat.appcompat-1.1.0', ext:'aar') - implementation(name: 'androidx.appcompat.appcompat-resources-1.1.0', ext:'aar') -// implementation(name: 'androidx.browser.browser-1.0.0', ext:'aar') - implementation(name: 'androidx.cardview.cardview-1.0.0', ext:'aar') - implementation(name: 'androidx.core.core-ktx-1.3.2', ext:'aar') - implementation(name: 'androidx.legacy.legacy-support-v4-1.0.0', ext:'aar') - implementation(name: 'androidx.savedstate.savedstate-1.0.0', ext:'aar') - implementation(name: 'androidx.vectordrawable.vectordrawable-animated-1.1.0', ext:'aar') - implementation(name: 'com.android.installreferrer.installreferrer-2.1', ext:'aar') - implementation(name: 'com.appsflyer.af-android-sdk-6.4.1', ext:'aar') - implementation(name: 'com.appsflyer.oaid-6.2.4', ext:'aar') - implementation(name: 'com.appsflyer.unity-wrapper-6.4.1', ext:'aar') - implementation(name: 'com.facebook.android.facebook-applinks-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-common-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-core-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-gamingservices-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-login-11.3.0', ext:'aar') - implementation(name: 'com.facebook.android.facebook-share-11.3.0', ext:'aar') - implementation(name: 'facebook-android-wrapper-11.0.0', ext:'aar') - implementation(name: 'screenutils-release', ext:'aar') - 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 platform('com.google.firebase:firebase-bom:29.2.1') - implementation 'com.google.firebase:firebase-analytics:21.1.0' - implementation 'com.google.firebase:firebase-messaging' - implementation 'com.google.android.gms:play-services-ads:21.0.0' - implementation 'com.google.ads.mediation:facebook:6.11.0.1' - implementation 'com.unity3d.ads:unity-ads:4.2.1' - implementation 'com.google.ads.mediation:unity:4.2.1.1' - implementation 'com.google.ads.mediation:applovin:11.4.4.0' - implementation(name: 'mediationsdk-7.2.3.1', ext:'aar') -} - -android { - compileSdkVersion 30 - buildToolsVersion '30.0.2' - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - defaultConfig { - minSdkVersion 19 - targetSdkVersion 30 - ndk { - abiFilters 'armeabi-v7a' - } - versionCode 1 - versionName '0.1.0' - consumerProguardFiles 'proguard-unity.txt' - } - - lintOptions { - abortOnError false - } - - aaptOptions { - noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') - ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" - } - - packagingOptions { - doNotStrip '*/armeabi-v7a/*.so' - } -} - -def getSdkDir() { - Properties local = new Properties() - local.load(new FileInputStream("${rootDir}/local.properties")) - return local.getProperty('sdk.dir') -} - -def BuildIl2Cpp(String workingDir, String targetDirectory, String architecture, String abi, String configuration) { - 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") - 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") -} - -android { - task BuildIl2CppTask { - doLast { - BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'ARMv7', 'armeabi-v7a', 'Release'); - } - } - afterEvaluate { - if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders')) - project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask - if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders')) - project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask - } - sourceSets { - main { - jni.srcDirs = ["src/main/Il2CppOutputProject"] - } - } -} - - - +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +apply plugin: 'com.android.library' + + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:multidex:1.0.3' + implementation(name: 'androidx.activity.activity-1.0.0', ext:'aar') + implementation(name: 'androidx.appcompat.appcompat-1.1.0', ext:'aar') + implementation(name: 'androidx.appcompat.appcompat-resources-1.1.0', ext:'aar') +// implementation(name: 'androidx.browser.browser-1.0.0', ext:'aar') + implementation(name: 'androidx.cardview.cardview-1.0.0', ext:'aar') + implementation(name: 'androidx.core.core-ktx-1.3.2', ext:'aar') + implementation(name: 'androidx.legacy.legacy-support-v4-1.0.0', ext:'aar') + implementation(name: 'androidx.savedstate.savedstate-1.0.0', ext:'aar') + implementation(name: 'androidx.vectordrawable.vectordrawable-animated-1.1.0', ext:'aar') + implementation(name: 'com.android.installreferrer.installreferrer-2.1', ext:'aar') + implementation(name: 'com.appsflyer.af-android-sdk-6.4.1', ext:'aar') + implementation(name: 'com.appsflyer.oaid-6.2.4', ext:'aar') + implementation(name: 'com.appsflyer.unity-wrapper-6.4.1', ext:'aar') + implementation(name: 'com.facebook.android.facebook-applinks-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-common-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-core-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-gamingservices-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-login-11.3.0', ext:'aar') + implementation(name: 'com.facebook.android.facebook-share-11.3.0', ext:'aar') + implementation(name: 'facebook-android-wrapper-11.0.0', ext:'aar') + implementation(name: 'screenutils-release', ext:'aar') + 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: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' + implementation 'com.google.android.gms:play-services-ads:21.0.0' + implementation 'com.google.ads.mediation:facebook:6.11.0.1' + implementation 'com.unity3d.ads:unity-ads:4.2.1' + implementation 'com.google.ads.mediation:unity:4.2.1.1' + implementation 'com.google.ads.mediation:applovin:11.4.4.0' + implementation(name: 'mediationsdk-7.2.3.1', ext:'aar') +} + +android { + compileSdkVersion 30 + buildToolsVersion '30.0.2' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 30 + ndk { + abiFilters 'armeabi-v7a' + } + versionCode 1 + versionName '0.1.0' + consumerProguardFiles 'proguard-unity.txt' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') + ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" + } + + packagingOptions { + doNotStrip '*/armeabi-v7a/*.so' + } +} + +def getSdkDir() { + Properties local = new Properties() + local.load(new FileInputStream("${rootDir}/local.properties")) + return local.getProperty('sdk.dir') +} + +def BuildIl2Cpp(String workingDir, String targetDirectory, String architecture, String abi, String configuration) { + 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") + 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") +} + +android { + task BuildIl2CppTask { + doLast { + BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), '/src/main/jniLibs/', 'ARMv7', 'armeabi-v7a', 'Release'); + } + } + afterEvaluate { + if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders')) + project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask + if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders')) + project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask + } + sourceSets { + main { + jni.srcDirs = ["src/main/Il2CppOutputProject"] + } + } +} + + + diff --git a/BFVersions/android/dz_release/unityLibrary/src/main/AndroidManifest.xml b/BFVersions/android/dz_release/unityLibrary/src/main/AndroidManifest.xml old mode 100644 new mode 100755 index 997a26882..92543085c --- a/BFVersions/android/dz_release/unityLibrary/src/main/AndroidManifest.xml +++ b/BFVersions/android/dz_release/unityLibrary/src/main/AndroidManifest.xml @@ -1,54 +1,53 @@ - - - - - - - - - - - - - - - - - - - - - REPLACE_BUILD_ID - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + REPLACE_BUILD_ID + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BFVersions/android/dz_release/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java b/BFVersions/android/dz_release/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java old mode 100644 new mode 100755 index ca3396058..05e5f71d6 --- a/BFVersions/android/dz_release/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java +++ b/BFVersions/android/dz_release/unityLibrary/src/main/java/com/unity3d/player/UnityPlayerActivity.java @@ -1,188 +1,188 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN -package com.unity3d.player; - -import android.app.Activity; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.MotionEvent; -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; - -public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents -{ - protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code - - // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player - // The command line arguments are passed as a string, separated by spaces - // UnityPlayerActivity calls this from 'onCreate' - // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan - // See https://docs.unity3d.com/Manual/CommandLineArguments.html - // @param cmdLine the current command line arguments, may be null - // @return the modified command line string or null - protected String updateUnityCommandLineArguments(String cmdLine) - { - return cmdLine; - } - - // Setup activity layout - @Override protected void onCreate(Bundle savedInstanceState) - { - requestWindowFeature(Window.FEATURE_NO_TITLE); - super.onCreate(savedInstanceState); - - String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity")); - getIntent().putExtra("unity", cmdLine); - - mUnityPlayer = new UnityPlayer(this, this); - setContentView(mUnityPlayer); - mUnityPlayer.requestFocus(); - - GooglePlugin.init(this); - } - - // When Unity player unloaded move task to background - @Override public void onUnityPlayerUnloaded() { - moveTaskToBack(true); - } - - // Callback before Unity player process is killed - @Override public void onUnityPlayerQuitted() { - } - - @Override protected void onNewIntent(Intent intent) - { - // To support deep linking, we need to make sure that the client can get access to - // the last sent intent. The clients access this through a JNI api that allows them - // to get the intent set on launch. To update that after launch we have to manually - // replace the intent with the one caught here. - setIntent(intent); - mUnityPlayer.newIntent(intent); - } - - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - GooglePlugin.onActivityResult(requestCode, resultCode, data); - } - - // Quit Unity - @Override protected void onDestroy () - { - GooglePlugin.onDestroy(); - mUnityPlayer.destroy(); - super.onDestroy(); - } - - // If the activity is in multi window mode or resizing the activity is allowed we will use - // onStart/onStop (the visibility callbacks) to determine when to pause/resume. - // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve - // existing behavior. - @Override protected void onStop() - { - super.onStop(); - - if (!MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.pause(); - } - - @Override protected void onStart() - { - super.onStart(); - - if (!MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.resume(); - } - - // Pause Unity - @Override protected void onPause() - { - super.onPause(); - - if (MultiWindowSupport.getAllowResizableWindow(this)) - return; - - mUnityPlayer.pause(); - IronSource.onPause(this); - } - - // Resume Unity - @Override protected void onResume() - { - super.onResume(); - - if (MultiWindowSupport.getAllowResizableWindow(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(); - // } - } - - // Low Memory Unity - @Override public void onLowMemory() - { - super.onLowMemory(); - mUnityPlayer.lowMemory(); - } - - // Trim Memory Unity - @Override public void onTrimMemory(int level) - { - super.onTrimMemory(level); - if (level == TRIM_MEMORY_RUNNING_CRITICAL) - { - mUnityPlayer.lowMemory(); - } - } - - // This ensures the layout will be correct. - @Override public void onConfigurationChanged(Configuration newConfig) - { - super.onConfigurationChanged(newConfig); - mUnityPlayer.configurationChanged(newConfig); - } - - // Notify Unity of the focus change. - @Override public void onWindowFocusChanged(boolean hasFocus) - { - super.onWindowFocusChanged(hasFocus); - mUnityPlayer.windowFocusChanged(hasFocus); - } - - // For some reason the multiple keyevent type is not supported by the ndk. - // Force event injection by overriding dispatchKeyEvent(). - @Override public boolean dispatchKeyEvent(KeyEvent event) - { - if (event.getAction() == KeyEvent.ACTION_MULTIPLE) - return mUnityPlayer.injectEvent(event); - return super.dispatchKeyEvent(event); - } - - // Pass any events not handled by (unfocused) views straight to UnityPlayer - @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } - @Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } - /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } -} +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN +package com.unity3d.player; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.MotionEvent; +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; + +public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents +{ + protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code + + // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player + // The command line arguments are passed as a string, separated by spaces + // UnityPlayerActivity calls this from 'onCreate' + // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan + // See https://docs.unity3d.com/Manual/CommandLineArguments.html + // @param cmdLine the current command line arguments, may be null + // @return the modified command line string or null + protected String updateUnityCommandLineArguments(String cmdLine) + { + return cmdLine; + } + + // Setup activity layout + @Override protected void onCreate(Bundle savedInstanceState) + { + requestWindowFeature(Window.FEATURE_NO_TITLE); + super.onCreate(savedInstanceState); + + String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity")); + getIntent().putExtra("unity", cmdLine); + + mUnityPlayer = new UnityPlayer(this, this); + setContentView(mUnityPlayer); + mUnityPlayer.requestFocus(); + + GooglePlugin.init(this); + } + + // When Unity player unloaded move task to background + @Override public void onUnityPlayerUnloaded() { + moveTaskToBack(true); + } + + // Callback before Unity player process is killed + @Override public void onUnityPlayerQuitted() { + } + + @Override protected void onNewIntent(Intent intent) + { + // To support deep linking, we need to make sure that the client can get access to + // the last sent intent. The clients access this through a JNI api that allows them + // to get the intent set on launch. To update that after launch we have to manually + // replace the intent with the one caught here. + setIntent(intent); + mUnityPlayer.newIntent(intent); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + GooglePlugin.onActivityResult(requestCode, resultCode, data); + } + + // Quit Unity + @Override protected void onDestroy () + { + GooglePlugin.onDestroy(); + mUnityPlayer.destroy(); + super.onDestroy(); + } + + // If the activity is in multi window mode or resizing the activity is allowed we will use + // onStart/onStop (the visibility callbacks) to determine when to pause/resume. + // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve + // existing behavior. + @Override protected void onStop() + { + super.onStop(); + + if (!MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.pause(); + } + + @Override protected void onStart() + { + super.onStart(); + + if (!MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.resume(); + } + + // Pause Unity + @Override protected void onPause() + { + super.onPause(); + + if (MultiWindowSupport.getAllowResizableWindow(this)) + return; + + mUnityPlayer.pause(); + IronSource.onPause(this); + } + + // Resume Unity + @Override protected void onResume() + { + super.onResume(); + + if (MultiWindowSupport.getAllowResizableWindow(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(); + } + } + + // Low Memory Unity + @Override public void onLowMemory() + { + super.onLowMemory(); + mUnityPlayer.lowMemory(); + } + + // Trim Memory Unity + @Override public void onTrimMemory(int level) + { + super.onTrimMemory(level); + if (level == TRIM_MEMORY_RUNNING_CRITICAL) + { + mUnityPlayer.lowMemory(); + } + } + + // This ensures the layout will be correct. + @Override public void onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + mUnityPlayer.configurationChanged(newConfig); + } + + // Notify Unity of the focus change. + @Override public void onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + mUnityPlayer.windowFocusChanged(hasFocus); + } + + // For some reason the multiple keyevent type is not supported by the ndk. + // Force event injection by overriding dispatchKeyEvent(). + @Override public boolean dispatchKeyEvent(KeyEvent event) + { + if (event.getAction() == KeyEvent.ACTION_MULTIPLE) + return mUnityPlayer.injectEvent(event); + return super.dispatchKeyEvent(event); + } + + // Pass any events not handled by (unfocused) views straight to UnityPlayer + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); } + @Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } + /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } +} diff --git a/BFVersions/android/dz_release/unityLibrary/src/main/res/xml/network_security_config.xml b/BFVersions/android/dz_release/unityLibrary/src/main/res/xml/network_security_config.xml old mode 100644 new mode 100755 index 3f9b9c92f..0edfdf604 --- a/BFVersions/android/dz_release/unityLibrary/src/main/res/xml/network_security_config.xml +++ b/BFVersions/android/dz_release/unityLibrary/src/main/res/xml/network_security_config.xml @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/BFVersions/android/google_common/launcher/build.gradle b/BFVersions/android/google_common/launcher/build.gradle old mode 100644 new mode 100755 index 83c42e095..e49d8dd1f --- a/BFVersions/android/google_common/launcher/build.gradle +++ b/BFVersions/android/google_common/launcher/build.gradle @@ -1,72 +1,91 @@ -// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN - -apply plugin: 'com.android.application' -apply plugin: 'com.google.gms.google-services' -apply plugin: 'com.google.firebase.crashlytics' - -dependencies { - implementation project(':unityLibrary') - - } - -android { - compileSdkVersion 34 - buildToolsVersion '30.0.2' - - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - - defaultConfig { - minSdkVersion 24 - targetSdkVersion 34 - applicationId 'REPLACE_APPLICATION_ID' - ndk { - abiFilters 'armeabi-v7a', 'arm64-v8a' - } - versionCode 1 - versionName '0.1.0' - multiDexEnabled true - } - - aaptOptions { - noCompress = ['.ress', '.resource', '.obb'] + unityStreamingAssets.tokenize(', ') - ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" - } - - lintOptions { - abortOnError false - } - - buildTypes { - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt') - signingConfig signingConfigs.debug - jniDebuggable true - } - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt') - signingConfig signingConfigs.debug - } - } - - packagingOptions { - doNotStrip '*/armeabi-v7a/*.so' - doNotStrip '*/arm64-v8a/*.so' - } - - bundle { - language { - enableSplit = false - } - density { - enableSplit = false - } - abi { - enableSplit = true - } - } -} +// 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') + + } + +android { + compileSdkVersion 34 + buildToolsVersion '30.0.2' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + 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 + applicationId 'REPLACE_APPLICATION_ID' + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a' + } + versionCode 1 + versionName '0.1.0' + multiDexEnabled true + } + + aaptOptions { + noCompress = ['.unity3d', '.ress', '.resource', '.obb', '.bundle', '.unityexp'] + unityStreamingAssets.tokenize(', ') + ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~" + } + + lintOptions { + abortOnError false + } + + buildTypes { + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt') + signingConfig signingConfigs.release + jniDebuggable true + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt') + signingConfig signingConfigs.release + } + } + + packagingOptions { + doNotStrip '*/armeabi-v7a/*.so' + doNotStrip '*/arm64-v8a/*.so' + jniLibs { + useLegacyPackaging = true + } + } + + bundle { + language { + enableSplit = false + } + density { + enableSplit = false + } + abi { + enableSplit = true + } + } +} diff --git a/BFVersions/android/google_common/launcher/google-services.json b/BFVersions/android/google_common/launcher/google-services.json old mode 100644 new mode 100755 index fa98ddb06..c8ccc10d3 --- a/BFVersions/android/google_common/launcher/google-services.json +++ b/BFVersions/android/google_common/launcher/google-services.json @@ -1,63 +1,29 @@ -{ - "project_info": { - "project_number": "1008416471093", - "project_id": "knights-combo", - "storage_bucket": "knights-combo.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:1008416471093:android:121c54160b7045e499d97c", - "android_client_info": { - "package_name": "com.combo.heroes.puzzle.rpg" - } - }, - "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 - } - ], - "api_key": [ - { - "current_key": "AIzaSyDtkUzjjkNiAZszdepIDIYss0ioNNbjncA" - } - ], - "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" - } - } - ] - } - } - } - ], - "configuration_version": "1" +{ + "project_info": { + "project_number": "22951947163", + "project_id": "pull-pull-pull-heroes", + "storage_bucket": "pull-pull-pull-heroes.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:22951947163:android:378ebaf483bc3fb9304f3e", + "android_client_info": { + "package_name": "com.fortune.td.game.global" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyAmIdtI6ZwxXfCVILmtZEr9y4CP_e9JpFE" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" } \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/AndroidManifest.xml b/BFVersions/android/google_common/launcher/src/main/AndroidManifest.xml old mode 100644 new mode 100755 index 7ce400abb..3095e54c6 --- a/BFVersions/android/google_common/launcher/src/main/AndroidManifest.xml +++ b/BFVersions/android/google_common/launcher/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/drawable/firebase_icon.png b/BFVersions/android/google_common/launcher/src/main/res/drawable/firebase_icon.png old mode 100644 new mode 100755 index 8b3afdfdfe88ab40920703238e254e2bfb740bb9..b874c25a2457ddfb64682a0d31af6423b9a1bc58 GIT binary patch delta 650 zcmV;50(Jex1jYrBNPhxoNklo7WqXBH%1wx;X)tyb^RYW6PID;zWd(1 z%gi5s%#3qp?w@<+o_n5_5F(e8=_C+*9a6Xjj{Uh=P%}|;*xFA)*rGLTr?Bq0^8v%P*vS-Im*+U%H z12jZE)22K_DejL~!B1|Me&lJeOqG8|He=6_GljHFAAv=mW|CZiUnvUaJ#e7goobJKFp-~9XG(0<}>y5jMuX72g5$nH6is_x*E4PSEpWpH$K0g zNzR^hQQDXebkd7F+n+a delta 520 zcmV+j0{8vK1;qrANPhw6Nklyt zNK^MTn@VhN9FvJ%Bblh7{HAW!Mf9Z&U-$d0sinp&jbI#_dF?5R8Hd z@m}ko+AKmWcYmI#Tpb%lh@Gr^_^KuHRjF4z6`#y7#q{1(h<~aNnxRn83>^l|P`}>y zvs0F>&S_9A+0-fM(nhEmwDZ%Jl5D6G%$UtXePG{*?P~(pdO36erhPD!Flw$@#z0bX zI~mcAA8N7D^Hy?{2F;HB3h0n*=*(!yUjq}8&9F;BDSvxam+vq*0xdoy^(Hr!6QL-0 z^|8_3X>;g}H@qduW-ha4^pZNvtF}xn7*%R^Q@!HzwJg2!RMGAB4U)}l@RngXBqjKk z^>EiE{BB?5qKOZ10#?C|-9H=Dssi&y)15=HlClkc%pUp`U;xKwr$N(5%dP+b002ov KPDHLkU;%=?8u^<5 diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-es/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values-es/strings.xml old mode 100644 new mode 100755 index 5b58c6eed..e44094536 --- a/BFVersions/android/google_common/launcher/src/main/res/values-es/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-es/strings.xml @@ -1,4 +1,4 @@ - - - Combo de Jinetes + + + Pull Pull Pull Heroes \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-in/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values-in/strings.xml old mode 100644 new mode 100755 index 70aead2b3..e44094536 --- a/BFVersions/android/google_common/launcher/src/main/res/values-in/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-in/strings.xml @@ -1,4 +1,4 @@ - - - Ksatria Kombo + + + Pull Pull Pull Heroes \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-ja/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values-ja/strings.xml old mode 100644 new mode 100755 index 486a23561..4cea27dae --- a/BFVersions/android/google_common/launcher/src/main/res/values-ja/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-ja/strings.xml @@ -1,4 +1,4 @@ - - - ナイト戦線 + + + プルせよ!ヒーローズ \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-ko/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values-ko/strings.xml old mode 100644 new mode 100755 index 79a258916..09b30d308 --- a/BFVersions/android/google_common/launcher/src/main/res/values-ko/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-ko/strings.xml @@ -1,4 +1,4 @@ - - - 워리어 콤보 + + + 픽미픽미 영웅 \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-pt/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values-pt/strings.xml old mode 100644 new mode 100755 index 1d1b881c1..e44094536 --- a/BFVersions/android/google_common/launcher/src/main/res/values-pt/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-pt/strings.xml @@ -1,4 +1,4 @@ - - - Cavaleiros do Combo + + + Pull Pull Pull Heroes \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-th/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values-th/strings.xml old mode 100644 new mode 100755 index 575ea36cb..e44094536 --- a/BFVersions/android/google_common/launcher/src/main/res/values-th/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-th/strings.xml @@ -1,4 +1,4 @@ - - - อัศวินคอมโบ + + + Pull Pull Pull Heroes \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-v21/styles.xml b/BFVersions/android/google_common/launcher/src/main/res/values-v21/styles.xml old mode 100644 new mode 100755 index fc4509a2a..2630061ba --- a/BFVersions/android/google_common/launcher/src/main/res/values-v21/styles.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-v21/styles.xml @@ -1,5 +1,5 @@ - - - - + + + + diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-v28/styles.xml b/BFVersions/android/google_common/launcher/src/main/res/values-v28/styles.xml old mode 100644 new mode 100755 index aa23d59fb..a17f8019f --- a/BFVersions/android/google_common/launcher/src/main/res/values-v28/styles.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-v28/styles.xml @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-v30/freeformwindow.xml b/BFVersions/android/google_common/launcher/src/main/res/values-v30/freeformwindow.xml old mode 100644 new mode 100755 index 081fb7de7..dcb77289e --- a/BFVersions/android/google_common/launcher/src/main/res/values-v30/freeformwindow.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-v30/freeformwindow.xml @@ -1,8 +1,8 @@ - - - maximize - tablet - phone - landscape - portrait - + + + maximize + tablet + phone + landscape + portrait + diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-vi/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values-vi/strings.xml old mode 100644 new mode 100755 index 5c7e4091d..d837403e0 --- a/BFVersions/android/google_common/launcher/src/main/res/values-vi/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-vi/strings.xml @@ -1,4 +1,4 @@ - - - Kỵ Sĩ Liên Hoàn + + + Phiêu Lưu Sinh Tồn - VTC Game \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-zh-rTW/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values-zh-rTW/strings.xml old mode 100644 new mode 100755 index a954d4879..04003ca64 --- a/BFVersions/android/google_common/launcher/src/main/res/values-zh-rTW/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-zh-rTW/strings.xml @@ -1,4 +1,4 @@ - - - 賽賽軍團 + + + 抽抽抽英雄 \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values-zh/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values-zh/strings.xml old mode 100644 new mode 100755 index dff77460b..04003ca64 --- a/BFVersions/android/google_common/launcher/src/main/res/values-zh/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values-zh/strings.xml @@ -1,4 +1,4 @@ - - - 赛赛军团 + + + 抽抽抽英雄 \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values/freeformwindow.xml b/BFVersions/android/google_common/launcher/src/main/res/values/freeformwindow.xml old mode 100644 new mode 100755 index 16b407646..48ce31b15 --- a/BFVersions/android/google_common/launcher/src/main/res/values/freeformwindow.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values/freeformwindow.xml @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/BFVersions/android/google_common/launcher/src/main/res/values/ids.xml b/BFVersions/android/google_common/launcher/src/main/res/values/ids.xml old mode 100644 new mode 100755 index 2ba560aea..23ba93480 --- a/BFVersions/android/google_common/launcher/src/main/res/values/ids.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values/ids.xml @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/BFVersions/android/google_common/launcher/src/main/res/values/strings.xml b/BFVersions/android/google_common/launcher/src/main/res/values/strings.xml old mode 100644 new mode 100755 index 93570466f..567bcba20 --- a/BFVersions/android/google_common/launcher/src/main/res/values/strings.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - - - Knights Combo - Game view + + + Pull Pull Pull Heroes + Game view \ No newline at end of file diff --git a/BFVersions/android/google_common/launcher/src/main/res/values/styles.xml b/BFVersions/android/google_common/launcher/src/main/res/values/styles.xml old mode 100644 new mode 100755 index e334a5f94..01b930720 --- a/BFVersions/android/google_common/launcher/src/main/res/values/styles.xml +++ b/BFVersions/android/google_common/launcher/src/main/res/values/styles.xml @@ -1,12 +1,12 @@ - - - - - - + + + + + + diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/message/BFMessage.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/message/BFMessage.java old mode 100644 new mode 100755 index 740236a21..8758ce9b6 --- a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/message/BFMessage.java +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/message/BFMessage.java @@ -1,44 +1,44 @@ -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; - // 显示全屏广告成功 - public static final int ADMOB_SHOWED_FULLSCREEN = 17; - // 显示全屏广告失败 - public static final int ADMOB_FAILEDTO_SHOW_FULLSCREEN = 18; - // 拒绝显示全屏广告 - public static final int ADMOB_DISMISSED_FULLSCREEN = 19; - // 加载广告成功 - public static final int ADMOB_LOADED = 20; - // 加载广告失败 - public static final int ADMOB_LOADED_FAILED = 21; - // 获得奖励 - public static final int ADMOB_EARNED_REWARD = 22; - // 初始化状态 - public static final int ADMOB_INITIALIZED = 23; -} +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; + // 显示全屏广告成功 + public static final int ADMOB_SHOWED_FULLSCREEN = 17; + // 显示全屏广告失败 + public static final int ADMOB_FAILEDTO_SHOW_FULLSCREEN = 18; + // 拒绝显示全屏广告 + public static final int ADMOB_DISMISSED_FULLSCREEN = 19; + // 加载广告成功 + public static final int ADMOB_LOADED = 20; + // 加载广告失败 + public static final int ADMOB_LOADED_FAILED = 21; + // 获得奖励 + public static final int ADMOB_EARNED_REWARD = 22; + // 初始化状态 + public static final int ADMOB_INITIALIZED = 23; +} diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/BFFirebaseMessagingService.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/BFFirebaseMessagingService.java old mode 100644 new mode 100755 index dd3f054af..faf20ce07 --- a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/BFFirebaseMessagingService.java +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/BFFirebaseMessagingService.java @@ -1,50 +1,50 @@ -package com.juzu.dz.third; - -import android.util.Log; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.core.app.NotificationCompat; - -import com.google.android.gms.tasks.OnCompleteListener; -import com.google.android.gms.tasks.Task; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingService; -import com.google.firebase.messaging.RemoteMessage; - -public class BFFirebaseMessagingService extends FirebaseMessagingService { - - /** - * There are two scenarios when onNewToken is called: - * 1) When a new token is generated on initial app startup - * 2) Whenever an existing token is changed - * Under #2, there are three scenarios when the existing token is changed: - * A) App is restored to a new device - * B) User uninstalls/reinstalls the app - * C) User clears app data - */ - - @Override - public void onMessageReceived(RemoteMessage remoteMessage) { - Log.d("TAG", "From: " + remoteMessage.getFrom()); - // Check if message contains a data payload. - if (remoteMessage.getData().size() > 0) { - Log.d("TAG", "Message data payload: " + remoteMessage.getData()); - } - - // Check if message contains a notification payload. - if (remoteMessage.getNotification() != null) { - Log.d("TAG", "Message Notification Body: " + remoteMessage.getNotification().getBody()); - } - } - - @Override - public void onNewToken(String token) { - Log.d("", "Refreshed token: " + token); - sendRegistrationToServer(token); - } - - private void sendRegistrationToServer(String token) { - // TODO: Implement this method to send token to your app server. - } -} +package com.juzu.dz.third; + +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; + +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +public class BFFirebaseMessagingService extends FirebaseMessagingService { + + /** + * There are two scenarios when onNewToken is called: + * 1) When a new token is generated on initial app startup + * 2) Whenever an existing token is changed + * Under #2, there are three scenarios when the existing token is changed: + * A) App is restored to a new device + * B) User uninstalls/reinstalls the app + * C) User clears app data + */ + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + Log.d("TAG", "From: " + remoteMessage.getFrom()); + // Check if message contains a data payload. + if (remoteMessage.getData().size() > 0) { + Log.d("TAG", "Message data payload: " + remoteMessage.getData()); + } + + // Check if message contains a notification payload. + if (remoteMessage.getNotification() != null) { + Log.d("TAG", "Message Notification Body: " + remoteMessage.getNotification().getBody()); + } + } + + @Override + public void onNewToken(String token) { + Log.d("", "Refreshed token: " + token); + sendRegistrationToServer(token); + } + + private void sendRegistrationToServer(String token) { + // TODO: Implement this method to send token to your app server. + } +} diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleAdmobRewardedVideo.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleAdmobRewardedVideo.java old mode 100644 new mode 100755 index 04abf3bbe..f5012eab6 --- a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleAdmobRewardedVideo.java +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleAdmobRewardedVideo.java @@ -1,291 +1,302 @@ -// package com.juzu.dz.third; - -// import android.app.Activity; -// import android.content.Intent; -// import android.os.Bundle; -// import android.util.Log; -// import androidx.annotation.NonNull; - -// import com.google.android.gms.ads.AdError; -// import com.google.android.gms.ads.AdRequest; -// import com.google.android.gms.ads.AdValue; -// import com.google.android.gms.ads.AdapterResponseInfo; -// import com.google.android.gms.ads.FullScreenContentCallback; -// import com.google.android.gms.ads.LoadAdError; -// import com.google.android.gms.ads.MobileAds; -// import com.google.android.gms.ads.OnPaidEventListener; -// import com.google.android.gms.ads.OnUserEarnedRewardListener; -// import com.google.android.gms.ads.ResponseInfo; -// import com.google.android.gms.ads.initialization.AdapterStatus; -// import com.google.android.gms.ads.initialization.InitializationStatus; -// import com.google.android.gms.ads.initialization.OnInitializationCompleteListener; -// import com.google.android.gms.ads.rewarded.RewardItem; -// import com.google.android.gms.ads.rewarded.RewardedAd; -// import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback; -// import com.juzu.dz.message.BFMessage; - -// import java.util.Map; - -// public class GoogleAdmobRewardedVideo { -// private final String AD_UNIT_ID = "ca-app-pub-1136292565368915/9438857158"; -// private final String LOG_TAG = "RewardedVideo"; -// private static volatile GoogleAdmobRewardedVideo sInstance; -// private RewardedAd rewardedAd; -// private String adPlacement = ""; - -// private boolean isLoading = false; - -// public static GoogleAdmobRewardedVideo getInstance() -// { -// if (sInstance == null) { -// synchronized (GoogleLogin.class) { -// if (sInstance == null) { -// sInstance = new GoogleAdmobRewardedVideo(); -// } -// } -// } -// return sInstance; -// } - -// public void init(Activity activity){ -// Bundle bundle = new Bundle(); -// bundle.putString("flow_seq", "01"); -// GooglePlugin.logEventBundle("admob_sdk_init_start", bundle); -// MobileAds.initialize(activity, new OnInitializationCompleteListener() { -// @Override -// public void onInitializationComplete(InitializationStatus initializationStatus) { -// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_INITIALIZED, ""); - -// Bundle bundle = new Bundle(); -// bundle.putString("flow_seq", "02"); -// GooglePlugin.logEventBundle("admob_sdk_init_complete", bundle); -// // loadRewardedAd(activity); -// } -// }); - -// loadRewardedAd(activity); -// } - -// private void pauseGame() { -// } - -// private void resumeGame() { -// } - -// private void loadRewardedAd(Activity activity) { -// if (isLoading) -// { -// return; -// } -// if (rewardedAd == null) { -// isLoading = true; -// Bundle bundle = new Bundle(); -// bundle.putString("flow_seq", "03"); -// bundle.putString("ad_placement", adPlacement); -// GooglePlugin.logEventBundle("admob_ad_request", bundle); -// AdRequest adRequest = new AdRequest.Builder().build(); -// activity.runOnUiThread(new Runnable() { -// public void run() { -// RewardedAd.load( -// activity, -// AD_UNIT_ID, -// adRequest, -// new RewardedAdLoadCallback() { -// @Override -// public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { -// // Handle the error. -// Log.w(LOG_TAG, "loadAdError:" + loadAdError.getMessage()); -// rewardedAd = null; -// isLoading = false; -// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_LOADED_FAILED, ""); -// // Toast.makeText(_activity.this, "onAdFailedToLoad", Toast.LENGTH_SHORT).show(); - -// Bundle bundle = new Bundle(); -// bundle.putString("flow_seq", "05"); -// bundle.putString("ad_placement", adPlacement); -// bundle.putString("error_code", "" + loadAdError.getCode()); -// GooglePlugin.logEventBundle("admob_ad_load_fail", bundle); -// } - -// @Override -// public void onAdLoaded(@NonNull RewardedAd newRewardedAd) { -// Log.w(LOG_TAG, "onAdLoaded"); -// rewardedAd = newRewardedAd; -// isLoading = false; -// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_LOADED, ""); - -// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); -// if (responseInfo != null) -// { -// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); -// if (loadedAdapterResponseInfo != null) -// { -// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); -// Bundle bundle = new Bundle(); -// bundle.putString("flow_seq", "04"); -// bundle.putString("ad_placement", adPlacement); -// bundle.putString("ad_source", adSourceName); -// bundle.putString("ad_response_id", responseInfo.getResponseId()); -// GooglePlugin.logEventBundle("admob_ad_load_success", bundle); -// } -// } - -// // Toast.makeText(_activity.this, "onAdLoaded", Toast.LENGTH_SHORT).show(); -// } -// }); -// } -// }); -// } -// } - -// private void showRewardedVideo(Activity activity) { -// if (rewardedAd == null) { -// Log.w(LOG_TAG, "The rewarded ad wasn't ready yet."); -// return; -// } - -// activity.runOnUiThread(new Runnable() { -// public void run() { -// rewardedAd.setOnPaidEventListener( -// new OnPaidEventListener() { -// @Override -// public void onPaidEvent(@NonNull AdValue adValue) { -// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); -// if (responseInfo != null) -// { -// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); -// if (loadedAdapterResponseInfo != null) -// { -// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); -// Bundle bundle = new Bundle(); -// bundle.putString("flow_seq", "10"); -// bundle.putString("ad_placement", adPlacement); -// bundle.putString("ad_source", adSourceName); -// bundle.putDouble("ad_value", adValue.getValueMicros()); -// bundle.putString("ad_response_id", responseInfo.getResponseId()); -// GooglePlugin.logEventBundle("admob_ad_open_success", bundle); -// } -// } -// } -// } -// ); -// rewardedAd.setFullScreenContentCallback( -// new FullScreenContentCallback() { -// @Override -// public void onAdShowedFullScreenContent() { -// // Called when ad is shown. -// Log.w(LOG_TAG, "onAdShowedFullScreenContent"); -// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_SHOWED_FULLSCREEN, ""); -// } - -// @Override -// public void onAdFailedToShowFullScreenContent(AdError adError) { -// // Called when ad fails to show. -// Log.w(LOG_TAG, "onAdFailedToShowFullScreenContent"); -// // Don't forget to set the ad reference to null so you -// // don't show the ad a second time. - -// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); -// if (responseInfo != null) -// { -// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); -// if (loadedAdapterResponseInfo != null) -// { -// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); - -// Bundle bundle = new Bundle(); -// bundle.putString("flow_seq", "11"); -// bundle.putString("ad_placement", adPlacement); -// bundle.putString("ad_source", adSourceName); -// bundle.putString("ad_response_id", responseInfo.getResponseId()); -// bundle.putString("error_code", "" + adError.getCode()); -// GooglePlugin.logEventBundle("admob_ad_open_fail", bundle); -// } -// } - -// rewardedAd = null; -// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_FAILEDTO_SHOW_FULLSCREEN, ""); -// adPlacement = ""; -// } - -// @Override -// public void onAdDismissedFullScreenContent() { -// // Called when ad is dismissed. -// // Don't forget to set the ad reference to null so you -// // don't show the ad a second time. - -// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); -// if (responseInfo != null) -// { -// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); -// if (loadedAdapterResponseInfo != null) -// { -// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); - -// Bundle bundle = new Bundle(); -// bundle.putString("flow_seq", "12"); -// bundle.putString("ad_placement", adPlacement); -// bundle.putString("ad_source", adSourceName); -// bundle.putString("ad_response_id", responseInfo.getResponseId()); -// GooglePlugin.logEventBundle("admob_ad_close", bundle); -// } -// } - -// rewardedAd = null; -// Log.w(LOG_TAG, "onAdDismissedFullScreenContent"); -// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_DISMISSED_FULLSCREEN, ""); -// } -// }); -// Activity activityContext = activity; -// rewardedAd.show( -// activityContext, -// new OnUserEarnedRewardListener() { -// @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); -// // JSONObject json = new JSONObject(); -// // json.put("status", 0); -// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_EARNED_REWARD, ""); - -// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); -// if (responseInfo != null) -// { -// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); -// if (loadedAdapterResponseInfo != null) -// { -// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); - -// Bundle bundle = new Bundle(); -// bundle.putString("flow_seq", "13"); -// bundle.putString("ad_placement", adPlacement); -// bundle.putString("ad_source", adSourceName); -// bundle.putString("ad_response_id", responseInfo.getResponseId()); -// GooglePlugin.logEventBundle("admob_reward_earn", bundle); -// } -// } -// adPlacement = ""; -// } -// }); -// } -// }); -// } - -// public void showFullScreenAds(Activity activity) { -// showRewardedVideo(activity); -// } - -// public void tryLoadRewardedAd(Activity activity) -// { -// loadRewardedAd(activity); -// } - -// public void setAdPlacement(String placement) -// { -// adPlacement = placement; -// } +// package com.juzu.dz.third; + +// import android.app.Activity; +// import android.content.Intent; +// import android.os.Bundle; +// import android.util.Log; +// import androidx.annotation.NonNull; + +// import com.google.android.gms.ads.AdError; +// import com.google.android.gms.ads.AdRequest; +// import com.google.android.gms.ads.AdValue; +// import com.google.android.gms.ads.AdapterResponseInfo; +// import com.google.android.gms.ads.FullScreenContentCallback; +// import com.google.android.gms.ads.LoadAdError; +// import com.google.android.gms.ads.MobileAds; +// import com.google.android.gms.ads.OnPaidEventListener; +// import com.google.android.gms.ads.OnUserEarnedRewardListener; +// import com.google.android.gms.ads.ResponseInfo; +// import com.google.android.gms.ads.initialization.AdapterStatus; +// import com.google.android.gms.ads.initialization.InitializationStatus; +// import com.google.android.gms.ads.initialization.OnInitializationCompleteListener; +// import com.google.android.gms.ads.rewarded.RewardItem; +// import com.google.android.gms.ads.rewarded.RewardedAd; +// import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback; +// import com.juzu.dz.message.BFMessage; + +// import java.util.Map; + +// public class GoogleAdmobRewardedVideo { +// // 这是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 = ""; + +// boolean isLoading; + +// public static GoogleAdmobRewardedVideo getInstance() +// { +// if (sInstance == null) { +// synchronized (GoogleLogin.class) { +// if (sInstance == null) { +// sInstance = new GoogleAdmobRewardedVideo(); +// } +// } +// } +// return sInstance; +// } + +// public void init(Activity activity){ +// Bundle bundle = new Bundle(); +// bundle.putString("flow_seq", "01"); +// GooglePlugin.logEventBundle("admob_sdk_init_start", bundle); +// MobileAds.initialize(activity, new OnInitializationCompleteListener() { +// @Override +// public void onInitializationComplete(InitializationStatus initializationStatus) { +// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_INITIALIZED, ""); + +// Bundle bundle = new Bundle(); +// bundle.putString("flow_seq", "02"); +// GooglePlugin.logEventBundle("admob_sdk_init_complete", bundle); +// // loadRewardedAd(activity); +// } +// }); + +// loadRewardedAd(activity); + +// // startGame(activity); +// } + +// private void pauseGame() { +// } + +// private void resumeGame() { +// } + +// private void loadRewardedAd(Activity activity) { +// if (rewardedAd == null) { +// isLoading = true; +// Bundle bundle = new Bundle(); +// bundle.putString("flow_seq", "03"); +// bundle.putString("ad_placement", adPlacement); +// GooglePlugin.logEventBundle("admob_ad_request", bundle); +// AdRequest adRequest = new AdRequest.Builder().build(); +// activity.runOnUiThread(new Runnable() { +// public void run() { +// RewardedAd.load( +// activity, +// AD_UNIT_ID, +// adRequest, +// new RewardedAdLoadCallback() { +// @Override +// public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { +// // Handle the error. +// Log.w("RewardedVideo", loadAdError.getMessage()); +// rewardedAd = null; +// isLoading = false; +// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_LOADED_FAILED, ""); +// // Toast.makeText(_activity.this, "onAdFailedToLoad", Toast.LENGTH_SHORT).show(); + +// Bundle bundle = new Bundle(); +// bundle.putString("flow_seq", "05"); +// bundle.putString("ad_placement", adPlacement); +// bundle.putString("error_code", "" + loadAdError.getCode()); +// GooglePlugin.logEventBundle("admob_ad_load_fail", bundle); +// } + +// @Override +// public void onAdLoaded(@NonNull RewardedAd newRewardedAd) { +// Log.w("RewardedVideo", "onAdLoaded"); +// rewardedAd = newRewardedAd; +// isLoading = false; +// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_LOADED, ""); + +// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); +// if (responseInfo != null) +// { +// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); +// if (loadedAdapterResponseInfo != null) +// { +// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); +// Bundle bundle = new Bundle(); +// bundle.putString("flow_seq", "04"); +// bundle.putString("ad_placement", adPlacement); +// bundle.putString("ad_source", adSourceName); +// bundle.putString("ad_response_id", responseInfo.getResponseId()); +// GooglePlugin.logEventBundle("admob_ad_load_success", bundle); +// } +// } + +// // Toast.makeText(_activity.this, "onAdLoaded", Toast.LENGTH_SHORT).show(); +// } +// }); +// } +// }); +// } +// } + +// // 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("TAG", "The rewarded ad wasn't ready yet."); +// return; +// } + +// activity.runOnUiThread(new Runnable() { +// public void run() { +// rewardedAd.setOnPaidEventListener( +// new OnPaidEventListener() { +// @Override +// public void onPaidEvent(@NonNull AdValue adValue) { +// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); +// if (responseInfo != null) +// { +// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); +// if (loadedAdapterResponseInfo != null) +// { +// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); +// Bundle bundle = new Bundle(); +// bundle.putString("flow_seq", "10"); +// bundle.putString("ad_placement", adPlacement); +// bundle.putString("ad_source", adSourceName); +// bundle.putDouble("ad_value", adValue.getValueMicros()); +// bundle.putString("ad_response_id", responseInfo.getResponseId()); +// GooglePlugin.logEventBundle("admob_ad_open_success", bundle); +// } +// } +// } +// } +// ); +// rewardedAd.setFullScreenContentCallback( +// new FullScreenContentCallback() { +// @Override +// public void onAdShowedFullScreenContent() { +// // Called when ad is shown. +// Log.w(LOG_TAG, "onAdShowedFullScreenContent"); +// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_SHOWED_FULLSCREEN, ""); +// } + +// @Override +// public void onAdFailedToShowFullScreenContent(AdError adError) { +// // Called when ad fails to show. +// Log.w(LOG_TAG, "onAdFailedToShowFullScreenContent"); +// // Don't forget to set the ad reference to null so you +// // don't show the ad a second time. + +// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); +// if (responseInfo != null) +// { +// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); +// if (loadedAdapterResponseInfo != null) +// { +// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); + +// Bundle bundle = new Bundle(); +// bundle.putString("flow_seq", "11"); +// bundle.putString("ad_placement", adPlacement); +// bundle.putString("ad_source", adSourceName); +// bundle.putString("ad_response_id", responseInfo.getResponseId()); +// bundle.putString("error_code", "" + adError.getCode()); +// GooglePlugin.logEventBundle("admob_ad_open_fail", bundle); +// } +// } + +// rewardedAd = null; +// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_FAILEDTO_SHOW_FULLSCREEN, ""); +// adPlacement = ""; +// } + +// @Override +// public void onAdDismissedFullScreenContent() { +// // Called when ad is dismissed. +// // Don't forget to set the ad reference to null so you +// // don't show the ad a second time. + +// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); +// if (responseInfo != null) +// { +// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); +// if (loadedAdapterResponseInfo != null) +// { +// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); + +// Bundle bundle = new Bundle(); +// bundle.putString("flow_seq", "12"); +// bundle.putString("ad_placement", adPlacement); +// bundle.putString("ad_source", adSourceName); +// bundle.putString("ad_response_id", responseInfo.getResponseId()); +// GooglePlugin.logEventBundle("admob_ad_close", bundle); +// } +// } + +// rewardedAd = null; +// Log.w(LOG_TAG, "onAdDismissedFullScreenContent"); +// GooglePlugin.sendMessageToUnity(BFMessage.ADMOB_DISMISSED_FULLSCREEN, ""); +// } +// }); +// Activity activityContext = activity; +// rewardedAd.show( +// activityContext, +// new OnUserEarnedRewardListener() { +// @Override +// public void onUserEarnedReward(@NonNull RewardItem rewardItem) { +// // Handle the reward. +// 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, ""); + +// ResponseInfo responseInfo = rewardedAd.getResponseInfo(); +// if (responseInfo != null) +// { +// AdapterResponseInfo loadedAdapterResponseInfo = responseInfo.getLoadedAdapterResponseInfo(); +// if (loadedAdapterResponseInfo != null) +// { +// String adSourceName = loadedAdapterResponseInfo.getAdSourceName(); + +// Bundle bundle = new Bundle(); +// bundle.putString("flow_seq", "13"); +// bundle.putString("ad_placement", adPlacement); +// bundle.putString("ad_source", adSourceName); +// bundle.putString("ad_response_id", responseInfo.getResponseId()); +// GooglePlugin.logEventBundle("admob_reward_earn", bundle); +// } +// } +// adPlacement = ""; +// } +// }); +// } +// }); +// } + +// public void showFullScreenAds(Activity activity) { +// Log.w(LOG_TAG, "showFullScreenAds"); +// showRewardedVideo(activity); +// } + +// public void tryLoadRewardedAd(Activity activity) +// { +// loadRewardedAd(activity); +// } + +// public void setAdPlacement(String placement) +// { +// adPlacement = placement; +// } // } \ No newline at end of file diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleBilling.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleBilling.java old mode 100644 new mode 100755 index 7f31841b3..d0fc1ee99 --- a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleBilling.java +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleBilling.java @@ -1,459 +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 mPurchase = new HashMap(); - private List mSubList = new ArrayList(); - private List mInAppList = new ArrayList(); - final private static String BASE_64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq3fn02CUbrnNkF1PYXk1XHuJdJsnUMsUHXaYR83+ZPE2a3NgGHpVsrVakZK28RARQSz8E2x8qy+4bFPji1TTLq+MRY9CAcGzP4HB5eGKhRmsHABJNnfWgMiXxZ/mmW/yQawTY0zmeDX6Z/GSN3SeZo0PmlpM7ZFJpN42vYO6Fs5zgR05SAUDx3uaPwkhZ0Z2bIIhbWaVyxoMy2pDnCfCL5ym1nwdDa8tRMhZ1yWaDdY4KkJ92W0kbyMntjtL9QWFCGcRTGHVEoir4E2nc2bugaOkw0qegng00L65qWnfZ1GFgPW3uNexWsbEc2A5g//bL2/+yDVQWqiJtkdQMKyIKwIDAQAB"; - - 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 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 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 mList = new ArrayList(); - 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 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 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 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 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 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 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; - } - } -} +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 mPurchase = new HashMap(); + private List mSubList = new ArrayList(); + private List mInAppList = new ArrayList(); + final private static String BASE_64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtIC/9macCZdKcRamY6XOf+J+ncHwUzrRx18696PPH0kixqIAovJKAISUeZCH28NTBb/t4dwgnPRMjNsviH71BVDOK7ZSm2xkbhi4aQ8lB5afGso9nj0+RaaONQeHMW17sIkqd2DIjiMWlPwt65pdonwXZ4NqQJwNabXMhzHggI9fjHH9mJodqqrKkwwIQtFyDe6OYC9kv6DIHhsVVDTfwKbq5QLJEHnsJKJbhuemI0aN6qwSf8tUxLWRJN+P6g88+tfRo5mI7A+slLdcom8Yf+OSQoXGPQJ6xplrfABTB1V7JymfiZg7JHLql65SwG6zk/GFAD0VcIWTfeogvyElkQIDAQAB"; + + 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 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 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 mList = new ArrayList(); + 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 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 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 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 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 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 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; + } + } +} diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleLogin.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleLogin.java old mode 100644 new mode 100755 index 95f5b6fbc..e8ba5da9b --- a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleLogin.java +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GoogleLogin.java @@ -1,97 +1,98 @@ -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 = "1008416471093-e47s7u8a7v31ulr2f7e9j1mdm9llepum.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) - .requestEmail() - .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() { - @Override - public void onComplete(@NonNull Task task) { - GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_LOGOUT_SUCCESS, ""); - } - }); - } - - public void onActivityResult(int requestCode, int resultCode, Intent data) - { - if (requestCode == REQUEST_CODE_GOOGLE_SIGN_IN) { - Task task = GoogleSignIn.getSignedInAccountFromIntent(data); - handleSignInResult(task); - } - } - - private void handleSignInResult(Task 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(); - } -} +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 = "22951947163-20850c0vsdknh7ts3nai4bs6j85dj5gl.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) + .requestEmail() + .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() { + @Override + public void onComplete(@NonNull Task task) { + GooglePlugin.sendMessageToUnity(BFMessage.GOOGLE_LOGOUT_SUCCESS, ""); + } + }); + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + if (requestCode == REQUEST_CODE_GOOGLE_SIGN_IN) { + Task task = GoogleSignIn.getSignedInAccountFromIntent(data); + handleSignInResult(task); + } + } + + private void handleSignInResult(Task 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(); + } +} diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GooglePlugin.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GooglePlugin.java old mode 100644 new mode 100755 index bb0373359..8e86a4353 --- a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GooglePlugin.java +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/GooglePlugin.java @@ -1,299 +1,499 @@ -package com.juzu.dz.third; - -import android.app.Activity; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -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.google.firebase.analytics.FirebaseAnalytics; -// import com.google.firebase.crashlytics.FirebaseCrashlytics; -import com.juzu.dz.message.BFMessage; -import com.unity3d.player.UnityPlayer; - -import java.util.Iterator; -import java.util.List; - -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 GoogleAdmobRewardedVideo mGoogleAdmobRewardedVideo = null; - private static Activity _activity; - public static FirebaseAnalytics mFirebaseAnalytics; - // private FirebaseCrashlytics mFirebaseCrashlytics; - - public static void init(Activity activity){ - _activity = activity; - mFirebaseAnalytics = FirebaseAnalytics.getInstance(_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 initAdmobRewardedVideo() - { - Log.d("RewardedVideo", "android initAdmobRewardedVideo"); - // if (mGoogleAdmobRewardedVideo == null) - // { - // mGoogleAdmobRewardedVideo = GoogleAdmobRewardedVideo.getInstance(); - // mGoogleAdmobRewardedVideo.init(_activity); - // } - } - - // 登录 - public static void login() - { - if (mGoogleLogin != null){ - UnityPlayer.currentActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mGoogleLogin.login(_activity); - } - }); - }else{ - sendMessageToUnity(BFMessage.GOOGLE_LOGIN_FAILED, "google login failed: not init"); - } - } - - // 登出 - public static void logout() - { - if (mGoogleLogin != null){ - UnityPlayer.currentActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mGoogleLogin.logout(_activity); - } - }); - }else{ - sendMessageToUnity(BFMessage.GOOGLE_LOGOUT_FAILED, ""); - } - } - - public static void pay(final String payType, final String productId, final String customMsg) - { - - if (mGoogleBilling != null){ - UnityPlayer.currentActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mGoogleBilling.pay(_activity, payType, productId, customMsg); - } - }); - }else{ - sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, "billing not init"); - } - } - - public static void queryProductsInfo(final String payType, final String productJson) - { - if (mGoogleBilling != null){ - UnityPlayer.currentActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mGoogleBilling.queryProductsInfo(payType, productJson); - } - }); - }else{ - sendMessageToUnity(BFMessage.QUERY_PRODUCT_FAILED, "billing not init"); - } - } - - public static void consumeAsync(final String purchaseToken) - { - if (mGoogleBilling != null){ - UnityPlayer.currentActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mGoogleBilling.consumeAsync(purchaseToken); - } - }); - }else{ - sendMessageToUnity(BFMessage.GOOGLE_CONSUME_FAILED, "billing not init"); - } - } - - public static void connectGoogleStore() - { - if (mGoogleBilling != null){ - UnityPlayer.currentActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mGoogleBilling.connectGoogleStore(); - } - }); - }else{ - sendMessageToUnity(BFMessage.GOOGLE_CONNECT_FAILED, "billing not init"); - } - } - - public static void queryUncompleteOrder(final String payType) - { - if (mGoogleBilling != null){ - UnityPlayer.currentActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mGoogleBilling.queryUncompleteOrder(payType); - } - }); - }else{ - sendMessageToUnity(BFMessage.QUERY_UNCOMPLETE_ORDER_FINISH, ""); - } - } - - public static void querySubscribeOrder(final String payType) - { - if (mGoogleBilling != null){ - UnityPlayer.currentActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - 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() - { - FirebaseMessaging.getInstance().getToken().addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (!task.isSuccessful()) { - Log.w(LOG_TAG, "Fetching FCM registration token failed", task.getException()); - return; - } - - // Get new FCM registration token - String token = task.getResult(); - GooglePlugin.sendMessageToUnity(BFMessage.FIREBASE_TOKEN, token); - } - }); - } - - public static void tryLoadRewardedAd() - { - Log.w("", "tryLoadRewardedAd"); - // if (mGoogleAdmobRewardedVideo != null) - // { - // mGoogleAdmobRewardedVideo.tryLoadRewardedAd(_activity); - // } - } - - public static void showFullScreenAds() - { - Log.w("", "showFullScreenAds"); - // if (mGoogleAdmobRewardedVideo != null) - // { - // mGoogleAdmobRewardedVideo.showFullScreenAds(_activity); - // } - } - - // log event - public static void logEvent(String eventName, String data) throws JSONException { - Log.w("", "logEvent"); - if (mFirebaseAnalytics == null) { - return; - } - JSONObject properties = new JSONObject(data); - Bundle bundle = new Bundle(); - Iterator iter = properties.keys(); - while (iter.hasNext()) { - String key = (String) iter.next(); - Object value = properties.get(key); - setBundleValue(bundle, key, value); - } - mFirebaseAnalytics.logEvent(eventName, bundle); - } - - public static void logEventBundle(String eventName, Bundle bundle){ - Log.w("", "logEvent"); - if (mFirebaseAnalytics == null) { - return; - } - mFirebaseAnalytics.logEvent(eventName, bundle); - } - - // log crash - public static void logCrash(String key, String stack) { - // this.mFirebaseCrashlytics = FirebaseCrashlytics.getInstance(); - // if (this.mFirebaseCrashlytics == null) - // return; - // this.mFirebaseCrashlytics.setCustomKey(key, stack); - } - - // set bundle to type - private static void setBundleValue(Bundle bundle, String key, Object value) { - if (bundle == null) - return; - - if (value instanceof Double) - bundle.putDouble(key, (Double) value); - else if (value instanceof Float) - bundle.putFloat(key, (Float)value); - else if (value instanceof Integer) - bundle.putInt(key, (Integer)value); - else if (value instanceof Long) - bundle.putLong(key, (Long) value); - else if (value instanceof Boolean) - bundle.putBoolean(key, (Boolean) value); - else - bundle.putString(key, (String) value); - } - - 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()); - } - - public static void setAdPlacement(String placement){ - // if (mGoogleAdmobRewardedVideo != null) - // { - // mGoogleAdmobRewardedVideo.setAdPlacement(placement); - // } - } +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.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; + private static GoogleBilling mGoogleBilling = null; + // private static GoogleAdmobRewardedVideo mGoogleAdmobRewardedVideo = null; + private static Activity _activity; + 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); + } + + 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 initAdmobRewardedVideo() + { + Log.d("RewardedVideo", "android initAdmobRewardedVideo"); + // if (mGoogleAdmobRewardedVideo == null) + // { + // mGoogleAdmobRewardedVideo = GoogleAdmobRewardedVideo.getInstance(); + // mGoogleAdmobRewardedVideo.init(_activity); + // } + } + + public static void initGDPR() + { + initConsentInformation(_activity); + } + + // 登录 + public static void login() + { + if (mGoogleLogin != null){ + UnityPlayer.currentActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mGoogleLogin.login(_activity); + } + }); + }else{ + sendMessageToUnity(BFMessage.GOOGLE_LOGIN_FAILED, "google login failed: not init"); + } + } + + // 登出 + public static void logout() + { + if (mGoogleLogin != null){ + UnityPlayer.currentActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mGoogleLogin.logout(_activity); + } + }); + }else{ + sendMessageToUnity(BFMessage.GOOGLE_LOGOUT_FAILED, ""); + } + } + + public static void pay(final String payType, final String productId, final String customMsg) + { + + if (mGoogleBilling != null){ + UnityPlayer.currentActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mGoogleBilling.pay(_activity, payType, productId, customMsg); + } + }); + }else{ + sendMessageToUnity(BFMessage.GOOGLE_PAY_FAILED, "billing not init"); + } + } + + public static void queryProductsInfo(final String payType, final String productJson) + { + if (mGoogleBilling != null){ + UnityPlayer.currentActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mGoogleBilling.queryProductsInfo(payType, productJson); + } + }); + }else{ + sendMessageToUnity(BFMessage.QUERY_PRODUCT_FAILED, "billing not init"); + } + } + + public static void consumeAsync(final String purchaseToken) + { + if (mGoogleBilling != null){ + UnityPlayer.currentActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mGoogleBilling.consumeAsync(purchaseToken); + } + }); + }else{ + sendMessageToUnity(BFMessage.GOOGLE_CONSUME_FAILED, "billing not init"); + } + } + + public static void connectGoogleStore() + { + if (mGoogleBilling != null){ + UnityPlayer.currentActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mGoogleBilling.connectGoogleStore(); + } + }); + }else{ + sendMessageToUnity(BFMessage.GOOGLE_CONNECT_FAILED, "billing not init"); + } + } + + public static void queryUncompleteOrder(final String payType) + { + if (mGoogleBilling != null){ + UnityPlayer.currentActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mGoogleBilling.queryUncompleteOrder(payType); + } + }); + }else{ + sendMessageToUnity(BFMessage.QUERY_UNCOMPLETE_ORDER_FINISH, ""); + } + } + + public static void querySubscribeOrder(final String payType) + { + if (mGoogleBilling != null){ + UnityPlayer.currentActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + 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() + { + FirebaseMessaging.getInstance().getToken().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (!task.isSuccessful()) { + Log.w(TAG, "Fetching FCM registration token failed", task.getException()); + return; + } + + // Get new FCM registration token + String token = task.getResult(); + GooglePlugin.sendMessageToUnity(BFMessage.FIREBASE_TOKEN, token); + } + }); + } + + public static void tryLoadRewardedAd() + { + Log.w("", "tryLoadRewardedAd"); + // if (mGoogleAdmobRewardedVideo != null) + // { + // mGoogleAdmobRewardedVideo.tryLoadRewardedAd(_activity); + // } + } + + public static void showFullScreenAds() + { + Log.w("", "showFullScreenAds"); + // if (mGoogleAdmobRewardedVideo != null) + // { + // mGoogleAdmobRewardedVideo.showFullScreenAds(_activity); + // } + } + + // log event + public static void logEvent(String eventName, String data) throws JSONException { + Log.w("", "logEvent"); + if (mFirebaseAnalytics == null) { + return; + } + JSONObject properties = new JSONObject(data); + Bundle bundle = new Bundle(); + Iterator iter = properties.keys(); + while (iter.hasNext()) { + String key = (String) iter.next(); + Object value = properties.get(key); + setBundleValue(bundle, key, value); + } + mFirebaseAnalytics.logEvent(eventName, bundle); + } + + public static void logEventBundle(String eventName, Bundle bundle){ + Log.w("", "logEvent"); + if (mFirebaseAnalytics == null) { + return; + } + mFirebaseAnalytics.logEvent(eventName, bundle); + } + + // log crash + public static void logCrash(String key, String stack) { +// this.mFirebaseCrashlytics = FirebaseCrashlytics.getInstance(); +// if (this.mFirebaseCrashlytics == null) +// return; +// this.mFirebaseCrashlytics.setCustomKey(key, stack); + } + + // set bundle to type + private static void setBundleValue(Bundle bundle, String key, Object value) { + if (bundle == null) + return; + + if (value instanceof Double) + bundle.putDouble(key, (Double) value); + else if (value instanceof Float) + bundle.putFloat(key, (Float)value); + else if (value instanceof Integer) + bundle.putInt(key, (Integer)value); + else if (value instanceof Long) + bundle.putLong(key, (Long) value); + else if (value instanceof Boolean) + bundle.putBoolean(key, (Boolean) value); + else + bundle.putString(key, (String) value); + } + + 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()); + } + + public static void setAdPlacement(String placement){ + // if (mGoogleAdmobRewardedVideo != null) + // { + // 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 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 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); + } + }); + } + } \ No newline at end of file diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/Security.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/Security.java old mode 100644 new mode 100755 index 8ab8ca595..1dd7fa7d6 --- a/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/Security.java +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/juzu/dz/third/Security.java @@ -1,136 +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. - *

- * 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; - } -} +/* + * 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. + *

+ * 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; + } +} diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationBackgroundThread.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationBackgroundThread.java new file mode 100755 index 000000000..4ad6e4315 --- /dev/null +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationBackgroundThread.java @@ -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 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 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 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 notifications) { + if (notifications.isEmpty()) + return false; + + Enumeration 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 notifications) { + HashSet notificationIds = new HashSet<>(); + Enumeration 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 mTasks = new LinkedTransferQueue(); + private ConcurrentHashMap mScheduledNotifications; + private UnityNotificationManager mManager; + private int mTasksSinceHousekeeping = TASKS_FOR_HOUSEKEEPING; // we want hoursekeeping at the start + + public UnityNotificationBackgroundThread(UnityNotificationManager manager, ConcurrentHashMap 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 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 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 notifications = mManager.loadSavedNotifications(); + for (Notification.Builder builder : notifications) { + int id = builder.getExtras().getInt(KEY_ID, -1); + mScheduledNotifications.put(id, builder); + } + } +} diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationManager.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationManager.java new file mode 100755 index 000000000..966ca46a1 --- /dev/null +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationManager.java @@ -0,0 +1,1124 @@ +package com.unity.androidnotifications; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.service.notification.StatusBarNotification; +import android.util.Log; + +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; +import static android.app.Notification.VISIBILITY_PUBLIC; + +import java.io.InputStream; +import java.lang.Integer; +import java.util.Calendar; +import java.util.Random; +import java.util.Set; +import java.util.HashSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import com.unity3d.player.UnityPlayer; + +public class UnityNotificationManager extends BroadcastReceiver { + static UnityNotificationManager mUnityNotificationManager; + + private Context mContext = null; + private Activity mActivity = null; + private Class mOpenActivity = null; + private UnityNotificationBackgroundThread mBackgroundThread; + private Random mRandom; + private HashSet mVisibleNotifications; + private ConcurrentHashMap mScheduledNotifications; + private NotificationCallback mNotificationCallback; + private int mExactSchedulingSetting = -1; + + private static final int PERMISSION_STATUS_ALLOWED = 1; + private static final int PERMISSION_STATUS_DENIED = 2; + private static final int PERMISSION_STATUS_NOTIFICATIONS_BLOCKED_FOR_APP = 5; + static final String TAG_UNITY = "UnityNotifications"; + + public static final String KEY_FIRE_TIME = "fireTime"; + public static final String KEY_ID = "id"; + public static final String KEY_INTENT_DATA = "data"; + public static final String KEY_LARGE_ICON = "largeIcon"; + public static final String KEY_REPEAT_INTERVAL = "repeatInterval"; + public static final String KEY_NOTIFICATION = "unityNotification"; + public static final String KEY_NOTIFICATION_ID = "com.unity.NotificationID"; + public static final String KEY_SMALL_ICON = "smallIcon"; + public static final String KEY_CHANNEL_ID = "channelID"; + public static final String KEY_SHOW_IN_FOREGROUND = "com.unity.showInForeground"; + public static final String KEY_NOTIFICATION_DISMISSED = "com.unity.NotificationDismissed"; + public static final String KEY_BIG_LARGE_ICON = "com.unity.BigLargeIcon"; + public static final String KEY_BIG_PICTURE = "com.unity.BigPicture"; + public static final String KEY_BIG_CONTENT_TITLE = "com.unity.BigContentTytle"; + public static final String KEY_BIG_SUMMARY_TEXT = "com.unity.BigSummaryText"; + public static final String KEY_BIG_CONTENT_DESCRIPTION = "com.unity.BigContentDescription"; + public static final String KEY_BIG_SHOW_WHEN_COLLAPSED = "com.unity.BigShowWhenCollapsed"; + + static final String NOTIFICATION_CHANNELS_SHARED_PREFS = "UNITY_NOTIFICATIONS"; + static final String NOTIFICATION_CHANNELS_SHARED_PREFS_KEY = "ChannelIDs"; + static final String NOTIFICATION_IDS_SHARED_PREFS = "UNITY_STORED_NOTIFICATION_IDS"; + static final String NOTIFICATION_IDS_SHARED_PREFS_KEY = "UNITY_NOTIFICATION_IDS"; + + private void initialize(Activity activity, NotificationCallback notificationCallback) { + // always assign these, as callback here is always new, activity and context might be + mContext = activity.getApplicationContext(); + mActivity = activity; + mNotificationCallback = notificationCallback; + if (mScheduledNotifications == null) + mScheduledNotifications = new ConcurrentHashMap(); + if (mBackgroundThread == null || !mBackgroundThread.isAlive()) + mBackgroundThread = new UnityNotificationBackgroundThread(this, mScheduledNotifications); + if (mRandom == null) + mRandom = new Random(); + if (mVisibleNotifications == null) + mVisibleNotifications = new HashSet<>(); + + Bundle metaData = getAppMetadata(); + + Boolean rescheduleOnRestart = false; + if (metaData != null) + rescheduleOnRestart = metaData.getBoolean("reschedule_notifications_on_restart", false); + + if (rescheduleOnRestart) { + ComponentName receiver = new ComponentName(mContext, UnityNotificationRestartOnBootReceiver.class); + PackageManager pm = mContext.getPackageManager(); + + pm.setComponentEnabledSetting(receiver, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP); + } + + mOpenActivity = UnityNotificationUtilities.getOpenAppActivity(mContext); + if (mOpenActivity == null) + throw new RuntimeException("Failed to determine Activity to be opened when tapping notification"); + if (!mBackgroundThread.isAlive()) + mBackgroundThread.start(); + } + + static synchronized UnityNotificationManager getNotificationManagerImpl(Context context) { + if (mUnityNotificationManager == null) { + mUnityNotificationManager = new UnityNotificationManager(); + mUnityNotificationManager.mVisibleNotifications = new HashSet<>(); + mUnityNotificationManager.mScheduledNotifications = new ConcurrentHashMap(); + } + + // always assign context, as it might change + mUnityNotificationManager.mContext = context.getApplicationContext(); + return mUnityNotificationManager; + } + + // Called from managed code. + public static synchronized UnityNotificationManager getNotificationManagerImpl(Activity activity, NotificationCallback notificationCallback) { + if (mUnityNotificationManager == null) { + mUnityNotificationManager = new UnityNotificationManager(); + } + + mUnityNotificationManager.initialize(activity, notificationCallback); + return mUnityNotificationManager; + } + + private Bundle getAppMetadata() { + try { + return mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA).metaData; + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + + public NotificationManager getNotificationManager() { + return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + public int getTargetSdk() { + return mContext.getApplicationInfo().targetSdkVersion; + } + + @TargetApi(Build.VERSION_CODES.N) + public int areNotificationsEnabled() { + boolean permissionGranted = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + permissionGranted = mContext.checkCallingOrSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED; + boolean notificationsEnabled = getNotificationManager().areNotificationsEnabled(); + if (permissionGranted) + return notificationsEnabled ? PERMISSION_STATUS_ALLOWED : PERMISSION_STATUS_NOTIFICATIONS_BLOCKED_FOR_APP; + return PERMISSION_STATUS_DENIED; + } + + public void registerNotificationChannelGroup(String id, String name, String description) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannelGroup group = new NotificationChannelGroup(id, name); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + group.setDescription(description); + } + + getNotificationManager().createNotificationChannelGroup(group); + } + } + + public void deleteNotificationChannelGroup(String id) { + if (id == null) + return; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + getNotificationManager().deleteNotificationChannelGroup(id); + } else { + for (NotificationChannelWrapper c : getNotificationChannels()) { + if (id.equals(c.group)) + deleteNotificationChannel(c.id); + } + } + } + + public void registerNotificationChannel( + String id, + String name, + int importance, + String description, + boolean enableLights, + boolean enableVibration, + boolean canBypassDnd, + boolean canShowBadge, + long[] vibrationPattern, + int lockscreenVisibility, + String group) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel(id, name, importance); + channel.setDescription(description); + channel.enableLights(enableLights); + channel.enableVibration(enableVibration); + channel.setBypassDnd(canBypassDnd); + channel.setShowBadge(canShowBadge); + channel.setVibrationPattern(vibrationPattern); + channel.setLockscreenVisibility(lockscreenVisibility); + channel.setGroup(group); + + getNotificationManager().createNotificationChannel(channel); + } else { + SharedPreferences prefs = mContext.getSharedPreferences(NOTIFICATION_CHANNELS_SHARED_PREFS, Context.MODE_PRIVATE); + Set channelIds = new HashSet(prefs.getStringSet(NOTIFICATION_CHANNELS_SHARED_PREFS_KEY, new HashSet())); + channelIds.add(id); // TODO: what if users create the channel again with the same id? + + // Add to notification channel ids SharedPreferences. + SharedPreferences.Editor editor = prefs.edit().clear(); + editor.putStringSet("ChannelIDs", channelIds); + editor.apply(); + + // Store the channel into a SharedPreferences. + SharedPreferences channelPrefs = mContext.getSharedPreferences(getSharedPrefsNameByChannelId(id), Context.MODE_PRIVATE); + editor = channelPrefs.edit(); + + editor.putString("title", name); // Sadly I can't change the "title" here to "name" due to backward compatibility. + editor.putInt("importance", importance); + editor.putString("description", description); + editor.putBoolean("enableLights", enableLights); + editor.putBoolean("enableVibration", enableVibration); + editor.putBoolean("canBypassDnd", canBypassDnd); + editor.putBoolean("canShowBadge", canShowBadge); + editor.putString("vibrationPattern", Arrays.toString(vibrationPattern)); + editor.putInt("lockscreenVisibility", lockscreenVisibility); + editor.putString("group", group); + + editor.apply(); + } + } + + private static String getSharedPrefsNameByChannelId(String id) + { + return String.format("unity_notification_channel_%s", id); + } + + public NotificationChannelWrapper getNotificationChannel(String id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel ch = getNotificationManagerImpl(mContext).getNotificationManager().getNotificationChannel(id); + if (ch == null) + return null; + return notificationChannelToWrapper(ch); + } + + SharedPreferences prefs = mContext.getSharedPreferences(getSharedPrefsNameByChannelId(id), Context.MODE_PRIVATE); + NotificationChannelWrapper channel = new NotificationChannelWrapper(); + + channel.id = id; + channel.name = prefs.getString("title", "undefined"); + channel.importance = prefs.getInt("importance", NotificationManager.IMPORTANCE_DEFAULT); + channel.description = prefs.getString("description", "undefined"); + channel.enableLights = prefs.getBoolean("enableLights", false); + channel.enableVibration = prefs.getBoolean("enableVibration", false); + channel.canBypassDnd = prefs.getBoolean("canBypassDnd", false); + channel.canShowBadge = prefs.getBoolean("canShowBadge", false); + channel.lockscreenVisibility = prefs.getInt("lockscreenVisibility", VISIBILITY_PUBLIC); + channel.group = prefs.getString("group", null); + String[] vibrationPatternStr = prefs.getString("vibrationPattern", "[]").split(","); + + long[] vibrationPattern = new long[vibrationPatternStr.length]; + + if (vibrationPattern.length > 1) { + for (int i = 0; i < vibrationPatternStr.length; i++) { + try { + vibrationPattern[i] = Long.parseLong(vibrationPatternStr[i]); + } catch (NumberFormatException e) { + vibrationPattern[i] = 1; + } + } + } + + channel.vibrationPattern = vibrationPattern.length > 1 ? vibrationPattern : null; + return channel; + } + + @TargetApi(Build.VERSION_CODES.O) + private static NotificationChannelWrapper notificationChannelToWrapper(Object chan) { + // Possibly unavailable classes cannot be in API, breaks reflection code looping over when searching for method + NotificationChannel channel = (NotificationChannel)chan; + NotificationChannelWrapper wrapper = new NotificationChannelWrapper(); + + wrapper.id = channel.getId(); + wrapper.name = channel.getName().toString(); + wrapper.importance = channel.getImportance(); + wrapper.description = channel.getDescription(); + wrapper.enableLights = channel.shouldShowLights(); + wrapper.enableVibration = channel.shouldVibrate(); + wrapper.canBypassDnd = channel.canBypassDnd(); + wrapper.canShowBadge = channel.canShowBadge(); + wrapper.vibrationPattern = channel.getVibrationPattern(); + wrapper.lockscreenVisibility = channel.getLockscreenVisibility(); + wrapper.group = channel.getGroup(); + + return wrapper; + } + + public void deleteNotificationChannel(String id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + getNotificationManager().deleteNotificationChannel(id); + } else { + SharedPreferences prefs = mContext.getSharedPreferences(NOTIFICATION_CHANNELS_SHARED_PREFS, Context.MODE_PRIVATE); + Set channelIds = prefs.getStringSet(NOTIFICATION_CHANNELS_SHARED_PREFS_KEY, new HashSet()); + + if (!channelIds.contains(id)) + return; + + // Remove from the notification channel ids SharedPreferences. + channelIds = new HashSet(channelIds); + channelIds.remove(id); + SharedPreferences.Editor editor = prefs.edit().clear(); + editor.putStringSet(NOTIFICATION_CHANNELS_SHARED_PREFS_KEY, channelIds); + editor.apply(); + + // Delete the notification channel SharedPreferences. + SharedPreferences channelPrefs = mContext.getSharedPreferences(getSharedPrefsNameByChannelId(id), Context.MODE_PRIVATE); + channelPrefs.edit().clear().apply(); + } + } + + public NotificationChannelWrapper[] getNotificationChannels() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + List channels = getNotificationManager().getNotificationChannels(); + if (channels.size() == 0) + return null; + NotificationChannelWrapper[] channelList = new NotificationChannelWrapper[channels.size()]; + int i = 0; + for (NotificationChannel ch : channels) { + channelList[i++] = notificationChannelToWrapper(ch); + } + + return channelList; + } else { + SharedPreferences prefs = mContext.getSharedPreferences(NOTIFICATION_CHANNELS_SHARED_PREFS, Context.MODE_PRIVATE); + Set channelIdsSet = prefs.getStringSet(NOTIFICATION_CHANNELS_SHARED_PREFS_KEY, new HashSet()); + if (channelIdsSet.size() == 0) + return null; + NotificationChannelWrapper[] channels = new NotificationChannelWrapper[channelIdsSet.size()]; + int i = 0; + for (String k : channelIdsSet) { + channels[i++] = getNotificationChannel(k); + } + return channels; + } + } + + private int generateUniqueId() { + int id = 0; + do { + id += mRandom.nextInt(1000); + } while (mScheduledNotifications.containsKey(Integer.valueOf(id))); + + return id; + } + + public int scheduleNotification(Notification.Builder notificationBuilder, boolean customized) { + Bundle extras = notificationBuilder.getExtras(); + int id; + if (extras.containsKey(KEY_ID)) + id = notificationBuilder.getExtras().getInt(KEY_ID, -1); + else { + id = generateUniqueId(); + extras.putInt(KEY_ID, id); + } + + boolean addedNew = mScheduledNotifications.putIfAbsent(id, notificationBuilder) == null; + mBackgroundThread.enqueueNotification(id, notificationBuilder, customized, addedNew); + return id; + } + + void performNotificationScheduling(int id, Notification.Builder notificationBuilder, boolean customized) { + Bundle extras = notificationBuilder.getExtras(); + long repeatInterval = extras.getLong(KEY_REPEAT_INTERVAL, -1); + long fireTime = extras.getLong(KEY_FIRE_TIME, -1); + + // if less than a second in the future, notify right away + boolean fireNow = fireTime - Calendar.getInstance().getTime().getTime() < 1000; + if (!fireNow || repeatInterval > 0) { + if (fireNow) { + // schedule at next repetition + fireTime += repeatInterval; + } + + Intent intent = buildNotificationIntent(); + + if (intent != null) { + saveNotification(notificationBuilder.build(), customized); + scheduleAlarmWithNotification(notificationBuilder, intent, fireTime); + } + } + + if (fireNow) { + Notification notification = buildNotificationForSending(mOpenActivity, notificationBuilder); + notify(id, notification); + } + } + + void scheduleAlarmWithNotification(Notification.Builder notificationBuilder, Intent intent, long fireTime) { + Bundle extras = notificationBuilder.getExtras(); + int id = extras.getInt(KEY_ID, -1); + long repeatInterval = extras.getLong(KEY_REPEAT_INTERVAL, -1); + // fireTime not taken from notification, because we may have adjusted it + + // when rescheduling after boot notification may be absent + // also, we may be replacing an existing notification + mScheduledNotifications.put(Integer.valueOf(id), notificationBuilder); + intent.putExtra(KEY_NOTIFICATION_ID, id); + + PendingIntent broadcast = getBroadcastPendingIntent(id, intent, PendingIntent.FLAG_UPDATE_CURRENT); + scheduleNotificationIntentAlarm(repeatInterval, fireTime, broadcast); + } + + void scheduleAlarmWithNotification(Notification.Builder notificationBuilder) { + long fireTime = notificationBuilder.getExtras().getLong(KEY_FIRE_TIME, 0L); + Intent intent = buildNotificationIntent(); + scheduleAlarmWithNotification(notificationBuilder, intent, fireTime); + } + + private Notification buildNotificationForSending(Class openActivity, Notification.Builder builder) { + int id = builder.getExtras().getInt(KEY_ID, -1); + Intent openAppIntent = new Intent(mContext, openActivity); + openAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + openAppIntent.putExtra(KEY_NOTIFICATION_ID, id); + PendingIntent pendingIntent = getActivityPendingIntent(id, openAppIntent, 0); + builder.setContentIntent(pendingIntent); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // Can't check StatusBar notifications pre-M, so ask to be notified when dismissed + Intent deleteIntent = new Intent(mContext, UnityNotificationManager.class); + deleteIntent.setAction(KEY_NOTIFICATION_DISMISSED); // need action to distinguish intent from content one + deleteIntent.putExtra(KEY_NOTIFICATION_DISMISSED, id); + PendingIntent deletePending = getBroadcastPendingIntent(id, deleteIntent, 0); + builder.setDeleteIntent(deletePending); + } + + finalizeNotificationForDisplay(builder); + return builder.build(); + } + + void performNotificationHousekeeping(Set ids) { + Log.d(TAG_UNITY, "Checking for invalid notification IDs still hanging around"); + + Set invalid = findInvalidNotificationIds(ids); + Set currentIds = new HashSet<>(ids); + for (String id : invalid) { + currentIds.remove(id); + mScheduledNotifications.remove(id); + } + + // in case we have saved intents, clear them + for (String id : invalid) + deleteExpiredNotificationIntent(id); + } + + private Set findInvalidNotificationIds(Set ids) { + Intent intent = buildNotificationIntent(); + HashSet invalid = new HashSet(); + for (String id : ids) { + // Get the given broadcast PendingIntent by id as request code. + // FLAG_NO_CREATE is set to return null if the described PendingIntent doesn't exist. + PendingIntent broadcast = getBroadcastPendingIntent(Integer.valueOf(id), intent, PendingIntent.FLAG_NO_CREATE); + if (broadcast == null) { + invalid.add(id); + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + StatusBarNotification[] active = getNotificationManager().getActiveNotifications(); + for (StatusBarNotification notification : active) { + // any notifications in status bar are still valid + String id = String.valueOf(notification.getId()); + invalid.remove(id); + } + } + else synchronized (this) { + for (Integer visibleId : mVisibleNotifications) { + String id = String.valueOf(visibleId); + invalid.remove(id); + } + } + + // if app is launched with notification, user still has access to it + if (UnityPlayer.currentActivity != null) { + Intent currentIntent = UnityPlayer.currentActivity.getIntent(); + if (currentIntent.hasExtra(KEY_NOTIFICATION_ID)) { + int id = currentIntent.getExtras().getInt(KEY_NOTIFICATION_ID); + invalid.remove(String.valueOf(id)); + } + } + + return invalid; + } + + private Intent buildNotificationIntent() { + Intent intent = new Intent(mContext, UnityNotificationManager.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return intent; + } + + private PendingIntent getActivityPendingIntent(int id, Intent intent, int flags) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + return PendingIntent.getActivity(mContext, id, intent, flags | PendingIntent.FLAG_IMMUTABLE); + else + return PendingIntent.getActivity(mContext, id, intent, flags); + } + + private PendingIntent getBroadcastPendingIntent(int id, Intent intent, int flags) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + return PendingIntent.getBroadcast(mContext, id, intent, flags | PendingIntent.FLAG_IMMUTABLE); + else + return PendingIntent.getBroadcast(mContext, id, intent, flags); + } + + // Save the notification intent to SharedPreferences if reschedule_on_restart is true, + // which will be consumed by UnityNotificationRestartOnBootReceiver for device reboot. + synchronized void saveNotification(Notification notification, boolean customized) { + String notification_id = Integer.toString(notification.extras.getInt(KEY_ID, -1)); + SharedPreferences prefs = mContext.getSharedPreferences(getSharedPrefsNameByNotificationId(notification_id), Context.MODE_PRIVATE); + UnityNotificationUtilities.serializeNotification(prefs, notification, customized); + } + + static String getSharedPrefsNameByNotificationId(String id) { + return String.format("u_notification_data_%s", id); + } + + // Load all the notification intents from SharedPreferences. + synchronized List loadSavedNotifications() { + Set ids = getScheduledNotificationIDs(); + + List intent_data_list = new ArrayList(); + Set idsMarkedForRemoval = new HashSet(); + + for (String id : ids) { + SharedPreferences prefs = mContext.getSharedPreferences(getSharedPrefsNameByNotificationId(id), Context.MODE_PRIVATE); + Notification.Builder builder = null; + Object notification = UnityNotificationUtilities.deserializeNotification(mContext, prefs); + if (notification != null) { + if (notification instanceof Notification.Builder) + builder = (Notification.Builder)notification; + else + builder = UnityNotificationUtilities.recoverBuilder(mContext, (Notification)notification); + } + + if (builder != null) + intent_data_list.add(builder); + else + idsMarkedForRemoval.add(id); + } + + if (idsMarkedForRemoval.size() > 0) { + ids = new HashSet<>(ids); + for (String id : idsMarkedForRemoval) { + ids.remove(id); + deleteExpiredNotificationIntent(id); + } + saveScheduledNotificationIDs(ids); + } + + return intent_data_list; + } + + private boolean canScheduleExactAlarms(AlarmManager alarmManager) { + // exact scheduling supported since Android 6 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) + return false; + if (mExactSchedulingSetting < 0) { + Bundle metaData = getAppMetadata(); + if (metaData != null) + mExactSchedulingSetting = metaData.getInt("com.unity.androidnotifications.exact_scheduling", 1); + } + if (mExactSchedulingSetting == 0) + return false; + if (Build.VERSION.SDK_INT < 31) + return true; + + return alarmManager.canScheduleExactAlarms(); + } + + public boolean canScheduleExactAlarms() { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + return canScheduleExactAlarms(alarmManager); + } + + // Call AlarmManager to set the broadcast intent with fire time and interval. + private void scheduleNotificationIntentAlarm(long repeatInterval, long fireTime, PendingIntent broadcast) { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + + if (repeatInterval <= 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && canScheduleExactAlarms(alarmManager)) { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, fireTime, broadcast); + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, fireTime, broadcast); + } + } else { + alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, fireTime, repeatInterval, broadcast); + } + } + + // Check the notification status by id. + public int checkNotificationStatus(int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + for (StatusBarNotification n : getNotificationManager().getActiveNotifications()) { + if (id == n.getId()) + return 2; + } + } else synchronized (this) { + for (Integer notificationId : mVisibleNotifications) { + if (notificationId.intValue() == id) + return 2; + } + } + + if (mScheduledNotifications.containsKey(id)) + return 1; + if (checkIfPendingNotificationIsRegistered(id)) + return 1; + + return 0; + } + + // Check if the pending notification with the given id has been registered. + public boolean checkIfPendingNotificationIsRegistered(int id) { + Intent intent = new Intent(mActivity, UnityNotificationManager.class); + return (getBroadcastPendingIntent(id, intent, PendingIntent.FLAG_NO_CREATE) != null); + } + + // Cancel all the pending notifications. + public void cancelAllPendingNotificationIntents() { + mBackgroundThread.enqueueCancelAllNotifications(); + } + + private synchronized Set getScheduledNotificationIDs() { + SharedPreferences prefs = mContext.getSharedPreferences(NOTIFICATION_IDS_SHARED_PREFS, Context.MODE_PRIVATE); + Set ids = prefs.getStringSet(NOTIFICATION_IDS_SHARED_PREFS_KEY, new HashSet()); + return ids; + } + + synchronized void saveScheduledNotificationIDs(Set ids) { + SharedPreferences.Editor editor = mContext.getSharedPreferences(NOTIFICATION_IDS_SHARED_PREFS, Context.MODE_PRIVATE).edit().clear(); + editor.putStringSet(NOTIFICATION_IDS_SHARED_PREFS_KEY, ids); + editor.apply(); + } + + // Cancel a pending notification by id. + public void cancelPendingNotification(int id) { + mBackgroundThread.enqueueCancelNotification(id); + } + + // Cancel a pending notification by id. + void cancelPendingNotificationIntent(int id) { + Intent intent = new Intent(mContext, UnityNotificationManager.class); + PendingIntent broadcast = getBroadcastPendingIntent(id, intent, PendingIntent.FLAG_NO_CREATE); + + if (broadcast != null) { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + alarmManager.cancel(broadcast); + broadcast.cancel(); + } + } + + // Delete the notification intent from SharedPreferences by id. + synchronized void deleteExpiredNotificationIntent(String id) { + SharedPreferences notificationPrefs = mContext.getSharedPreferences(getSharedPrefsNameByNotificationId(id), Context.MODE_PRIVATE); + notificationPrefs.edit().clear().apply(); + } + + // Cancel a previously shown notification by id. + public void cancelDisplayedNotification(int id) { + getNotificationManager().cancel(id); + } + + // Cancel all previously shown notifications. + public void cancelAllNotifications() { + getNotificationManager().cancelAll(); + } + + @Override + public void onReceive(Context context, Intent intent) { + // This method is called on OS created instance and that instance is recreated during various times + // for example sending app to background will cause new instance to be created when alarm fires + // since we also create one instance for our uses, always forward to that instance (creating if necessary) + getNotificationManagerImpl(context).onReceive(intent); + } + + public void onReceive(Intent intent) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + if (KEY_NOTIFICATION_DISMISSED.equals(intent.getAction())) { + int removedId = intent.getIntExtra(KEY_NOTIFICATION_DISMISSED, -1); + if (removedId > 0) synchronized (this) { + mVisibleNotifications.remove(removedId); + } + return; + } + } + showNotification(intent); + } + + private void showNotification(Intent intent) { + Object notification = getNotificationOrIdForIntent(intent); + if (notification == null) { + return; + } + + if (notification instanceof Notification) { + Notification notif = (Notification) notification; + int id = notif.extras.getInt(KEY_ID, -1); + notify(id, notif); + return; + } + + Integer notificationId = (Integer)notification; + Notification.Builder builder = mScheduledNotifications.get(notificationId); + if (builder != null) { + notify(notificationId, builder); + return; + } + + AsyncTask.execute(() -> { + Notification.Builder nb = deserializeNotificationBuilder(notificationId); + if (nb == null) { + Log.e(TAG_UNITY, "Failed to recover builder, can't send notification"); + return; + } + + notify(notificationId, nb); + }); + } + + void notify(int id, Notification.Builder builder) { + Class openActivity; + if (mOpenActivity == null) { + openActivity = UnityNotificationUtilities.getOpenAppActivity(mContext); + if (openActivity == null) { + Log.e(TAG_UNITY, "Activity not found, cannot show notification"); + return; + } + } + else { + openActivity = mOpenActivity; + } + + Notification notification = buildNotificationForSending(openActivity, builder); + if (notification != null) { + notify(id, notification); + } + } + + // Call the system notification service to notify the notification. + private void notify(int id, Notification notification) { + boolean showInForeground = notification.extras.getBoolean(KEY_SHOW_IN_FOREGROUND, true); + if (!isInForeground() || showInForeground) { + getNotificationManager().notify(id, notification); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) synchronized (this) { + mVisibleNotifications.add(Integer.valueOf(id)); + } + } + + long repeatInterval = notification.extras.getLong(KEY_REPEAT_INTERVAL, -1); + if (repeatInterval <= 0) { + mScheduledNotifications.remove(id); + cancelPendingNotificationIntent(id); + } + + try { + if (mNotificationCallback != null) + mNotificationCallback.onSentNotification(notification); + } catch (RuntimeException ex) { + Log.w(TAG_UNITY, "Can not invoke OnNotificationReceived event when the app is not running!"); + } + } + + public static Integer getNotificationColor(Notification notification) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + return null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (!notification.extras.containsKey(Notification.EXTRA_COLORIZED)) + return null; + } + + return notification.color; + } + + public static int getNotificationGroupAlertBehavior(Notification notification) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + return notification.getGroupAlertBehavior(); + return 0; + } + + private void finalizeNotificationForDisplay(Notification.Builder notificationBuilder) { + String icon = notificationBuilder.getExtras().getString(KEY_SMALL_ICON); + Object ico = getIconForUri(icon); + if (ico != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + notificationBuilder.setSmallIcon((Icon)ico); + } else { + int iconId = UnityNotificationUtilities.findResourceIdInContextByName(mContext, icon); + if (iconId == 0) { + iconId = mContext.getApplicationInfo().icon; + } + notificationBuilder.setSmallIcon(iconId); + } + + icon = notificationBuilder.getExtras().getString(KEY_LARGE_ICON); + Object largeIcon = getIcon(icon); + if (largeIcon != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && largeIcon instanceof Icon) + notificationBuilder.setLargeIcon((Icon)largeIcon); + else + notificationBuilder.setLargeIcon((Bitmap)largeIcon); + } + + setupBigPictureStyle(notificationBuilder); + } + + private Object getIcon(String icon) { + if (icon == null || icon.length() == 0) + return null; + if (icon.charAt(0) == '/') { + BitmapFactory.decodeFile(icon); + } + + Object ico = getIconForUri(icon); + if (ico != null) + return ico; + + return getIconFromResources(icon, false); + } + + private Object getIconForUri(String uri) { + if (uri == null || uri.length() == 0) + return null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && uri.indexOf("://") > 0) { + return Icon.createWithContentUri(uri); + } + + return null; + } + + private Object getIconFromResources(String name, boolean forceBitmap) { + int iconId = UnityNotificationUtilities.findResourceIdInContextByName(mContext, name); + if (iconId != 0) { + if (!forceBitmap && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + return Icon.createWithResource(mContext, iconId); + return BitmapFactory.decodeResource(mContext.getResources(), iconId); + } + + return null; + } + + private Bitmap loadBitmap(String uri) { + try { + InputStream in = mContext.getContentResolver().openInputStream(Uri.parse(uri)); + return BitmapFactory.decodeStream(in); + } catch (Exception e) { + Log.e(TAG_UNITY, "Failed to load image " + uri, e); + return null; + } + } + + @SuppressWarnings("deprecation") + public Notification.Builder createNotificationBuilder(String channelID) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + Notification.Builder notificationBuilder = new Notification.Builder(mContext); + + // For device below Android O, we use the values from NotificationChannelWrapper to set visibility, priority etc. + NotificationChannelWrapper fakeNotificationChannel = getNotificationChannel(channelID); + + if (fakeNotificationChannel.vibrationPattern != null && fakeNotificationChannel.vibrationPattern.length > 0) { + notificationBuilder.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_SOUND); + notificationBuilder.setVibrate(fakeNotificationChannel.vibrationPattern); + } else { + notificationBuilder.setDefaults(Notification.DEFAULT_ALL); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + notificationBuilder.setVisibility((int) fakeNotificationChannel.lockscreenVisibility); + } + + // Need to convert Oreo channel importance to pre-Oreo priority. + int priority; + switch (fakeNotificationChannel.importance) { + case NotificationManager.IMPORTANCE_HIGH: + priority = Notification.PRIORITY_MAX; + break; + case NotificationManager.IMPORTANCE_DEFAULT: + priority = Notification.PRIORITY_DEFAULT; + break; + case NotificationManager.IMPORTANCE_LOW: + priority = Notification.PRIORITY_LOW; + break; + case NotificationManager.IMPORTANCE_NONE: + priority = Notification.PRIORITY_MIN; + break; + default: + priority = Notification.PRIORITY_DEFAULT; + } + notificationBuilder.setPriority(priority); + notificationBuilder.getExtras().putString(KEY_CHANNEL_ID, channelID); + + return notificationBuilder; + } else { + return new Notification.Builder(mContext, channelID); + } + } + + public static void setNotificationIcon(Notification.Builder notificationBuilder, String keyName, String icon) { + if (icon == null || icon.length() == 0 && notificationBuilder.getExtras().getString(keyName) != null) + notificationBuilder.getExtras().remove(keyName); + else + notificationBuilder.getExtras().putString(keyName, icon); + } + + public void setupBigPictureStyle(Notification.Builder builder, + String largeIcon, String picture, String contentTitle, String contentDescription, String summaryText, boolean showWhenCollapsed) { + Bundle extras = builder.getExtras(); + if (picture == null || picture.length() == 0) + return; + extras.putString(KEY_BIG_LARGE_ICON, largeIcon); + extras.putString(KEY_BIG_PICTURE, picture); + extras.putString(KEY_BIG_CONTENT_TITLE, contentTitle); + extras.putString(KEY_BIG_SUMMARY_TEXT, summaryText); + extras.putString(KEY_BIG_CONTENT_DESCRIPTION, contentDescription); + extras.putBoolean(KEY_BIG_SHOW_WHEN_COLLAPSED, showWhenCollapsed); + } + + private void setupBigPictureStyle(Notification.Builder builder) { + Bundle extras = builder.getExtras(); + String picture = extras.getString(KEY_BIG_PICTURE); + if (picture == null) + return; // not big picture style + Notification.BigPictureStyle style = new Notification.BigPictureStyle(); + String largeIcon = extras.getString(KEY_BIG_LARGE_ICON); + Object ico = getIcon(largeIcon); + if (ico != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ico instanceof Icon) + style.bigLargeIcon((Icon)ico); + else + style.bigLargeIcon((Bitmap)ico); + } + + if (picture.charAt(0) == '/') { + style.bigPicture(BitmapFactory.decodeFile(picture)); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && picture.indexOf("://") > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + Icon icon = Icon.createWithContentUri(picture); + style.bigPicture(icon); + } else { + Bitmap pic = loadBitmap(picture); + if (pic != null) { + style.bigPicture(pic); + } + } + } else { + Object pic = getIconFromResources(picture, Build.VERSION.SDK_INT < Build.VERSION_CODES.S); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && pic instanceof Icon) + style.bigPicture((Icon)pic); + else if (pic instanceof Bitmap) + style.bigPicture((Bitmap)pic); + } + + style.setBigContentTitle(extras.getString(KEY_BIG_CONTENT_TITLE)); + style.setSummaryText(extras.getString(KEY_BIG_SUMMARY_TEXT)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + style.setContentDescription(extras.getString(KEY_BIG_CONTENT_DESCRIPTION)); + style.showBigPictureWhenCollapsed(extras.getBoolean(KEY_BIG_SHOW_WHEN_COLLAPSED, false)); + } + + builder.setStyle(style); + } + + public static void setNotificationColor(Notification.Builder notificationBuilder, int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (color != 0) { + notificationBuilder.setColor(color); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notificationBuilder.setColorized(true); + } + } + } + } + + public static void setNotificationUsesChronometer(Notification.Builder notificationBuilder, boolean usesChrono) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) + notificationBuilder.setUsesChronometer(usesChrono); + } + + public static void setNotificationGroupAlertBehavior(Notification.Builder notificationBuilder, int behavior) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + notificationBuilder.setGroupAlertBehavior(behavior); + } + + public static String getNotificationChannelId(Notification notification) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return notification.getChannelId(); + } + + return null; + } + + private static boolean isInForeground() { + ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo(); + ActivityManager.getMyMemoryState(appProcessInfo); + return (appProcessInfo.importance == IMPORTANCE_FOREGROUND || appProcessInfo.importance == IMPORTANCE_VISIBLE); + } + + public Notification getNotificationFromIntent(Intent intent) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (intent.hasExtra(KEY_NOTIFICATION_ID)) { + int id = intent.getExtras().getInt(KEY_NOTIFICATION_ID); + StatusBarNotification[] shownNotifications = getNotificationManager().getActiveNotifications(); + for (StatusBarNotification n : shownNotifications) { + if (n.getId() == id) { + return n.getNotification(); + } + } + } + } + + Object notification = getNotificationOrBuilderForIntent(intent); + if (notification == null) + return null; + if (notification instanceof Notification) + return (Notification)notification; + Notification.Builder builder = (Notification.Builder)notification; + return builder.build(); + } + + private Object getNotificationOrIdForIntent(Intent intent) { + if (intent.hasExtra(KEY_NOTIFICATION_ID)) { + return intent.getExtras().getInt(KEY_NOTIFICATION_ID); + } else if (intent.hasExtra(KEY_NOTIFICATION)) { + // old code path where Notification object is in intent + // in case the app was replaced and there still are pending alarms with notification + return intent.getParcelableExtra(KEY_NOTIFICATION); + } + + return null; + } + + private Object getNotificationOrBuilderForIntent(Intent intent) { + Object notification = getNotificationOrIdForIntent(intent); + if (notification instanceof Integer) { + Integer notificationId = (Integer)notification; + if ((notification = mScheduledNotifications.get(notificationId)) == null) { + // in case we don't have cached notification, deserialize from storage + return deserializeNotificationBuilder(notificationId); + } + } + + return notification; + } + + private Notification.Builder deserializeNotificationBuilder(Integer notificationId) { + SharedPreferences prefs = mContext.getSharedPreferences(getSharedPrefsNameByNotificationId(notificationId.toString()), Context.MODE_PRIVATE); + Object notification = UnityNotificationUtilities.deserializeNotification(mContext, prefs); + if (notification == null) { + return null; + } + + if (notification instanceof Notification) { + return UnityNotificationUtilities.recoverBuilder(mContext, (Notification)notification); + } + + return (Notification.Builder)notification; + } + + public void showNotificationSettings(String channelId) { + Intent settingsIntent; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + settingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", mContext.getPackageName(), null); + settingsIntent.setData(uri); + } else { + if (channelId != null && channelId.length() > 0) { + settingsIntent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); + settingsIntent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId); + } else { + settingsIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + } + + settingsIntent.putExtra(Settings.EXTRA_APP_PACKAGE, mContext.getPackageName()); + } + + settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mActivity.startActivity(settingsIntent); + } +} + +// Provide a wrapper for NotificationChannel. +// Create this wrapper for all Android versions as NotificationChannel is only available for Android O or above. +class NotificationChannelWrapper { + public String id; + public String name; + public int importance; + public String description; + public boolean enableLights; + public boolean enableVibration; + public boolean canBypassDnd; + public boolean canShowBadge; + public long[] vibrationPattern; + public int lockscreenVisibility; + public String group; +} + +// Implemented in C# to receive callback on notification show +interface NotificationCallback { + void onSentNotification(Notification notification); +} diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationRestartOnBootReceiver.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationRestartOnBootReceiver.java new file mode 100755 index 000000000..70774514b --- /dev/null +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationRestartOnBootReceiver.java @@ -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 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; + } + } +} diff --git a/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationUtilities.java b/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationUtilities.java new file mode 100755 index 000000000..2adefc2cd --- /dev/null +++ b/BFVersions/android/google_common/unityLibrary/src/main/java/com/unity/androidnotifications/UnityNotificationUtilities.java @@ -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 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; + } +} diff --git a/BFVersions/android/keystore/dz_keystore.jks b/BFVersions/android/keystore/dz_keystore.jks new file mode 100755 index 0000000000000000000000000000000000000000..04d1eadd179ed426f303435eda62da9c3ebfa08e GIT binary patch literal 2395 zcmY+FcRU-47RMur5UWP)y(P4bQmaB~izwBGqPtgl)DBW&1ZmNh8>2?WxHd&?7j4l> zl^Bs~Y&BA&R7I%TyuSB&@7~Y-<9yEN^ZlK3{`?*!5$Xiw03nG`K?oEPYZbf2&%wo! zM}%^KiQETBBKHoG$m#Zvh|>{F-=tLL~O4!h5%^%nis16yu*p8|Hov zl^qL{8%XJK!PGlPHwP6e>{Q?B@75?E@vrUsc^7rd{A?fj*6}PA|J6|Nl--hWIr^d} z%FdWK6d|!XQb!WUgv^$8#i-F*K5M03dGfKrac1j8uB-~r^$M7^4QM9tSv(RZ2^#^3 zj%FvKY2E3YSWH&_$4CKfp?!Bb<=wV#!r>WwA({UQjqu6w<2ae4aR9{=zPJne$S8GZ zjY%;aqkskFk zrFio6qX|EN5i=T*JZJV=mF}?jZ&jbe*A9FhgRdV4qcDNZmQ#iZg!FZ48p_|(46_uA z4c6>AWS5VtQ2+_cFg%5opO4$}4d{|5HhJ~84idUkS`U~p1lX;RoOyHB_tWEqh6}sj zSOYuttS2FYzb(eP_TmSWdkLD&8DmQcZ?P`anE)}x6^Mj{K6}l};Wcb$!{KdFlIo51eHT65 zmyhGaikJCI?SU(0APA9?0)u1#PUAFk!y@%so zaiP8UOcr^^iiVFz|M^sWQv5+5M-w-p(DdaZ0~T4-e0lM}oaD3Z1g$zuzIH94aNQ7i zD6n#FE^zwian5b}F-z?fAJpqSF(r~>nGO8er3h;*w+8#P;~CK^$@4AClNtl4 z+@E+UQ*Jpv^XPZQcoMxn!`x5OO6EX>Uhl5FGC!^@phFJ1#gT`UtO??NFCVzIr=FNj zuH(+pHb#p7rAjDFn@5ft;0eG1LIF6W;@^R&HViE6aNoyGL0eB(TVF>9i9DrkV2C7w z68;+Eg5(iF5x)=~$N~7(0{%`o{$W?hf9(3v53Y-3e~D5UteJ=HVW+Z_3&{VqYZMV= zU>1!RuAsN2c%gFCnD(TG6Fc30?Doo>8k^E=t;&*2om+g&rWN%%@k@}n7)fYmZS)!` zpPVsNTBH7_0k7o=6VTO@o_kU~nOh7U+bcZFLJewKBR7%8(;AOY&zQ`;1$%J9^=(bm43GCzT)9^AYD{bwX)&WRKD)dVK%|_m1r^FN&n}^rkpqRlHTAnW{*3w z$(FuGNH~p^Y+GD+s>9>(7GH~c<5w%Ec5>)@Jfg6E4iN^cEhP(vT1N)m-Zw!9*Fn(9 zrk8J4ebL{jELq$5`RiYdSxyLZQ+ThMr?*JX5ku-|3lrk2&IhJ@@3IU*X%eZ)`e8S! zJ)ZQ?F@(EVXJwf1zbZ6C=aEO5y`kED<2U0b_r5y#k9NgUG0hKK+MN8s>&7A|*tTH%r09=W#vawTczq{vXM=#+>0pKuclVB_~g4o-c zCjajA$_QAqGs)T(sodvGWKCXbN1k}-5m4GL%7$a+Q?NqL)*&$h?<&P;nE$EHaIS-o-7zsBCva8A#x_u zCrQ4a&HrYOp_pt`uA+T8t_8nrvS5pr4f{MAem^V5p*P zrLT1Me2kD1UGNC594&hwb~lwVS9IeQzaoEK$5i!{e}?io&)|y~12obEsfdI^I5h=8 zKq*cD7?vwxTyvY4?J-+izDBbc)>5LK-2%hXLr*#Q6?zOqHC|M(IZC$0UabBAf;eLH F{tbkWd=3Br literal 0 HcmV?d00001 diff --git a/BFVersions/android/keystore/dz_keystore.txt b/BFVersions/android/keystore/dz_keystore.txt new file mode 100755 index 000000000..08141cc14 --- /dev/null +++ b/BFVersions/android/keystore/dz_keystore.txt @@ -0,0 +1 @@ +juzu_dz0! \ No newline at end of file