From bdf07b29f6e0bcba8ca8e26c1870127b96865819 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 9 Sep 2019 12:05:32 +0200 Subject: [PATCH] Remove Seeds SDK --- Habitica/build.gradle | 4 - Habitica/proguard-rules.pro | 3 - Habitica/res/layout/fragment_gem_purchase.xml | 3 +- Habitica/res/layout/purchase_gem_view.xml | 11 - Habitica/res/values/attrs.xml | 1 - .../habitica/HabiticaPurchaseVerifier.java | 12 - .../habitica/ui/GemPurchaseOptionsView.kt | 5 - .../ui/activities/GemPurchaseActivity.kt | 53 +- .../ui/fragments/GemsPurchaseFragment.kt | 2 - habitica.resources.example | 3 - seeds-sdk/LICENSE | 202 ---- seeds-sdk/MoPub Client Licence.txt | 10 - seeds-sdk/NOTICE | 15 - seeds-sdk/build.gradle | 71 -- seeds-sdk/gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - seeds-sdk/gradlew | 160 --- seeds-sdk/gradlew.bat | 90 -- seeds-sdk/proguard-rules.pro | 17 - seeds-sdk/src/main/AndroidManifest.xml | 30 - .../android/sdk/AdvertisingIdAdapter.java | 62 - .../android/sdk/CertificateTrustManager.java | 93 -- .../android/sdk/ConnectionProcessor.java | 235 ---- .../android/sdk/ConnectionQueue.java | 318 ----- .../playseeds/android/sdk/CountlyStore.java | 276 ----- .../playseeds/android/sdk/CrashDetails.java | 405 ------- .../com/playseeds/android/sdk/DeviceId.java | 176 --- .../com/playseeds/android/sdk/DeviceInfo.java | 255 ---- .../java/com/playseeds/android/sdk/Event.java | 142 --- .../com/playseeds/android/sdk/EventQueue.java | 109 -- .../sdk/IInAppMessageShowCountListener.java | 5 - .../sdk/IInAppPurchaseCountListener.java | 5 - .../sdk/IUserBehaviorQueryListener.java | 6 - .../sdk/MainActivityEventListener.java | 116 -- .../android/sdk/MessagingAdapter.java | 48 - .../android/sdk/OpenUDIDAdapter.java | 66 -- .../android/sdk/ReferrerReceiver.java | 81 -- .../java/com/playseeds/android/sdk/Seeds.java | 1035 ----------------- .../com/playseeds/android/sdk/UserData.java | 213 ---- .../android/sdk/inappmessaging/ClickType.java | 29 - .../android/sdk/inappmessaging/Const.java | 82 -- .../android/sdk/inappmessaging/Gender.java | 32 - .../GeneralInAppMessageProvider.java | 95 -- .../sdk/inappmessaging/InAppMessage.java | 26 - .../inappmessaging/InAppMessageListener.java | 33 - .../inappmessaging/InAppMessageManager.java | 571 --------- .../inappmessaging/InAppMessageProvider.java | 60 - .../inappmessaging/InAppMessageRequest.java | 314 ----- .../inappmessaging/InAppMessageResponse.java | 230 ---- .../sdk/inappmessaging/InAppMessageView.java | 323 ----- .../sdk/inappmessaging/InAppWebView.java | 102 -- .../android/sdk/inappmessaging/Log.java | 94 -- .../sdk/inappmessaging/RequestException.java | 37 - .../sdk/inappmessaging/ResourceManager.java | 200 ---- .../sdk/inappmessaging/RichMediaActivity.java | 371 ------ .../android/sdk/inappmessaging/Util.java | 284 ----- .../java/org/openudid/OpenUDID_manager.java | 200 ---- .../java/org/openudid/OpenUDID_service.java | 37 - seeds-sdk/src/test/AndroidManifest.xml | 64 - settings.gradle | 2 +- 60 files changed, 3 insertions(+), 7527 deletions(-) delete mode 100755 seeds-sdk/LICENSE delete mode 100755 seeds-sdk/MoPub Client Licence.txt delete mode 100644 seeds-sdk/NOTICE delete mode 100755 seeds-sdk/build.gradle delete mode 100644 seeds-sdk/gradle/wrapper/gradle-wrapper.jar delete mode 100644 seeds-sdk/gradle/wrapper/gradle-wrapper.properties delete mode 100644 seeds-sdk/gradlew delete mode 100644 seeds-sdk/gradlew.bat delete mode 100755 seeds-sdk/proguard-rules.pro delete mode 100755 seeds-sdk/src/main/AndroidManifest.xml delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/AdvertisingIdAdapter.java delete mode 100644 seeds-sdk/src/main/java/com/playseeds/android/sdk/CertificateTrustManager.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/ConnectionProcessor.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/ConnectionQueue.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/CountlyStore.java delete mode 100644 seeds-sdk/src/main/java/com/playseeds/android/sdk/CrashDetails.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/DeviceId.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/DeviceInfo.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/Event.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/EventQueue.java delete mode 100644 seeds-sdk/src/main/java/com/playseeds/android/sdk/IInAppMessageShowCountListener.java delete mode 100644 seeds-sdk/src/main/java/com/playseeds/android/sdk/IInAppPurchaseCountListener.java delete mode 100644 seeds-sdk/src/main/java/com/playseeds/android/sdk/IUserBehaviorQueryListener.java delete mode 100644 seeds-sdk/src/main/java/com/playseeds/android/sdk/MainActivityEventListener.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/MessagingAdapter.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/OpenUDIDAdapter.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/ReferrerReceiver.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/Seeds.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/UserData.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/ClickType.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Const.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Gender.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/GeneralInAppMessageProvider.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessage.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageListener.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageManager.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageProvider.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageRequest.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageResponse.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageView.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppWebView.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Log.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/RequestException.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/ResourceManager.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/RichMediaActivity.java delete mode 100755 seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Util.java delete mode 100755 seeds-sdk/src/main/java/org/openudid/OpenUDID_manager.java delete mode 100755 seeds-sdk/src/main/java/org/openudid/OpenUDID_service.java delete mode 100644 seeds-sdk/src/test/AndroidManifest.xml diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 0db104892..8b4213efa 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -119,10 +119,6 @@ dependencies { implementation 'com.google.firebase:firebase-perf:19.0.0' implementation 'com.google.android.gms:play-services-auth:17.0.0' implementation 'io.realm:android-adapters:3.1.0' - implementation(project(':seeds-sdk')) { - exclude group: 'com.google.android.gms' - exclude group: 'com.android.support', module: 'multidex' - } implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.multidex:multidex:2.0.1' implementation 'com.nex3z:flow-layout:1.2.2' diff --git a/Habitica/proguard-rules.pro b/Habitica/proguard-rules.pro index 001931548..038acf3b3 100644 --- a/Habitica/proguard-rules.pro +++ b/Habitica/proguard-rules.pro @@ -153,9 +153,6 @@ #checkout -keep class com.android.vending.billing.** -#seeds sdk --keep class com.playseeds.** { *; } - -assumenosideeffects class org.solovyev.android.checkout.Billing { public static void debug(...); public static void warning(...); diff --git a/Habitica/res/layout/fragment_gem_purchase.xml b/Habitica/res/layout/fragment_gem_purchase.xml index f582f32ca..351f006d7 100644 --- a/Habitica/res/layout/fragment_gem_purchase.xml +++ b/Habitica/res/layout/fragment_gem_purchase.xml @@ -87,8 +87,7 @@ android:layout_height="wrap_content" android:layout_weight="1" app:gemAmount="84" - app:gemDrawable="@drawable/gems_84" - app:showSeedsPromo="true" /> + app:gemDrawable="@drawable/gems_84" /> - - \ No newline at end of file diff --git a/Habitica/res/values/attrs.xml b/Habitica/res/values/attrs.xml index ac876719b..cf1a55d25 100644 --- a/Habitica/res/values/attrs.xml +++ b/Habitica/res/values/attrs.xml @@ -31,7 +31,6 @@ - diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.java b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.java index 58aba1888..6cc9d868f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaPurchaseVerifier.java @@ -15,7 +15,6 @@ import com.habitrpg.android.habitica.models.PurchaseValidationRequest; import com.habitrpg.android.habitica.models.SubscriptionValidationRequest; import com.habitrpg.android.habitica.models.Transaction; import com.habitrpg.android.habitica.models.responses.ErrorResponse; -import com.playseeds.android.sdk.Seeds; import org.greenrobot.eventbus.EventBus; import org.json.JSONObject; @@ -82,17 +81,6 @@ public class HabiticaPurchaseVerifier extends BasePurchaseVerifier { requestListener.onSuccess(verifiedPurchases); EventBus.getDefault().post(new ConsumablePurchasedEvent(purchase)); - - //TODO: find way to get $ price automatically. - if (purchase.sku.equals(PurchaseTypes.Purchase4Gems)) { - Seeds.sharedInstance().recordIAPEvent(purchase.sku, 0.99); - } else if (purchase.sku.equals(PurchaseTypes.Purchase21Gems)) { - Seeds.sharedInstance().recordIAPEvent(purchase.sku, 4.99); - } else if (purchase.sku.equals(PurchaseTypes.Purchase42Gems)) { - Seeds.sharedInstance().recordIAPEvent(purchase.sku, 9.99); - } else if (purchase.sku.equals(PurchaseTypes.Purchase84Gems)) { - Seeds.sharedInstance().recordSeedsIAPEvent(purchase.sku, 19.99); - } }, throwable -> { if (throwable.getClass().equals(retrofit2.adapter.rxjava2.HttpException.class)) { HttpException error = (HttpException) throwable; diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/GemPurchaseOptionsView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/GemPurchaseOptionsView.kt index eb6118ef2..663adc533 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/GemPurchaseOptionsView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/GemPurchaseOptionsView.kt @@ -11,7 +11,6 @@ import com.habitrpg.android.habitica.ui.helpers.bindView class GemPurchaseOptionsView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) { - val seedsImageButton: ImageButton by bindView(R.id.seedsImageButton) private val gemImageView: ImageView by bindView(R.id.gem_image) private val gemAmountTextView: TextView by bindView(R.id.gem_amount) private val purchaseButton: Button by bindView(R.id.purchase_button) @@ -31,10 +30,6 @@ class GemPurchaseOptionsView(context: Context, attrs: AttributeSet) : FrameLayou if (iconRes != null) { gemImageView.setImageDrawable(iconRes) } - - if (a.getBoolean(R.styleable.GemPurchaseOptionsView_showSeedsPromo, false)) { - seedsImageButton.visibility = View.VISIBLE - } } fun setOnPurchaseClickListener(listener: OnClickListener) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GemPurchaseActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GemPurchaseActivity.kt index 8922a54ba..e0c8197c2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GemPurchaseActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/GemPurchaseActivity.kt @@ -20,15 +20,13 @@ import com.habitrpg.android.habitica.proxy.CrashlyticsProxy import com.habitrpg.android.habitica.ui.fragments.GemsPurchaseFragment import com.habitrpg.android.habitica.ui.fragments.SubscriptionFragment import com.habitrpg.android.habitica.ui.helpers.bindView -import com.playseeds.android.sdk.Seeds -import com.playseeds.android.sdk.inappmessaging.InAppMessageListener import io.reactivex.functions.Consumer import org.greenrobot.eventbus.Subscribe import org.solovyev.android.checkout.* import java.util.* import javax.inject.Inject -class GemPurchaseActivity : BaseActivity(), InAppMessageListener { +class GemPurchaseActivity : BaseActivity() { @Inject lateinit var crashlyticsProxy: CrashlyticsProxy @@ -62,11 +60,6 @@ class GemPurchaseActivity : BaseActivity(), InAppMessageListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Seeds.sharedInstance() - .simpleInit(this, this, "https://dash.playseeds.com", getString(R.string.seeds_app_key)).isLoggingEnabled = true - Seeds.sharedInstance().requestInAppMessage(getString(R.string.seeds_interstitial_gems)) - Seeds.sharedInstance().requestInAppMessage(getString(R.string.seeds_interstitial_sharing)) - val toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar) @@ -160,50 +153,6 @@ class GemPurchaseActivity : BaseActivity(), InAppMessageListener { } - override fun inAppMessageClicked(messageId: String) { - for (fragment in fragments) { - if (fragment.javaClass.isAssignableFrom(GemsPurchaseFragment::class.java)) { - (fragment as? GemsPurchaseFragment)?.purchaseGems(PurchaseTypes.Purchase84Gems) - } - } - } - - override fun inAppMessageDismissed(messageId: String) { - - } - - override fun inAppMessageLoadSucceeded(messageId: String) { - - } - - override fun inAppMessageShown(messageId: String, succeeded: Boolean) { - - } - - override fun noInAppMessageFound(messageId: String) { - - } - - override fun inAppMessageClickedWithDynamicPrice(messageId: String, price: Double?) { - - } - - fun showSeedsPromo(messageId: String, context: String) { - try { - runOnUiThread { - if (Seeds.sharedInstance().isInAppMessageLoaded(messageId)) { - Seeds.sharedInstance().showInAppMessage(messageId, context) - } else { - // Skip the interstitial showing this time and try to reload the interstitial - Seeds.sharedInstance().requestInAppMessage(messageId) - } - } - } catch (e: Exception) { - println("Exception: $e") - } - - } - private fun setViewPagerAdapter() { val fragmentManager = supportFragmentManager diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/GemsPurchaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/GemsPurchaseFragment.kt index 1aa0121d2..d872c7aca 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/GemsPurchaseFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/GemsPurchaseFragment.kt @@ -56,8 +56,6 @@ class GemsPurchaseFragment : BaseFragment(), GemPurchaseActivity.CheckoutFragmen val heartDrawable = BitmapDrawable(resources, HabiticaIconsHelper.imageOfHeartLarge()) supportTextView?.setCompoundDrawables(null, heartDrawable, null, null) - - gems84View?.seedsImageButton?.setOnClickListener { (this.activity as? GemPurchaseActivity)?.showSeedsPromo(getString(R.string.seeds_interstitial_gems), "store") } } override fun setupCheckout() { diff --git a/habitica.resources.example b/habitica.resources.example index 98f80f458..989a5322f 100644 --- a/habitica.resources.example +++ b/habitica.resources.example @@ -1,6 +1,3 @@ fabric_key=CHANGE_ME facebook_app_id=CHANGE_ME amplitude_app_id=CHANGE_ME -seeds_app_key=CHANGE_ME -seeds_interstitial_gems=CHANGE_ME -seeds_interstitial_sharing=CHANGE_ME diff --git a/seeds-sdk/LICENSE b/seeds-sdk/LICENSE deleted file mode 100755 index 75bb68d7b..000000000 --- a/seeds-sdk/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2010-2015 Matomy Media Group Ltd. - - 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. diff --git a/seeds-sdk/MoPub Client Licence.txt b/seeds-sdk/MoPub Client Licence.txt deleted file mode 100755 index 83beb8a16..000000000 --- a/seeds-sdk/MoPub Client Licence.txt +++ /dev/null @@ -1,10 +0,0 @@ -Copyright (c) 2011 MoPub Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name of MoPub nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/seeds-sdk/NOTICE b/seeds-sdk/NOTICE deleted file mode 100644 index 5f41fb7e9..000000000 --- a/seeds-sdk/NOTICE +++ /dev/null @@ -1,15 +0,0 @@ - Copyright 2015 MobFox -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. - -Code in the com.playseeds.android.sdk.inappmessaging.inappmessaging package modified from -MobFox Android SDK available at https://github.com/mobfox/MobFox-Android-SDK \ No newline at end of file diff --git a/seeds-sdk/build.gradle b/seeds-sdk/build.gradle deleted file mode 100755 index 240fb82e3..000000000 --- a/seeds-sdk/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -apply plugin: 'com.android.library' - -buildscript { - repositories { - jcenter() - } -} - -repositories { - mavenLocal() - mavenCentral() - maven { url "https://oss.sonatype.org/content/repositories/snapshots" } - maven { url 'https://maven.fabric.io/public' } - - // Material View Pager - maven { url "http://dl.bintray.com/florent37/maven" } - - // Markdown - maven { url "https://s3.amazonaws.com/repo.commonsware.com" } - maven { url "https://jitpack.io" } - maven { url "https://maven.google.com" } -} - -android { - compileSdkVersion sdk_version - buildToolsVersion build_tools_version - - defaultConfig { - minSdkVersion 14 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner 'com.playseeds.android.sdk.test.InstrumentationTestRunner' - testHandleProfiling true - testFunctionalTest true - multiDexEnabled true - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - debug { - testCoverageEnabled = true - } - } - - dexOptions { - javaMaxHeapSize "4g" - } - - lintOptions { - abortOnError false - } -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'org.glassfish:javax.json:1.0.4' - implementation 'com.google.android.gms:play-services:12.0.1' - implementation 'com.google.code.gson:gson:2.8.2' - implementation 'androidx.multidex:multidex:2.0.0' - implementation 'com.loopj.android:android-async-http:1.4.9' - implementation 'org.solovyev.android:checkout:1.2.1@aar' - androidTestImplementation 'org.mockito:mockito-core:2.8.9' - androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' - androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2' - testImplementation 'junit:junit:4.12' - testImplementation "org.robolectric:robolectric:3.8" - testImplementation "org.robolectric:shadows-multidex:3.8" -} diff --git a/seeds-sdk/gradle/wrapper/gradle-wrapper.jar b/seeds-sdk/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 13372aef5e24af05341d49695ee84e5f9b594659..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53636 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ⅅ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 H-+%o-(+5aQ diff --git a/seeds-sdk/gradle/wrapper/gradle-wrapper.properties b/seeds-sdk/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 122a0dca2..000000000 --- a/seeds-sdk/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Mon Dec 28 10:00:20 PST 2015 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/seeds-sdk/gradlew b/seeds-sdk/gradlew deleted file mode 100644 index 9d82f7891..000000000 --- a/seeds-sdk/gradlew +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/seeds-sdk/gradlew.bat b/seeds-sdk/gradlew.bat deleted file mode 100644 index aec99730b..000000000 --- a/seeds-sdk/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/seeds-sdk/proguard-rules.pro b/seeds-sdk/proguard-rules.pro deleted file mode 100755 index e9ece7221..000000000 --- a/seeds-sdk/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /projects/android-sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/seeds-sdk/src/main/AndroidManifest.xml b/seeds-sdk/src/main/AndroidManifest.xml deleted file mode 100755 index e9e1e5340..000000000 --- a/seeds-sdk/src/main/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/AdvertisingIdAdapter.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/AdvertisingIdAdapter.java deleted file mode 100755 index d9eb8d636..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/AdvertisingIdAdapter.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.playseeds.android.sdk; - -import android.content.Context; -import android.os.Handler; -import android.util.Log; - -import java.lang.reflect.Method; - -public class AdvertisingIdAdapter { - private static final String TAG = "AdvertisingIdAdapter"; - private final static String ADVERTISING_ID_CLIENT_CLASS_NAME = "com.google.android.gms.ads.identifier.AdvertisingIdClient"; - - public static boolean isAdvertisingIdAvailable() { - boolean advertisingIdAvailable = false; - try { - Class.forName(ADVERTISING_ID_CLIENT_CLASS_NAME); - advertisingIdAvailable = true; - } - catch (ClassNotFoundException ignored) {} - return advertisingIdAvailable; - } - - public static void setAdvertisingId(final Context context, final CountlyStore store, final DeviceId deviceId) { - new Thread(new Runnable() { - @Override - public void run() { - try { - deviceId.setId(DeviceId.Type.ADVERTISING_ID, getAdvertisingId(context)); - } catch (Throwable t) { - if (t.getCause() != null && t.getCause().getClass().toString().contains("GooglePlayServicesAvailabilityException")) { - // recoverable, let device ID be null, which will result in storing all requests to Seeds server - // and rerunning them whenever Advertising ID becomes available - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(TAG, "Advertising ID cannot be determined yet"); - } - } else if (t.getCause() != null && t.getCause().getClass().toString().contains("GooglePlayServicesNotAvailableException")) { - // non-recoverable, fallback to OpenUDID - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(TAG, "Advertising ID cannot be determined because Play Services are not available"); - } - deviceId.switchToIdType(DeviceId.Type.OPEN_UDID, context, store); - } else { - // unexpected - Log.e(TAG, "Couldn't get advertising ID", t); - } - } - } - }).start(); - } - - private static String getAdvertisingId(Context context) throws Throwable{ - final Class cls = Class.forName(ADVERTISING_ID_CLIENT_CLASS_NAME); - final Method getAdvertisingIdInfo = cls.getMethod("getAdvertisingIdInfo", Context.class); - Object info = getAdvertisingIdInfo.invoke(null, context); - if (info != null) { - final Method getId = info.getClass().getMethod("getId"); - Object id = getId.invoke(info); - return (String)id; - } - return null; - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/CertificateTrustManager.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/CertificateTrustManager.java deleted file mode 100644 index fff0b922d..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/CertificateTrustManager.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.playseeds.android.sdk; - -import android.util.Base64; -import android.util.Log; - -import java.io.ByteArrayInputStream; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -/** - * Created by artem on 11/06/15. Taken from https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning - */ - -// Many thanks to Nikolay Elenkov for feedback. -// Shamelessly based upon Moxie's example code (AOSP/Google did not offer code) -// http://www.thoughtcrime.org/blog/authenticity-is-broken-in-ssl-but-your-app-ha/ -public final class CertificateTrustManager implements X509TrustManager { - - // DER encoded public key - private final List keys; - - public CertificateTrustManager(List certificates) throws CertificateException { - if (certificates == null || certificates.size() == 0) { - throw new IllegalArgumentException("You must specify non-empty keys list"); - } - - this.keys = new ArrayList<>(); - for (String key : certificates) { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Certificate cert = cf.generateCertificate(new ByteArrayInputStream(Base64.decode(key, Base64.DEFAULT))); - this.keys.add(cert.getPublicKey().getEncoded()); - } - } - - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - if (chain == null) { - throw new IllegalArgumentException("PublicKeyManager: X509Certificate array is null"); - } - - if (!(chain.length > 0)) { - throw new IllegalArgumentException("PublicKeyManager: X509Certificate is empty"); - } - - if (!(null != authType && authType.equalsIgnoreCase("RSA"))) { - throw new CertificateException("PublicKeyManager: AuthType is not RSA"); - } - - // Perform customary SSL/TLS checks - TrustManagerFactory tmf; - try { - tmf = TrustManagerFactory.getInstance("X509"); - tmf.init((KeyStore) null); - - for (TrustManager trustManager : tmf.getTrustManagers()) { - ((X509TrustManager) trustManager).checkServerTrusted(chain, authType); - } - - } catch (Exception e) { - throw new CertificateException(e); - } - - byte server[] = chain[0].getPublicKey().getEncoded(); - - for (byte[] key : keys) { - if (Arrays.equals(key, server)) { - return; - } - } - - throw new CertificateException("Public keys didn't pass checks"); - } - - public void checkClientTrusted(X509Certificate[] xcs, String string) { - // throw new - // UnsupportedOperationException("checkClientTrusted: Not supported yet."); - } - - public X509Certificate[] getAcceptedIssuers() { - // throw new - // UnsupportedOperationException("getAcceptedIssuers: Not supported yet."); - return null; - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/ConnectionProcessor.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/ConnectionProcessor.java deleted file mode 100755 index 38b2779fb..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/ConnectionProcessor.java +++ /dev/null @@ -1,235 +0,0 @@ -/* -Copyright (c) 2012, 2013, 2014 Countly - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -package com.playseeds.android.sdk; - -import android.util.Log; - -import org.json.JSONObject; - -import java.io.BufferedInputStream; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; - -/** - * ConnectionProcessor is a Runnable that is executed on a background - * thread to submit session & event data to a Count.ly server. - * - * NOTE: This class is only public to facilitate unit testing, because - * of this bug in dexmaker: https://code.google.com/p/dexmaker/issues/detail?id=34 - */ -public class ConnectionProcessor implements Runnable { - private static final int CONNECT_TIMEOUT_IN_MILLISECONDS = 30000; - private static final int READ_TIMEOUT_IN_MILLISECONDS = 30000; - - private final CountlyStore store_; - private final DeviceId deviceId_; - private final String serverURL_; - private final SSLContext sslContext_; - - ConnectionProcessor(final String serverURL, final CountlyStore store, final DeviceId deviceId, final SSLContext sslContext) { - serverURL_ = serverURL; - store_ = store; - deviceId_ = deviceId; - sslContext_ = sslContext; - - // HTTP connection reuse which was buggy pre-froyo - System.setProperty("http.keepAlive", "false"); - } - - public URLConnection urlConnectionForEventData(final String eventData) throws IOException { - String urlStr = serverURL_ + "/i?"; - if(!eventData.contains("&crash=")) - urlStr += eventData; - final URL url = new URL(urlStr); - final HttpURLConnection conn; - if (Seeds.publicKeyPinCertificates == null) { - conn = (HttpURLConnection)url.openConnection(); - } else { - HttpsURLConnection c = (HttpsURLConnection)url.openConnection(); - c.setSSLSocketFactory(sslContext_.getSocketFactory()); - conn = c; - } - conn.setConnectTimeout(CONNECT_TIMEOUT_IN_MILLISECONDS); - conn.setReadTimeout(READ_TIMEOUT_IN_MILLISECONDS); - conn.setUseCaches(false); - conn.setDoInput(true); - String picturePath = UserData.getPicturePathFromQuery(url); - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.d(Seeds.TAG, "Got picturePath: " + picturePath); - } - if(!picturePath.equals("")){ - //Uploading files: - //http://stackoverflow.com/questions/2793150/how-to-use-java-net-urlconnection-to-fire-and-handle-http-requests - - File binaryFile = new File(picturePath); - conn.setDoOutput(true); - // Just generate some unique random value. - String boundary = Long.toHexString(System.currentTimeMillis()); - // Line separator required by multipart/form-data. - String CRLF = "\r\n"; - String charset = "UTF-8"; - conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); - OutputStream output = conn.getOutputStream(); - PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true); - // Send binary file. - writer.append("--" + boundary).append(CRLF); - writer.append("Content-Disposition: form-data; name=\"binaryFile\"; filename=\"" + binaryFile.getName() + "\"").append(CRLF); - writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(binaryFile.getName())).append(CRLF); - writer.append("Content-Transfer-Encoding: binary").append(CRLF); - writer.append(CRLF).flush(); - FileInputStream fileInputStream = new FileInputStream(binaryFile); - byte[] buffer = new byte[1024]; - int len; - while ((len = fileInputStream.read(buffer)) != -1) { - output.write(buffer, 0, len); - } - output.flush(); // Important before continuing with writer! - writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary. - fileInputStream.close(); - - // End of multipart/form-data. - writer.append("--" + boundary + "--").append(CRLF).flush(); - } - else if(eventData.contains("&crash=")){ - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.d(Seeds.TAG, "Using post because of crash"); - } - conn.setDoOutput(true); - conn.setRequestMethod("POST"); - OutputStream os = conn.getOutputStream(); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); - writer.write(eventData); - writer.flush(); - writer.close(); - os.close(); - } - else{ - conn.setDoOutput(false); - } - return conn; - } - - @Override - public void run() { - while (true) { - final String[] storedEvents = store_.connections(); - if (storedEvents == null || storedEvents.length == 0) { - // currently no data to send, we are done for now - break; - } - - // get first event from collection - if (deviceId_.getId() == null) { - // When device ID is supplied by OpenUDID or by Google Advertising ID. - // In some cases it might take time for them to initialize. So, just wait for it. - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(Seeds.TAG, "No Device ID available yet, skipping request " + storedEvents[0]); - } - break; - } - final String eventData = storedEvents[0] + "&device_id=" + deviceId_.getId(); - - URLConnection conn = null; - BufferedInputStream responseStream = null; - try { - // initialize and open connection - conn = urlConnectionForEventData(eventData); - conn.connect(); - - // consume response stream - responseStream = new BufferedInputStream(conn.getInputStream()); - final ByteArrayOutputStream responseData = new ByteArrayOutputStream(256); // big enough to handle success response without reallocating - int c; - while ((c = responseStream.read()) != -1) { - responseData.write(c); - } - - // response code has to be 2xx to be considered a success - boolean success = true; - if (conn instanceof HttpURLConnection) { - final HttpURLConnection httpConn = (HttpURLConnection) conn; - final int responseCode = httpConn.getResponseCode(); - success = responseCode >= 200 && responseCode < 300; - if (!success && Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(Seeds.TAG, "HTTP error response code was " + responseCode + " from submitting event data: " + eventData); - } - } - - // HTTP response code was good, check response JSON contains {"result":"Success"} - if (success) { - final JSONObject responseDict = new JSONObject(responseData.toString("UTF-8")); - success = responseDict.optString("result").equalsIgnoreCase("success"); - if (!success && Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(Seeds.TAG, "Response from Seeds server did not report success, it was: " + responseData.toString("UTF-8")); - } - } - - if (success) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.d(Seeds.TAG, "ok ->" + eventData); - } - - // successfully submitted event data to Count.ly server, so remove - // this one from the stored events collection - store_.removeConnection(storedEvents[0]); - } - else { - // warning was logged above, stop processing, let next tick take care of retrying - break; - } - } - catch (Exception e) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(Seeds.TAG, "Got exception while trying to submit event data: " + eventData, e); - } - // if exception occurred, stop processing, let next tick take care of retrying - break; - } - finally { - // free connection resources - if (responseStream != null) { - try { responseStream.close(); } catch (IOException ignored) {} - } - if (conn != null && conn instanceof HttpURLConnection) { - ((HttpURLConnection)conn).disconnect(); - } - } - } - } - - // for unit testing - String getServerURL() { return serverURL_; } - CountlyStore getCountlyStore() { return store_; } - DeviceId getDeviceId() { return deviceId_; } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/ConnectionQueue.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/ConnectionQueue.java deleted file mode 100755 index 2f2bb9c55..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/ConnectionQueue.java +++ /dev/null @@ -1,318 +0,0 @@ -/* -Copyright (c) 2012, 2013, 2014 Countly - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -package com.playseeds.android.sdk; - -import android.content.Context; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -/** - * ConnectionQueue queues session and event data and periodically sends that data to - * a Count.ly server on a background thread. - * - * None of the methods in this class are synchronized because access to this class is - * controlled by the Seeds singleton, which is synchronized. - * - * NOTE: This class is only public to facilitate unit testing, because - * of this bug in dexmaker: https://code.google.com/p/dexmaker/issues/detail?id=34 - */ -public class ConnectionQueue { - private CountlyStore store_; - private ExecutorService executor_; - private String appKey_; - private Context context_; - private String serverURL_; - private Future connectionProcessorFuture_; - private DeviceId deviceId_; - private SSLContext sslContext_; - - // Getters are for unit testing - String getAppKey() { - return appKey_; - } - - void setAppKey(final String appKey) { - appKey_ = appKey; - } - - Context getContext() { - return context_; - } - - void setContext(final Context context) { - context_ = context; - } - - String getServerURL() { - return serverURL_; - } - - void setServerURL(final String serverURL) { - serverURL_ = serverURL; - - if (Seeds.publicKeyPinCertificates == null) { - sslContext_ = null; - } else { - try { - TrustManager tm[] = { new CertificateTrustManager(Seeds.publicKeyPinCertificates) }; - sslContext_ = SSLContext.getInstance("TLS"); - sslContext_.init(null, tm, null); - } catch (Throwable e) { - throw new IllegalStateException(e); - } - } - } - - CountlyStore getCountlyStore() { - return store_; - } - - void setCountlyStore(final CountlyStore countlyStore) { - store_ = countlyStore; - } - - DeviceId getDeviceId() { return deviceId_; } - - public void setDeviceId(DeviceId deviceId) { - this.deviceId_ = deviceId; - } - - /** - * Checks internal state and throws IllegalStateException if state is invalid to begin use. - * @throws IllegalStateException if context, app key, store, or server URL have not been set - */ - void checkInternalState() { - if (context_ == null) { - throw new IllegalStateException("context has not been set"); - } - if (appKey_ == null || appKey_.length() == 0) { - throw new IllegalStateException("app key has not been set"); - } - if (store_ == null) { - throw new IllegalStateException("countly store has not been set"); - } - if (serverURL_ == null || !Seeds.isValidURL(serverURL_)) { - throw new IllegalStateException("server URL is not valid"); - } - if (Seeds.publicKeyPinCertificates != null && !serverURL_.startsWith("https")) { - throw new IllegalStateException("server must start with https once you specified public keys"); - } - } - - /** - * Records a session start event for the app and sends it to the server. - * @throws IllegalStateException if context, app key, store, or server URL have not been set - */ - void beginSession() { - checkInternalState(); - final String data = "app_key=" + appKey_ - + "×tamp=" + Seeds.currentTimestamp() - + "&sdk_version=" + Seeds.COUNTLY_SDK_VERSION_STRING - + "&begin_session=1" - + "&metrics=" + DeviceInfo.getMetrics(context_); - - store_.addConnection(data); - - tick(); - } - - /** - * Records a session duration event for the app and sends it to the server. This method does nothing - * if passed a negative or zero duration. - * @param duration duration in seconds to extend the current app session, should be more than zero - * @throws IllegalStateException if context, app key, store, or server URL have not been set - */ - void updateSession(final int duration) { - checkInternalState(); - if (duration > 0) { - final String data = "app_key=" + appKey_ - + "×tamp=" + Seeds.currentTimestamp() - + "&session_duration=" + duration - + "&location=" + getCountlyStore().getAndRemoveLocation(); - - store_.addConnection(data); - - tick(); - } - } - - public void tokenSession(String token, Seeds.CountlyMessagingMode mode) { - checkInternalState(); - - final String data = "app_key=" + appKey_ - + "&" + "timestamp=" + Seeds.currentTimestamp() - + "&" + "token_session=1" - + "&" + "android_token=" + token - + "&" + "test_mode=" + (mode == Seeds.CountlyMessagingMode.TEST ? 2 : 0) - + "&" + "locale=" + DeviceInfo.getLocale(); - - // To ensure begin_session will be fully processed by the server before token_session - final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor(); - worker.schedule(new Runnable() { - @Override - public void run() { - store_.addConnection(data); - tick(); - } - }, 10, TimeUnit.SECONDS); - } - - /** - * Records a session end event for the app and sends it to the server. Duration is only included in - * the session end event if it is more than zero. - * @param duration duration in seconds to extend the current app session - * @throws IllegalStateException if context, app key, store, or server URL have not been set - */ - void endSession(final int duration) { - checkInternalState(); - String data = "app_key=" + appKey_ - + "×tamp=" + Seeds.currentTimestamp() - + "&end_session=1"; - if (duration > 0) { - data += "&session_duration=" + duration; - } - - store_.addConnection(data); - - tick(); - } - - /** - * Send user data to the server. - * @throws java.lang.IllegalStateException if context, app key, store, or server URL have not been set - */ - void sendUserData() { - checkInternalState(); - String userdata = UserData.getDataForRequest(); - - if(!userdata.equals("")){ - String data = "app_key=" + appKey_ - + "×tamp=" + Seeds.currentTimestamp() - + userdata; - store_.addConnection(data); - - tick(); - } - } - - /** - * Attribute installation to Seeds server. - * @param referrer query parameters - * @throws java.lang.IllegalStateException if context, app key, store, or server URL have not been set - */ - void sendReferrerData(String referrer) { - checkInternalState(); - - if(referrer != null){ - String data = "app_key=" + appKey_ - + "×tamp=" + Seeds.currentTimestamp() - + referrer; - store_.addConnection(data); - - tick(); - } - } - - /** - * Reports a crash with device data to the server. - * @throws IllegalStateException if context, app key, store, or server URL have not been set - */ - void sendCrashReport(String error, boolean nonfatal) { - checkInternalState(); - final String data = "app_key=" + appKey_ - + "×tamp=" + Seeds.currentTimestamp() - + "&sdk_version=" + Seeds.COUNTLY_SDK_VERSION_STRING - + "&crash=" + CrashDetails.getCrashData(context_, error, nonfatal); - - store_.addConnection(data); - - tick(); - } - - /** - * Records the specified events and sends them to the server. - * @param events URL-encoded JSON string of event data - * @throws IllegalStateException if context, app key, store, or server URL have not been set - */ - void recordEvents(final String events) { - checkInternalState(); - final String data = "app_key=" + appKey_ - + "×tamp=" + Seeds.currentTimestamp() - + "&events=" + events; - - store_.addConnection(data); - - tick(); - } - - /** - * Records the specified events and sends them to the server. - * @param events URL-encoded JSON string of event data - * @throws IllegalStateException if context, app key, store, or server URL have not been set - */ - void recordLocation(final String events) { - checkInternalState(); - final String data = "app_key=" + appKey_ - + "×tamp=" + Seeds.currentTimestamp() - + "&events=" + events; - - store_.addConnection(data); - - tick(); - } - - /** - * Ensures that an executor has been created for ConnectionProcessor instances to be submitted to. - */ - void ensureExecutor() { - if (executor_ == null) { - executor_ = Executors.newSingleThreadExecutor(); - } - } - - /** - * Starts ConnectionProcessor instances running in the background to - * process the local connection queue data. - * Does nothing if there is connection queue data or if a ConnectionProcessor - * is already running. - */ - void tick() { - if (!store_.isEmptyConnections() && (connectionProcessorFuture_ == null || connectionProcessorFuture_.isDone())) { - ensureExecutor(); - connectionProcessorFuture_ = executor_.submit(new ConnectionProcessor(serverURL_, store_, deviceId_, sslContext_)); - } - } - - // for unit testing - ExecutorService getExecutor() { return executor_; } - void setExecutor(final ExecutorService executor) { executor_ = executor; } - Future getConnectionProcessorFuture() { return connectionProcessorFuture_; } - void setConnectionProcessorFuture(final Future connectionProcessorFuture) { connectionProcessorFuture_ = connectionProcessorFuture; } - -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/CountlyStore.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/CountlyStore.java deleted file mode 100755 index 93df55ed7..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/CountlyStore.java +++ /dev/null @@ -1,276 +0,0 @@ -/* -Copyright (c) 2012, 2013, 2014 Countly - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -package com.playseeds.android.sdk; - -import android.content.Context; -import android.content.SharedPreferences; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; - -/** - * This class provides a persistence layer for the local event & connection queues. - * - * The "read" methods in this class are not synchronized, because the underlying data store - * provides thread-safe reads. The "write" methods in this class are synchronized, because - * 1) they often read a list of items, modify the list, and then commit it back to the underlying - * data store, and 2) while the Seeds singleton is synchronized to ensure only a single writer - * at a time from the public API side, the internal implementation has a background thread that - * submits data to a Seeds server, and it writes to this store as well. - * - * NOTE: This class is only public to facilitate unit testing, because - * of this bug in dexmaker: https://code.google.com/p/dexmaker/issues/detail?id=34 - */ -public class CountlyStore { - private static final String PREFERENCES = "COUNTLY_STORE"; - private static final String DELIMITER = ":::"; - private static final String CONNECTIONS_PREFERENCE = "CONNECTIONS"; - private static final String EVENTS_PREFERENCE = "EVENTS"; - private static final String LOCATION_PREFERENCE = "LOCATION"; - - private final SharedPreferences preferences_; - - /** - * Constructs a CountlyStore object. - * @param context used to retrieve storage meta data, must not be null. - * @throws IllegalArgumentException if context is null - */ - CountlyStore(final Context context) { - if (context == null) { - throw new IllegalArgumentException("must provide valid context"); - } - preferences_ = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE); - } - - /** - * Returns an unsorted array of the current stored connections. - */ - public String[] connections() { - final String joinedConnStr = preferences_.getString(CONNECTIONS_PREFERENCE, ""); - return joinedConnStr.length() == 0 ? new String[0] : joinedConnStr.split(DELIMITER); - } - - /** - * Returns an unsorted array of the current stored event JSON strings. - */ - public String[] events() { - final String joinedEventsStr = preferences_.getString(EVENTS_PREFERENCE, ""); - return joinedEventsStr.length() == 0 ? new String[0] : joinedEventsStr.split(DELIMITER); - } - - /** - * Returns a list of the current stored events, sorted by timestamp from oldest to newest. - */ - public List eventsList() { - final String[] array = events(); - final List events = new ArrayList<>(array.length); - for (String s : array) { - try { - final Event event = Event.fromJSON(new JSONObject(s)); - if (event != null) { - events.add(event); - } - } catch (JSONException ignored) { - // should not happen since JSONObject is being constructed from previously stringified JSONObject - // events -> json objects -> json strings -> storage -> json strings -> here - } - } - // order the events from least to most recent - Collections.sort(events, new Comparator() { - @Override - public int compare(final Event e1, final Event e2) { - return e1.timestamp - e2.timestamp; - } - }); - return events; - } - - /** - * Returns true if no connections are current stored, false otherwise. - */ - public boolean isEmptyConnections() { - return preferences_.getString(CONNECTIONS_PREFERENCE, "").length() == 0; - } - - /** - * Adds a connection to the local store. - * @param str the connection to be added, ignored if null or empty - */ - public synchronized void addConnection(final String str) { - if (str != null && str.length() > 0) { - final List connections = new ArrayList<>(Arrays.asList(connections())); - connections.add(str); - preferences_.edit().putString(CONNECTIONS_PREFERENCE, join(connections, DELIMITER)).commit(); - } - } - - /** - * Removes a connection from the local store. - * @param str the connection to be removed, ignored if null or empty, - * or if a matching connection cannot be found - */ - public synchronized void removeConnection(final String str) { - if (str != null && str.length() > 0) { - final List connections = new ArrayList<>(Arrays.asList(connections())); - if (connections.remove(str)) { - preferences_.edit().putString(CONNECTIONS_PREFERENCE, join(connections, DELIMITER)).commit(); - } - } - } - - /** - * Adds a custom event to the local store. - * @param event event to be added to the local store, must not be null - */ - void addEvent(final Event event) { - final List events = eventsList(); - events.add(event); - preferences_.edit().putString(EVENTS_PREFERENCE, joinEvents(events, DELIMITER)).commit(); - } - - /** - * Sets location of user and sends it with next request - */ - void setLocation(final double lat, final double lon) { - if (preferences_ != null) { - preferences_.edit().putString(LOCATION_PREFERENCE, lat + "," + lon).commit(); - } - } - - /** - * Get location or empty string in case if no location is specified - */ - String getAndRemoveLocation() { - String location = null; - if (preferences_ != null) { - location = preferences_.getString(LOCATION_PREFERENCE, ""); - if (!location.equals("")) { - preferences_.edit().remove(LOCATION_PREFERENCE).commit(); - } - } - return location; - } - - /** - * Adds a custom event to the local store. - * @param key name of the custom event, required, must not be the empty string - * @param segmentation segmentation values for the custom event, may be null - * @param timestamp timestamp (seconds since 1970) in GMT when the event occurred - * @param count count associated with the custom event, should be more than zero - * @param sum sum associated with the custom event, if not used, pass zero. - * NaN and infinity values will be quietly ignored. - */ - public synchronized void addEvent(final String key, final Map segmentation, final int timestamp, final int count, final double sum) { - final Event event = new Event(); - event.key = key; - event.segmentation = segmentation; - event.timestamp = timestamp; - event.count = count; - event.sum = sum; - - addEvent(event); - } - - /** - * Removes the specified events from the local store. Does nothing if the event collection - * is null or empty. - * @param eventsToRemove collection containing the events to remove from the local store - */ - public synchronized void removeEvents(final Collection eventsToRemove) { - if (eventsToRemove != null && eventsToRemove.size() > 0) { - final List events = eventsList(); - if (events.removeAll(eventsToRemove)) { - preferences_.edit().putString(EVENTS_PREFERENCE, joinEvents(events, DELIMITER)).commit(); - } - } - } - - /** - * Converts a collection of Event objects to URL-encoded JSON to a string, with each - * event JSON string delimited by the specified delimiter. - * @param collection events to join into a delimited string - * @param delimiter delimiter to use, should not be something that can be found in URL-encoded JSON string - */ - static String joinEvents(final Collection collection, final String delimiter) { - final List strings = new ArrayList<>(); - for (Event e : collection) { - strings.add(e.toJSON().toString()); - } - return join(strings, delimiter); - } - - /** - * Joins all the strings in the specified collection into a single string with the specified delimiter. - */ - static String join(final Collection collection, final String delimiter) { - final StringBuilder builder = new StringBuilder(); - - int i = 0; - for (String s : collection) { - builder.append(s); - if (++i < collection.size()) { - builder.append(delimiter); - } - } - - return builder.toString(); - } - - /** - * Retrieves a preference from local store. - * @param key the preference key - */ - public synchronized String getPreference(final String key) { - return preferences_.getString(key, null); - } - - /** - * Adds a preference to local store. - * @param key the preference key - * @param value the preference value, supply null value to remove preference - */ - public synchronized void setPreference(final String key, final String value) { - if (value == null) { - preferences_.edit().remove(key).commit(); - } else { - preferences_.edit().putString(key, value).commit(); - } - } - - // for unit testing - synchronized void clear() { - if (preferences_ != null) { - SharedPreferences.Editor prefsEditor = preferences_.edit(); - prefsEditor.remove(EVENTS_PREFERENCE); - prefsEditor.remove(CONNECTIONS_PREFERENCE); - prefsEditor.commit(); - } - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/CrashDetails.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/CrashDetails.java deleted file mode 100644 index 7e91bbef6..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/CrashDetails.java +++ /dev/null @@ -1,405 +0,0 @@ -/* -Copyright (c) 2012, 2013, 2014 Countly - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -package com.playseeds.android.sdk; - -import android.app.ActivityManager; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.FeatureInfo; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.media.AudioManager; -import android.net.ConnectivityManager; -import android.os.BatteryManager; -import android.os.Build; -import android.os.Environment; -import android.os.StatFs; -import android.util.Log; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * This class provides several static methods to retrieve information about - * the current device and operating environment for crash reporting purposes. - * - */ -public class CrashDetails { - private static ArrayList logs = new ArrayList<>(); - private static int startTime = Seeds.currentTimestamp(); - private static Map customSegments = null; - private static boolean inBackground = true; - private static long totalMemory = 0; - - private static long getTotalRAM() { - if(totalMemory == 0) { - RandomAccessFile reader; - String load; - - try { - reader = new RandomAccessFile("/proc/meminfo", "r"); - load = reader.readLine(); - - // Get the Number value from the string - Pattern p = Pattern.compile("(\\d+)"); - Matcher m = p.matcher(load); - String value = ""; - while (m.find()) { - value = m.group(1); - } - reader.close(); - - totalMemory = Long.parseLong(value) / 1024; - } catch (IOException ex) { - ex.printStackTrace(); - } - } - return totalMemory; - } - - /** - * Notify when app is in foreground - */ - static void inForeground() { - inBackground = false; - } - - /** - * Notify when app is in background - */ - static void inBackground() { - inBackground = true; - } - - /** - * Returns app background state - */ - static String isInBackground() { - return Boolean.toString(inBackground); - } - - /** - * Adds a record in the log - */ - static void addLog(String record) { - logs.add(record); - } - - /** - * Returns the collected logs. - */ - static String getLogs() { - String allLogs = ""; - - for (String s : logs) - { - allLogs += s + "\n"; - } - logs.clear(); - return allLogs; - } - - /** - * Adds developer provided custom segments for crash, - * like versions of dependency libraries. - */ - static void setCustomSegments(Map segments) { - customSegments = new HashMap<>(); - customSegments.putAll(segments); - } - - /** - * Get custom segments json string - */ - static JSONObject getCustomSegments() { - if(customSegments != null && !customSegments.isEmpty()) - return new JSONObject(customSegments); - else - return null; - } - - /** - * Returns the current device manufacturer. - */ - static String getManufacturer() { - return android.os.Build.MANUFACTURER; - } - - /** - * Returns the current device cpu. - */ - static String getCpu() { - if(android.os.Build.VERSION.SDK_INT < 21 ) - return android.os.Build.CPU_ABI; - else - return Build.SUPPORTED_ABIS[0]; - } - - /** - * Returns the current device openGL version. - */ - static String getOpenGL(Context context) { - PackageManager packageManager = context.getPackageManager(); - FeatureInfo[] featureInfos = packageManager.getSystemAvailableFeatures(); - if (featureInfos != null && featureInfos.length > 0) { - for (FeatureInfo featureInfo : featureInfos) { - // Null feature name means this feature is the open gl es version feature. - if (featureInfo.name == null) { - if (featureInfo.reqGlEsVersion != FeatureInfo.GL_ES_VERSION_UNDEFINED) { - return Integer.toString((featureInfo.reqGlEsVersion & 0xffff0000) >> 16); - } else { - return "1"; // Lack of property means OpenGL ES version 1 - } - } - } - } - return "1"; - } - - /** - * Returns the current device RAM amount. - */ - static String getRamCurrent(Context context) { - ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); - ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - activityManager.getMemoryInfo(mi); - return Long.toString(getTotalRAM() - (mi.availMem / 1048576L)); - } - - /** - * Returns the total device RAM amount. - */ - static String getRamTotal() { - return Long.toString(getTotalRAM()); - } - - /** - * Returns the current device disk space. - */ - static String getDiskCurrent() { - if(android.os.Build.VERSION.SDK_INT < 18 ) { - StatFs statFs = new StatFs(Environment.getRootDirectory().getAbsolutePath()); - long total = (statFs.getBlockCount() * statFs.getBlockSize()); - long free = (statFs.getAvailableBlocks() * statFs.getBlockSize()); - return Long.toString((total - free)/ 1048576L); - } else{ - StatFs statFs = new StatFs(Environment.getRootDirectory().getAbsolutePath()); - long total = (statFs.getBlockCountLong() * statFs.getBlockSizeLong()); - long free = (statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong()); - return Long.toString((total - free) / 1048576L); - } - } - - /** - * Returns the current device disk space. - */ - static String getDiskTotal() { - if(android.os.Build.VERSION.SDK_INT < 18 ) { - StatFs statFs = new StatFs(Environment.getRootDirectory().getAbsolutePath()); - long total = (statFs.getBlockCount() * statFs.getBlockSize()); - return Long.toString(total/ 1048576L); - } else{ - StatFs statFs = new StatFs(Environment.getRootDirectory().getAbsolutePath()); - long total = (statFs.getBlockCountLong() * statFs.getBlockSizeLong()); - return Long.toString(total/ 1048576L); - } - } - - /** - * Returns the current device battery level. - */ - static String getBatteryLevel(Context context) { - Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - assert batteryIntent != null; - int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - - // Error checking that probably isn't needed but I added just in case. - if (level > -1 && scale > 0) { - return Float.toString(((float) level / (float) scale) * 100.0f); - } - - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(Seeds.TAG, "Can't get battery level"); - } - - return null; - } - - /** - * Get app's running time before crashing. - */ - static String getRunningTime() { - return Integer.toString(Seeds.currentTimestamp() - startTime); - } - - /** - * Returns the current device orientation. - */ - static String getOrientation(Context context) { - int orientation = context.getResources().getConfiguration().orientation; - switch(orientation) - { - case Configuration.ORIENTATION_LANDSCAPE: - return "Landscape"; - case Configuration.ORIENTATION_PORTRAIT: - return "Portrait"; - case Configuration.ORIENTATION_SQUARE: - return "Square"; - case Configuration.ORIENTATION_UNDEFINED: - return "Unknown"; - default: - return null; - } - } - - /** - * Checks if device is rooted. - */ - static String isRooted() { - String[] paths = { "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", - "/system/bin/failsafe/su", "/data/local/su" }; - for (String path : paths) { - if (new File(path).exists()) return "true"; - } - return "false"; - } - - /** - * Checks if device is online. - */ - static String isOnline(Context context) { - try { - ConnectivityManager conMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (conMgr != null && conMgr.getActiveNetworkInfo() != null && conMgr.getActiveNetworkInfo().isAvailable() - && conMgr.getActiveNetworkInfo().isConnected()) { - - return "true"; - } - return "false"; - } - catch(Exception e){ - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(Seeds.TAG, "Got exception determining connectivity", e); - } - } - return null; - } - - /** - * Checks if device is muted. - */ - static String isMuted(Context context) { - AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - switch( audio.getRingerMode() ){ - case AudioManager.RINGER_MODE_SILENT: - return "true"; - case AudioManager.RINGER_MODE_VIBRATE: - return "true"; - default: - return "false"; - } - } - - /** - * Returns a URL-encoded JSON string containing the device crash report - * See the following link for more info: - * http://resources.count.ly/v1.0/docs/i - */ - static String getCrashData(final Context context, String error, Boolean nonfatal) { - final JSONObject json = new JSONObject(); - - fillJSONIfValuesNotEmpty(json, - "_error", error, - "_nonfatal", Boolean.toString(nonfatal), - "_logs", getLogs(), - "_device", DeviceInfo.getDevice(), - "_os", DeviceInfo.getOS(), - "_os_version", DeviceInfo.getOSVersion(), - "_resolution", DeviceInfo.getResolution(context), - "_app_version", DeviceInfo.getAppVersion(context), - "_manufacture", getManufacturer(), - "_cpu", getCpu(), - "_opengl", getOpenGL(context), - "_ram_current", getRamCurrent(context), - "_ram_total", getRamTotal(), - "_disk_current", getDiskCurrent(), - "_disk_total", getDiskTotal(), - "_bat", getBatteryLevel(context), - "_run", getRunningTime(), - "_orientation", getOrientation(context), - "_root", isRooted(), - "_online", isOnline(context), - "_muted", isMuted(context), - "_background", isInBackground() - ); - - try { - json.put("_custom", getCustomSegments()); - } catch (JSONException e) { - //no custom segments - } - String result = json.toString(); - - try { - result = java.net.URLEncoder.encode(result, "UTF-8"); - } catch (UnsupportedEncodingException ignored) { - // should never happen because Android guarantees UTF-8 support - } - - return result; - } - - /** - * Utility method to fill JSONObject with supplied objects for supplied keys. - * Fills json only with non-null and non-empty key/value pairs. - * @param json JSONObject to fill - * @param objects varargs of this kind: key1, value1, key2, value2, ... - */ - static void fillJSONIfValuesNotEmpty(final JSONObject json, final String ... objects) { - try { - if (objects.length > 0 && objects.length % 2 == 0) { - for (int i = 0; i < objects.length; i += 2) { - final String key = objects[i]; - final String value = objects[i + 1]; - if (value != null && value.length() > 0) { - json.put(key, value); - } - } - } - } catch (JSONException ignored) { - // shouldn't ever happen when putting String objects into a JSONObject, - // it can only happen when putting NaN or INFINITE doubles or floats into it - } - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/DeviceId.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/DeviceId.java deleted file mode 100755 index f65953e4d..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/DeviceId.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.playseeds.android.sdk; - -import android.content.Context; -import android.util.Log; - - -public class DeviceId { - private String id; - private Type type; - private static final String TAG = "DeviceId"; - private static final String PREFERENCE_KEY_ID_TYPE = "ly.count.android.api.DeviceId.type"; - - /** - * Enum used throughout Seeds which controls what kind of ID Seeds should use. - */ - public enum Type { - DEVELOPER_SUPPLIED, - OPEN_UDID, - ADVERTISING_ID, - } - - /** - * Initialize DeviceId with Type of OPEN_UDID or ADVERTISING_ID - * @param type type of ID generation strategy - */ - public DeviceId(Type type) { - if (type == null) { - throw new IllegalStateException("Please specify DeviceId.Type, that is which type of device ID generation you want to use"); - } else if (type == Type.DEVELOPER_SUPPLIED) { - throw new IllegalStateException("Please use another DeviceId constructor for device IDs supplied by developer"); - } - this.type = type; - } - - /** - * Initialize DeviceId with Developer-supplied id string - * @param developerSuppliedId Device ID string supplied by developer - */ - public DeviceId(String developerSuppliedId) { - if (developerSuppliedId == null || "".equals(developerSuppliedId)) { - throw new IllegalStateException("Please make sure that device ID is not null or empty"); - } - this.type = Type.DEVELOPER_SUPPLIED; - this.id = developerSuppliedId; - } - - /** - * Initialize device ID generation, that is start up required services and send requests. - * Device ID is expected to be available after some time. - * In some cases, Seeds can override ID generation strategy to other one, for example when - * Google Play Services are not available and user chose Advertising ID strategy, it will fall - * back to OpenUDID - * @param context Context to use - * @param store CountlyStore to store configuration in - * @param raiseExceptions whether to raise exceptions in case of illegal state or not - */ - public void init(Context context, CountlyStore store, boolean raiseExceptions) { - Type overriddenType = retrieveOverriddenType(store); - - // Some time ago some ID generation strategy was not available and SDK fell back to - // some other strategy. We still have to use that strategy. - if (overriddenType != null && overriddenType != type) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(TAG, "Overridden device ID generation strategy detected: " + overriddenType + ", using it instead of " + this.type); - } - type = overriddenType; - } - - switch (type) { - case DEVELOPER_SUPPLIED: - // no initialization for developer id - break; - case OPEN_UDID: - if (OpenUDIDAdapter.isOpenUDIDAvailable()) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(TAG, "Using OpenUDID"); - } - if (!OpenUDIDAdapter.isInitialized()) { - OpenUDIDAdapter.sync(context); - } - } else { - if (raiseExceptions) - throw new IllegalStateException("OpenUDID is not available, please make sure that you have it in your classpath"); - } - break; - case ADVERTISING_ID: - if (AdvertisingIdAdapter.isAdvertisingIdAvailable()) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(TAG, "Using Advertising ID"); - } - AdvertisingIdAdapter.setAdvertisingId(context, store, this); - } else if (OpenUDIDAdapter.isOpenUDIDAvailable()) { - // Fall back to OpenUDID on devices without google play services set up - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(TAG, "Advertising ID is not available, falling back to OpenUDID"); - } - if (!OpenUDIDAdapter.isInitialized()) { - OpenUDIDAdapter.sync(context); - } - } else { - // just do nothing, without Advertising ID and OpenUDID this user is lost for Seeds - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(TAG, "Advertising ID is not available, neither OpenUDID is"); - } - if (raiseExceptions) - throw new IllegalStateException("OpenUDID is not available, please make sure that you have it in your classpath"); - } - break; - } - } - - private void storeOverriddenType(CountlyStore store, Type type) { - // Using strings is safer when it comes to extending Enum values list - store.setPreference(PREFERENCE_KEY_ID_TYPE, type == null ? null : type.toString()); - } - - private Type retrieveOverriddenType(CountlyStore store) { - Type oldType; - - // Using strings is safer when it comes to extending Enum values list - String oldTypeString = store.getPreference(PREFERENCE_KEY_ID_TYPE); - if (oldTypeString == null) { - oldType = null; - } else if (oldTypeString.equals(Type.DEVELOPER_SUPPLIED.toString())) { - oldType = Type.DEVELOPER_SUPPLIED; - } else if (oldTypeString.equals(Type.OPEN_UDID.toString())) { - oldType = Type.OPEN_UDID; - } else if (oldTypeString.equals(Type.ADVERTISING_ID.toString())) { - oldType = Type.ADVERTISING_ID; - } else { - oldType = null; - } - return oldType; - } - - protected void switchToIdType(Type type, Context context, CountlyStore store) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(TAG, "Switching to device ID generation strategy " + type + " from " + this.type); - } - this.type = type; - storeOverriddenType(store, type); - init(context, store, false); - } - - public String getId() { - if (id == null && type == Type.OPEN_UDID) { - id = OpenUDIDAdapter.getOpenUDID(); - } - return id; - } - - protected void setId(Type type, String id) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(TAG, "Device ID is " + id + " (type " + type + ")"); - } - this.type = type; - this.id = id; - } - - public Type getType() { - return type; - } - - /** - * Helper method for null safe comparison of current device ID and the one supplied to Seeds.init - * @return true if supplied device ID equal to the one registered before - */ - static boolean deviceIDEqualsNullSafe(final String id, Type type, final DeviceId deviceId) { - if (type == null || type == Type.DEVELOPER_SUPPLIED) { - final String deviceIdId = (deviceId == null) ? null : deviceId.getId(); - return (deviceIdId == null && id == null) || (deviceIdId != null && deviceIdId.equals(id)); - } else { - return true; - } - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/DeviceInfo.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/DeviceInfo.java deleted file mode 100755 index 36f94e15d..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/DeviceInfo.java +++ /dev/null @@ -1,255 +0,0 @@ -/* -Copyright (c) 2012, 2013, 2014 Countly - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -package com.playseeds.android.sdk; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.telephony.TelephonyManager; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; -import android.view.WindowManager; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.UnsupportedEncodingException; -import java.util.Locale; - -/** - * This class provides several static methods to retrieve information about - * the current device and operating environment. - * - * It is important to call setDeviceID early, before logging any session or custom - * event data. - */ -class DeviceInfo { - /** - * Returns the display name of the current operating system. - */ - static String getOS() { - return "Android"; - } - - /** - * Returns the current operating system version as a displayable string. - */ - static String getOSVersion() { - return android.os.Build.VERSION.RELEASE; - } - - /** - * Returns the current device model. - */ - static String getDevice() { - return android.os.Build.MODEL; - } - - /** - * Returns the non-scaled pixel resolution of the current default display being used by the - * WindowManager in the specified context. - * @param context context to use to retrieve the current WindowManager - * @return a string in the format "WxH", or the empty string "" if resolution cannot be determined - */ - static String getResolution(final Context context) { - // user reported NPE in this method; that means either getSystemService or getDefaultDisplay - // were returning null, even though the documentation doesn't say they should do so; so now - // we catch Throwable and return empty string if that happens - String resolution = ""; - try { - final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - final Display display = wm.getDefaultDisplay(); - final DisplayMetrics metrics = new DisplayMetrics(); - display.getMetrics(metrics); - resolution = metrics.widthPixels + "x" + metrics.heightPixels; - } - catch (Throwable t) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(Seeds.TAG, "Device resolution cannot be determined"); - } - } - return resolution; - } - - /** - * Maps the current display density to a string constant. - * @param context context to use to retrieve the current display metrics - * @return a string constant representing the current display density, or the - * empty string if the density is unknown - */ - static String getDensity(final Context context) { - String densityStr = ""; - final int density = context.getResources().getDisplayMetrics().densityDpi; - switch (density) { - case DisplayMetrics.DENSITY_LOW: - densityStr = "LDPI"; - break; - case DisplayMetrics.DENSITY_MEDIUM: - densityStr = "MDPI"; - break; - case DisplayMetrics.DENSITY_TV: - densityStr = "TVDPI"; - break; - case DisplayMetrics.DENSITY_HIGH: - densityStr = "HDPI"; - break; - case DisplayMetrics.DENSITY_XHIGH: - densityStr = "XHDPI"; - break; - case DisplayMetrics.DENSITY_400: - densityStr = "XMHDPI"; - break; - case DisplayMetrics.DENSITY_XXHIGH: - densityStr = "XXHDPI"; - break; - case DisplayMetrics.DENSITY_XXXHIGH: - densityStr = "XXXHDPI"; - break; - } - return densityStr; - } - - /** - * Returns the display name of the current network operator from the - * TelephonyManager from the specified context. - * @param context context to use to retrieve the TelephonyManager from - * @return the display name of the current network operator, or the empty - * string if it cannot be accessed or determined - */ - static String getCarrier(final Context context) { - String carrier = ""; - final TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (manager != null) { - carrier = manager.getNetworkOperatorName(); - } - if (carrier == null || carrier.length() == 0) { - carrier = ""; - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(Seeds.TAG, "No carrier found"); - } - } - return carrier; - } - - /** - * Returns the current locale (ex. "en_US"). - */ - static String getLocale() { - final Locale locale = Locale.getDefault(); - return locale.getLanguage() + "_" + locale.getCountry(); - } - - /** - * Returns the application version string stored in the specified - * context's package info versionName field, or "1.0" if versionName - * is not present. - */ - static String getAppVersion(final Context context) { - String result = Seeds.DEFAULT_APP_VERSION; - try { - result = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; - } - catch (PackageManager.NameNotFoundException e) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(Seeds.TAG, "No app version found"); - } - } - return result; - } - - /** - * Returns the package name of the app that installed this app - */ - static String getStore(final Context context) { - String result = ""; - if(android.os.Build.VERSION.SDK_INT >= 3 ) { - try { - result = context.getPackageManager().getInstallerPackageName(context.getPackageName()); - } catch (Exception e) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(Seeds.TAG, "Can't get Installer package"); - } - } - if (result == null || result.length() == 0) { - result = ""; - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.i(Seeds.TAG, "No store found"); - } - } - } - return result; - } - - /** - * Returns a URL-encoded JSON string containing the device metrics - * to be associated with a begin session event. - * See the following link for more info: - * https://count.ly/resources/reference/server-api - */ - static String getMetrics(final Context context) { - final JSONObject json = new JSONObject(); - - fillJSONIfValuesNotEmpty(json, - "_device", getDevice(), - "_os", getOS(), - "_os_version", getOSVersion(), - "_carrier", getCarrier(context), - "_resolution", getResolution(context), - "_density", getDensity(context), - "_locale", getLocale(), - "_app_version", getAppVersion(context), - "_store", getStore(context)); - - String result = json.toString(); - - try { - result = java.net.URLEncoder.encode(result, "UTF-8"); - } catch (UnsupportedEncodingException ignored) { - // should never happen because Android guarantees UTF-8 support - } - - return result; - } - - /** - * Utility method to fill JSONObject with supplied objects for supplied keys. - * Fills json only with non-null and non-empty key/value pairs. - * @param json JSONObject to fill - * @param objects varargs of this kind: key1, value1, key2, value2, ... - */ - static void fillJSONIfValuesNotEmpty(final JSONObject json, final String ... objects) { - try { - if (objects.length > 0 && objects.length % 2 == 0) { - for (int i = 0; i < objects.length; i += 2) { - final String key = objects[i]; - final String value = objects[i + 1]; - if (value != null && value.length() > 0) { - json.put(key, value); - } - } - } - } catch (JSONException ignored) { - // shouldn't ever happen when putting String objects into a JSONObject, - // it can only happen when putting NaN or INFINITE doubles or floats into it - } - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/Event.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/Event.java deleted file mode 100755 index 8d68ac3b7..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/Event.java +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright (c) 2012, 2013, 2014 Countly - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -package com.playseeds.android.sdk; - -import android.util.Log; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -/** - * This class holds the data for a single Count.ly custom event instance. - * It also knows how to read & write itself to the Count.ly custom event JSON syntax. - * See the following link for more info: - * https://count.ly/resources/reference/custom-events - */ -class Event { - private static final String SEGMENTATION_KEY = "segmentation"; - private static final String KEY_KEY = "key"; - private static final String COUNT_KEY = "count"; - private static final String SUM_KEY = "sum"; - private static final String TIMESTAMP_KEY = "timestamp"; - - public String key; - public Map segmentation; - public int count; - public double sum; - public int timestamp; - - /** - * Creates and returns a JSONObject containing the event data from this object. - * @return a JSONObject containing the event data from this object - */ - JSONObject toJSON() { - final JSONObject json = new JSONObject(); - - try { - json.put(KEY_KEY, key); - json.put(COUNT_KEY, count); - json.put(TIMESTAMP_KEY, timestamp); - - if (segmentation != null) { - json.put(SEGMENTATION_KEY, new JSONObject(segmentation)); - } - - // we put in the sum last, the only reason that a JSONException would be thrown - // would be if sum is NaN or infinite, so in that case, at least we will return - // a JSON object with the rest of the fields populated - json.put(SUM_KEY, sum); - } - catch (JSONException e) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(Seeds.TAG, "Got exception converting an Event to JSON", e); - } - } - - return json; - } - - /** - * Factory method to create an Event from its JSON representation. - * @param json JSON object to extract event data from - * @return Event object built from the data in the JSON or null if the "key" value is not - * present or the empty string, or if a JSON exception occurs - * @throws NullPointerException if JSONObject is null - */ - static Event fromJSON(final JSONObject json) { - Event event = new Event(); - - try { - if (!json.isNull(KEY_KEY)) { - event.key = json.getString(KEY_KEY); - } - event.count = json.optInt(COUNT_KEY); - event.sum = json.optDouble(SUM_KEY, 0.0d); - event.timestamp = json.optInt(TIMESTAMP_KEY); - - if (!json.isNull(SEGMENTATION_KEY)) { - final JSONObject segm = json.getJSONObject(SEGMENTATION_KEY); - final HashMap segmentation = new HashMap<>(segm.length()); - final Iterator nameItr = segm.keys(); - while (nameItr.hasNext()) { - final String key = (String) nameItr.next(); - if (!segm.isNull(key)) { - segmentation.put(key, segm.getString(key)); - } - } - event.segmentation = segmentation; - } - } - catch (JSONException e) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(Seeds.TAG, "Got exception converting JSON to an Event", e); - } - event = null; - } - - return (event != null && event.key != null && event.key.length() > 0) ? event : null; - } - - @Override - public boolean equals(final Object o) { - if (o == null || !(o instanceof Event)) { - return false; - } - - final Event e = (Event) o; - - return (key == null ? e.key == null : key.equals(e.key)) && - timestamp == e.timestamp && - (segmentation == null ? e.segmentation == null : segmentation.equals(e.segmentation)); - } - - @Override - public int hashCode() { - return (key != null ? key.hashCode() : 1) ^ - (segmentation != null ? segmentation.hashCode() : 1) ^ - (timestamp != 0 ? timestamp : 1); - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/EventQueue.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/EventQueue.java deleted file mode 100755 index 7ca9a0dae..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/EventQueue.java +++ /dev/null @@ -1,109 +0,0 @@ -/* -Copyright (c) 2012, 2013, 2014 Countly - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -package com.playseeds.android.sdk; - -import org.json.JSONArray; - -import java.io.UnsupportedEncodingException; -import java.util.List; -import java.util.Map; - -/** - * This class queues event data locally and can convert that event data to JSON - * for submission to a Count.ly server. - * - * None of the methods in this class are synchronized because access to this class is - * controlled by the Seeds singleton, which is synchronized. - * - * NOTE: This class is only public to facilitate unit testing, because - * of this bug in dexmaker: https://code.google.com/p/dexmaker/issues/detail?id=34 - */ -public class EventQueue { - private final CountlyStore countlyStore_; - - /** - * Constructs an EventQueue. - * @param countlyStore backing store to be used for local event queue persistence - */ - EventQueue(final CountlyStore countlyStore) { - countlyStore_ = countlyStore; - } - - /** - * Returns the number of events in the local event queue. - * @return the number of events in the local event queue - */ - int size() { - if (countlyStore_ != null) { - return countlyStore_.events().length; - } - - return -1; - } - - /** - * Removes all current events from the local queue and returns them as a - * URL-encoded JSON string that can be submitted to a ConnectionQueue. - * @return URL-encoded JSON string of event data from the local event queue - */ - String events() { - String result; - - final List events = countlyStore_.eventsList(); - - final JSONArray eventArray = new JSONArray(); - for (Event e : events) { - eventArray.put(e.toJSON()); - } - - result = eventArray.toString(); - - countlyStore_.removeEvents(events); - - try { - result = java.net.URLEncoder.encode(result, "UTF-8"); - } catch (UnsupportedEncodingException e) { - // should never happen because Android guarantees UTF-8 support - } - - return result; - } - - /** - * Records a custom Count.ly event to the local event queue. - * @param key name of the custom event, required, must not be the empty string - * @param segmentation segmentation values for the custom event, may be null - * @param count count associated with the custom event, should be more than zero - * @param sum sum associated with the custom event, if not used, pass zero. - * NaN and infinity values will be quietly ignored. - * @throws IllegalArgumentException if key is null or empty - */ - void recordEvent(final String key, final Map segmentation, final int count, final double sum) { - final int timestamp = Seeds.currentTimestamp(); - countlyStore_.addEvent(key, segmentation, timestamp, count, sum); - } - - // for unit tests - CountlyStore getCountlyStore() { - return countlyStore_; - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/IInAppMessageShowCountListener.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/IInAppMessageShowCountListener.java deleted file mode 100644 index 0e3b1c814..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/IInAppMessageShowCountListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.playseeds.android.sdk; - -public interface IInAppMessageShowCountListener { - void onInAppMessageShowCount(String errorMessage, int showCount, String message_id); -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/IInAppPurchaseCountListener.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/IInAppPurchaseCountListener.java deleted file mode 100644 index bb4f91ee8..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/IInAppPurchaseCountListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.playseeds.android.sdk; - -public interface IInAppPurchaseCountListener { - void onInAppPurchaseCount(String errorMessage, int purchasesCount, String key); -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/IUserBehaviorQueryListener.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/IUserBehaviorQueryListener.java deleted file mode 100644 index f4e498f48..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/IUserBehaviorQueryListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.playseeds.android.sdk; -import com.google.gson.JsonElement; - -public interface IUserBehaviorQueryListener { - void onUserBehaviorResponse(String errorMessage, JsonElement result, String queryPath); -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/MainActivityEventListener.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/MainActivityEventListener.java deleted file mode 100644 index 881c4f383..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/MainActivityEventListener.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.playseeds.android.sdk; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.Application; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.util.Log; - -import com.android.vending.billing.IInAppBillingService; -import com.playseeds.android.sdk.inappmessaging.InAppMessageListener; - -/** - * Created by atte on 23/08/16. - * - * Streamlines the integration experience on Android v4.0 and up by - * - automating the resolving of the billing service - * - listening to onStart, onStop and onDestroy of the activity and informing Seeds SDK - * about the changes in application state - */ - -@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) -public class MainActivityEventListener implements Application.ActivityLifecycleCallbacks { - private Activity mainActivity; - private final InAppMessageListener listener; - private final String serverURL; - private final String appKey; - private final String deviceID; - private final DeviceId.Type idMode; - private ServiceConnection mServiceConn; - IInAppBillingService mService; - - public MainActivityEventListener(Activity mainActivity, InAppMessageListener listener, String serverURL, String appKey, String deviceID, DeviceId.Type idMode) { - this.mainActivity = mainActivity; - this.listener = listener; - this.serverURL = serverURL; - this.appKey = appKey; - this.deviceID = deviceID; - this.idMode = idMode; - } - - public void resolve() { - mainActivity.getApplication().registerActivityLifecycleCallbacks(this); - - mServiceConn = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - mService = IInAppBillingService.Stub.asInterface(service); - - Seeds.sharedInstance() - .init(mainActivity, mService, listener, serverURL, appKey, deviceID, idMode); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - } - }; - - Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); - serviceIntent.setPackage("com.android.vending"); - - mainActivity.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); - } - - @Override - public void onActivityStarted(Activity activity) { - if (activity == mainActivity) { - Log.d(Seeds.TAG, "mainactivity onstart"); - Seeds.sharedInstance().onStart(); - } - } - - @Override - public void onActivityStopped(Activity activity) { - if (activity == mainActivity) { - Log.d(Seeds.TAG, "mainactivity onstop"); - Seeds.sharedInstance().onStop(); - } - } - - @Override - public void onActivityDestroyed(Activity activity) { - if (activity == mainActivity) { - if (mService != null) { - Log.d(Seeds.TAG, "mainactivity onactivitydestroyed"); - mainActivity.unbindService(mServiceConn); - } - } - } - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - // Unneeded - } - - @Override - public void onActivityResumed(Activity activity) { - // Unneeded - } - - @Override - public void onActivityPaused(Activity activity) { - // Unneeded - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - // Unneeded - } -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/MessagingAdapter.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/MessagingAdapter.java deleted file mode 100755 index 0ef71a2e1..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/MessagingAdapter.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.playseeds.android.sdk; - -import android.app.Activity; -import android.content.Context; -import android.util.Log; - -import java.lang.reflect.Method; - -public class MessagingAdapter { - private static final String TAG = "MessagingAdapter"; - private final static String MESSAGING_CLASS_NAME = "ly.count.android.sdk.messaging.CountlyMessaging"; - - public static boolean isMessagingAvailable() { - boolean messagingAvailable = false; - try { - Class.forName(MESSAGING_CLASS_NAME); - messagingAvailable = true; - } - catch (ClassNotFoundException ignored) {} - return messagingAvailable; - } - - public static boolean init(Activity activity, Class activityClass, String sender, String[] buttonNames) { - try { - final Class cls = Class.forName(MESSAGING_CLASS_NAME); - final Method method = cls.getMethod("init", Activity.class, Class.class, String.class, String[].class); - method.invoke(null, activity, activityClass, sender, buttonNames); - return true; - } - catch (Throwable logged) { - Log.e(TAG, "Couldn't init Seeds Messaging", logged); - return false; - } - } - - public static boolean storeConfiguration(Context context, String serverURL, String appKey, String deviceID, DeviceId.Type idMode) { - try { - final Class cls = Class.forName(MESSAGING_CLASS_NAME); - final Method method = cls.getMethod("storeConfiguration", Context.class, String.class, String.class, String.class, DeviceId.Type.class); - method.invoke(null, context, serverURL, appKey, deviceID, idMode); - return true; - } - catch (Throwable logged) { - Log.e(TAG, "Couldn't store configuration in Seeds Messaging", logged); - return false; - } - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/OpenUDIDAdapter.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/OpenUDIDAdapter.java deleted file mode 100755 index a082d8af3..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/OpenUDIDAdapter.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.playseeds.android.sdk; - -import android.content.Context; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public class OpenUDIDAdapter { - private final static String OPEN_UDID_MANAGER_CLASS_NAME = "org.openudid.OpenUDID_manager"; - - public static boolean isOpenUDIDAvailable() { - boolean openUDIDAvailable = false; - try { - Class.forName(OPEN_UDID_MANAGER_CLASS_NAME); - openUDIDAvailable = true; - } - catch (ClassNotFoundException ignored) {} - return openUDIDAvailable; - } - - public static boolean isInitialized() { - boolean initialized = false; - try { - final Class cls = Class.forName(OPEN_UDID_MANAGER_CLASS_NAME); - final Method isInitializedMethod = cls.getMethod("isInitialized", (Class[]) null); - final Object result = isInitializedMethod.invoke(null, (Object[]) null); - if (result instanceof Boolean) { - initialized = (Boolean) result; - } - } - catch (ClassNotFoundException ignored) {} - catch (NoSuchMethodException ignored) {} - catch (InvocationTargetException ignored) {} - catch (IllegalAccessException ignored) {} - return initialized; - } - - public static void sync(final Context context) { - try { - final Class cls = Class.forName(OPEN_UDID_MANAGER_CLASS_NAME); - final Method syncMethod = cls.getMethod("sync", Context.class); - syncMethod.invoke(null, context); - } - catch (ClassNotFoundException ignored) {} - catch (NoSuchMethodException ignored) {} - catch (InvocationTargetException ignored) {} - catch (IllegalAccessException ignored) {} - } - - public static String getOpenUDID() { - String openUDID = null; - try { - final Class cls = Class.forName(OPEN_UDID_MANAGER_CLASS_NAME); - final Method getOpenUDIDMethod = cls.getMethod("getOpenUDID", (Class[]) null); - final Object result = getOpenUDIDMethod.invoke(null, (Object[]) null); - if (result instanceof String) { - openUDID = (String) result; - } - } - catch (ClassNotFoundException ignored) {} - catch (NoSuchMethodException ignored) {} - catch (InvocationTargetException ignored) {} - catch (IllegalAccessException ignored) {} - return openUDID; - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/ReferrerReceiver.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/ReferrerReceiver.java deleted file mode 100755 index c8d6a2443..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/ReferrerReceiver.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.playseeds.android.sdk; - -import java.net.URLDecoder; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -/** - * ADB Testing - * adb shell - * am broadcast -a com.android.vending.INSTALL_REFERRER --es "referrer" "countly_cid%3Dcb14e5f33b528334715f1809e4572842c74686df%26countly_cuid%3Decf125107e4e27e6bcaacb3ae10ddba66459e6ae" -**/ -//****************************************************************************** -public class ReferrerReceiver extends BroadcastReceiver -{ - private static String key = "referrer"; - //-------------------------------------------------------------------------- - public static String getReferrer(Context context) - { - // Return any persisted referrer value or null if we don't have a referrer. - return context.getSharedPreferences(key, Context.MODE_PRIVATE).getString(key, null); - } - - public static void deleteReferrer(Context context) - { - // delete stored referrer. - context.getSharedPreferences(key, Context.MODE_PRIVATE).edit().remove(key).commit(); - } - - //-------------------------------------------------------------------------- - public ReferrerReceiver(){ - } - - //-------------------------------------------------------------------------- - @Override public void onReceive(Context context, Intent intent) - { - try - { - // Make sure this is the intent we expect - it always should be. - if ((null != intent) && (intent.getAction().equals("com.android.vending.INSTALL_REFERRER"))) - { - // This intent should have a referrer string attached to it. - String rawReferrer = intent.getStringExtra(key); - if (null != rawReferrer) - { - // The string is usually URL Encoded, so we need to decode it. - String referrer = URLDecoder.decode(rawReferrer, "UTF-8"); - - // Log the referrer string. - Log.d(Seeds.TAG, "Referrer: " + referrer); - - String parts[] = referrer.split("&"); - String cid = null; - String uid = null; - for(int i = 0; i < parts.length; i++){ - if(parts[i].startsWith("countly_cid")) - cid = parts[i].replace("countly_cid=", "").trim(); - if(parts[i].startsWith("countly_cuid")) - uid = parts[i].replace("countly_cuid=", "").trim(); - } - String res = ""; - if(cid != null) - res += "&campaign_id="+cid; - if(uid != null) - res += "&campaign_user="+uid; - - Log.d(Seeds.TAG, "Processed: " + res); - // Persist the referrer string. - if(!res.equals("")) - context.getSharedPreferences(key, Context.MODE_PRIVATE).edit().putString(key, res).commit(); - } - } - } - catch (Exception e) - { - Log.d(Seeds.TAG, e.toString()); - } - } -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/Seeds.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/Seeds.java deleted file mode 100755 index 19602ab0d..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/Seeds.java +++ /dev/null @@ -1,1035 +0,0 @@ -/* -Copyright (c) 2012, 2013, 2014 Countly -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -package com.playseeds.android.sdk; - -import android.app.Activity; -import android.content.Context; -import android.net.Uri; -import android.os.Build; -import android.util.Log; - -import com.android.vending.billing.IInAppBillingService; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.loopj.android.http.AsyncHttpClient; -import com.loopj.android.http.TextHttpResponseHandler; -import com.playseeds.android.sdk.inappmessaging.InAppMessageListener; -import com.playseeds.android.sdk.inappmessaging.InAppMessageManager; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import cz.msebera.android.httpclient.Header; - -/** - * This class is the public API for the Seeds Android SDK. - * Get more details here. - */ -public class Seeds { - private ConnectionQueue connectionQueue_; - @SuppressWarnings("FieldCanBeLocal") - private ScheduledExecutorService timerService_; - private EventQueue eventQueue_; - private long prevSessionDurationStartTime_; - private int activityCount_; - private boolean disableUpdateSessionRequests_; - private boolean enableLogging_; - private Seeds.CountlyMessagingMode messagingMode_; - private Context context_; - protected static List publicKeyPinCertificates; - private IInAppBillingService billingService; - private AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); - private MainActivityEventListener mainActivityEventListener; - - /** - * Current version of the Count.ly Android SDK as a displayable string. - */ - public static final String COUNTLY_SDK_VERSION_STRING = "15.06"; - - /** - * Default string used in the begin session metrics if the - * app version cannot be found. - */ - public static final String DEFAULT_APP_VERSION = "1.0"; - - /** - * Tag used in all logging in the Count.ly SDK. - */ - public static final String TAG = "Seeds"; - - /** - * Determines how many custom events can be queued locally before - * an attempt is made to submit them to a Count.ly server. - */ - private static final int EVENT_QUEUE_SIZE_THRESHOLD = 10; - - /** - * How often onTimer() is called. - */ - private static final long TIMER_DELAY_IN_SECONDS = 60; - - /** - * Constructs a Seeds object. - * Creates a new ConnectionQueue and initializes the session timer. - */ - Seeds() { - connectionQueue_ = new ConnectionQueue(); - timerService_ = Executors.newSingleThreadScheduledExecutor(); - timerService_.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - onTimer(); - } - }, TIMER_DELAY_IN_SECONDS, TIMER_DELAY_IN_SECONDS, TimeUnit.SECONDS); - } - - // see http://stackoverflow.com/questions/7048198/thread-safe-singletons-in-java - private static class SingletonHolder { - static final Seeds instance = new Seeds(); - } - - /** - * Returns the Seeds singleton. - */ - public static Seeds sharedInstance() { - return SingletonHolder.instance; - } - - /** - * Enum used in Seeds.initMessaging() method which controls what kind of - * app installation it is. Later (in Seeds Dashboard or when calling Seeds API method), - * you'll be able to choose whether you want to send a message to ly.count.android.sdk.test devices, - * or to production ones. - */ - public enum CountlyMessagingMode { - TEST, - PRODUCTION, - } - - /** - * Initializes the Seeds SDK in a simplified fashion. Call from your main Activity's onCreate() method. - * Must be called before other SDK methods can be used. - * This version integrates to activity lifecycle hooks and isn't supported in Android v. 13 or lower. - * The use of lifecycle hook removes the need for adding code to onStart, onStop and onDestroy manually. - * Device ID is supplied by OpenUDID service if available, otherwise Advertising ID is used. - * Be cautious: If neither OpenUDID, nor Advertising ID is available, Seeds will ignore this user. - * @param activity preferably the main activity of the app - * @param listener callbacks listener - * @param serverURL URL of the Seeds server to submit data to; use "https://cloud.count.ly" for Seeds Cloud - * @param appKey app key for the application being tracked; find in the Seeds Dashboard under Management > Applications - * @return Seeds instance for easy method chaining - * @throws java.lang.IllegalArgumentException if context, serverURL, appKey, or deviceID are invalid - * @throws java.lang.IllegalStateException if the Seeds SDK has already been initialized or Android SDK is too old - */ - public Seeds simpleInit(final Activity activity, final InAppMessageListener listener, final String serverURL, final String appKey) { - final boolean apiSupportsLifecycleCallbacks = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; - - if (apiSupportsLifecycleCallbacks) { - DeviceId.Type idMode = OpenUDIDAdapter.isOpenUDIDAvailable() ? DeviceId.Type.OPEN_UDID : DeviceId.Type.ADVERTISING_ID; - - // Pre-initialize SDK without the billing service - Seeds sdk = Seeds.sharedInstance() - .init(activity, null, listener, serverURL, appKey, null, idMode); - - // MainActivityEventListener takes care of the creation of the billing service - // and binds to the main activity lifecycle hooks - mainActivityEventListener = - new MainActivityEventListener(activity, listener, serverURL, appKey, null, idMode); - mainActivityEventListener.resolve(); - - return sdk; - } else { - throw new IllegalStateException("You can't use automatedInit with Android SDK version <= 13"); - } - } - - /** - * Initializes the Seeds SDK. Call from your main Activity's onCreate() method. - * Must be called before other SDK methods can be used. - * Device ID is supplied by OpenUDID service if available, otherwise Advertising ID is used. - * Be cautious: If neither OpenUDID, nor Advertising ID is available, Seeds will ignore this user. - * @param context application context - * @param billingService billing service or null - * @param listener callbacks listener - * @param serverURL URL of the Seeds server to submit data to; use "https://cloud.count.ly" for Seeds Cloud - * @param appKey app key for the application being tracked; find in the Seeds Dashboard under Management > Applications - * @return Seeds instance for easy method chaining - * @throws java.lang.IllegalArgumentException if context, serverURL, appKey, or deviceID are invalid - * @throws java.lang.IllegalStateException if the Seeds SDK has already been initialized - */ - public Seeds init(final Context context, IInAppBillingService billingService, final InAppMessageListener listener, final String serverURL, final String appKey) { - return init(context, billingService, listener, serverURL, appKey, null, OpenUDIDAdapter.isOpenUDIDAvailable() ? DeviceId.Type.OPEN_UDID : DeviceId.Type.ADVERTISING_ID); - } - - /** - * Initializes the Seeds SDK. Call from your main Activity's onCreate() method. - * Must be called before other SDK methods can be used. - * @param context application context - * @param billingService billing service or null - * @param listener callbacks listener - * @param serverURL URL of the Seeds server to submit data to; use "https://cloud.count.ly" for Seeds Cloud - * @param appKey app key for the application being tracked; find in the Seeds Dashboard under Management > Applications - * @param deviceID unique ID for the device the app is running on; note that null in deviceID means that Seeds will fall back to OpenUDID, then, if it's not available, to Google Advertising ID - * @return Seeds instance for easy method chaining - * @throws IllegalArgumentException if context, serverURL, appKey, or deviceID are invalid - * @throws IllegalStateException if init has previously been called with different values during the same application instance - */ - public Seeds init(final Context context, IInAppBillingService billingService, final InAppMessageListener listener, final String serverURL, final String appKey, final String deviceID) { - return init(context, billingService, listener, serverURL, appKey, deviceID, null); - } - - /** - * Initializes the Seeds SDK. Call from your main Activity's onCreate() method. - * Must be called before other SDK methods can be used. - * @param context application context - * @param billingService billing service or null - * @param listener callbacks listener - * @param serverURL URL of the Seeds server to submit data to; use "https://cloud.count.ly" for Seeds Cloud - * @param appKey app key for the application being tracked; find in the Seeds Dashboard under Management > Applications - * @param deviceID unique ID for the device the app is running on; note that null in deviceID means that Seeds will fall back to OpenUDID, then, if it's not available, to Google Advertising ID - * @param idMode enum value specifying which device ID generation strategy Seeds should use: OpenUDID or Google Advertising ID - * @return Seeds instance for easy method chaining - * @throws IllegalArgumentException if context, serverURL, appKey, or deviceID are invalid - * @throws IllegalStateException if init has previously been called with different values during the same application instance - */ - public synchronized Seeds init(final Context context, IInAppBillingService billingService, final InAppMessageListener listener, final String serverURL, final String appKey, final String deviceID, DeviceId.Type idMode) { - if (context == null) { - throw new IllegalArgumentException("valid context is required"); - } - if (!isValidURL(serverURL)) { - throw new IllegalArgumentException("valid serverURL is required"); - } - if (appKey == null || appKey.length() == 0) { - throw new IllegalArgumentException("valid appKey is required"); - } - if (deviceID != null && deviceID.length() == 0) { - throw new IllegalArgumentException("valid deviceID is required"); - } - if (deviceID == null && idMode == null) { - if (OpenUDIDAdapter.isOpenUDIDAvailable()) idMode = DeviceId.Type.OPEN_UDID; - else if (AdvertisingIdAdapter.isAdvertisingIdAvailable()) idMode = DeviceId.Type.ADVERTISING_ID; - } - if (deviceID == null && idMode == DeviceId.Type.OPEN_UDID && !OpenUDIDAdapter.isOpenUDIDAvailable()) { - throw new IllegalArgumentException("valid deviceID is required because OpenUDID is not available"); - } - if (deviceID == null && idMode == DeviceId.Type.ADVERTISING_ID && !AdvertisingIdAdapter.isAdvertisingIdAvailable()) { - throw new IllegalArgumentException("valid deviceID is required because Advertising ID is not available (you need to include Google Play services 4.0+ into your project)"); - } - if (eventQueue_ != null && (!connectionQueue_.getServerURL().equals(serverURL) || - !connectionQueue_.getAppKey().equals(appKey) || - !DeviceId.deviceIDEqualsNullSafe(deviceID, idMode, connectionQueue_.getDeviceId()) )) { - throw new IllegalStateException("Seeds cannot be reinitialized with different values"); - } - - // In some cases CountlyMessaging does some background processing, so it needs a way - // to start Seeds on itself - if (MessagingAdapter.isMessagingAvailable()) { - MessagingAdapter.storeConfiguration(context, serverURL, appKey, deviceID, idMode); - } - - // if we get here and eventQueue_ != null, init is being called again with the same values, - // so there is nothing to do, because we are already initialized with those values - if (eventQueue_ == null) { - DeviceId deviceIdInstance; - if (deviceID != null) { - deviceIdInstance = new DeviceId(deviceID); - } else { - deviceIdInstance = new DeviceId(idMode); - } - - final CountlyStore countlyStore = new CountlyStore(context); - - deviceIdInstance.init(context, countlyStore, true); - - connectionQueue_.setServerURL(serverURL); - connectionQueue_.setAppKey(appKey); - connectionQueue_.setCountlyStore(countlyStore); - connectionQueue_.setDeviceId(deviceIdInstance); - - eventQueue_ = new EventQueue(countlyStore); - } - - context_ = context; - this.billingService = billingService; - - // context is allowed to be changed on the second init call - connectionQueue_.setContext(context); - - initInAppMessaging(); - InAppMessageManager.sharedInstance().setListener(listener); - - return this; - } - - /** - * Checks whether Seeds.init has been already called. - * @return true if Seeds is ready to use - */ - public synchronized boolean isInitialized() { - return eventQueue_ != null; - } - - /** - * Initializes the Seeds MessagingSDK. Call from your main Activity's onCreate() method. - * @param activity application activity which acts as a final destination for notifications - * @param activityClass application activity class which acts as a final destination for notifications - * @param projectID ProjectID for this app from Google API Console - * @param mode whether this app installation is a ly.count.android.sdk.test release or production - * @return Seeds instance for easy method chaining - * @throws IllegalStateException if no CountlyMessaging class is found (you need to use countly-messaging-sdk-android library instead of countly-sdk-android) - */ - public Seeds initMessaging(Activity activity, Class activityClass, String projectID, Seeds.CountlyMessagingMode mode) { - return initMessaging(activity, activityClass, projectID, null, mode); - } - - /** - * Initializes the Seeds MessagingSDK. Call from your main Activity's onCreate() method. - * @param activity application activity which acts as a final destination for notifications - * @param activityClass application activity class which acts as a final destination for notifications - * @param projectID ProjectID for this app from Google API Console - * @param buttonNames Strings to use when displaying Dialogs (uses new String[]{"Open", "Review"} by default) - * @param mode whether this app installation is a ly.count.android.sdk.test release or production - * @return Seeds instance for easy method chaining - * @throws IllegalStateException if no CountlyMessaging class is found (you need to use countly-messaging-sdk-android library instead of countly-sdk-android) - */ - public synchronized Seeds initMessaging(Activity activity, Class activityClass, String projectID, String[] buttonNames, Seeds.CountlyMessagingMode mode) { - if (mode != null && !MessagingAdapter.isMessagingAvailable()) { - throw new IllegalStateException("you need to include countly-messaging-sdk-android library instead of countly-sdk-android if you want to use Seeds Messaging"); - } else { - if (!MessagingAdapter.init(activity, activityClass, projectID, buttonNames)) { - throw new IllegalStateException("couldn't initialize Seeds Messaging"); - } - } - messagingMode_ = mode; - - if (MessagingAdapter.isMessagingAvailable()) { - Log.d(Seeds.TAG, "deviceId in initMessaging: " + connectionQueue_.getDeviceId() + connectionQueue_.getDeviceId().getId() + connectionQueue_.getDeviceId().getType()); - MessagingAdapter.storeConfiguration(connectionQueue_.getContext(), connectionQueue_.getServerURL(), connectionQueue_.getAppKey(), connectionQueue_.getDeviceId().getId(), connectionQueue_.getDeviceId().getType()); - } - - return this; - } - - /** - * Initializes the Seeds InAppMessaging part of the MessagingSDK. Call from your main Activity's onCreate() method. - * @return Seeds instance for easy method chaining - */ - public synchronized Seeds initInAppMessaging() { - Log.d(Seeds.TAG, "deviceId: " + connectionQueue_.getDeviceId() + - connectionQueue_.getDeviceId().getId() + connectionQueue_.getDeviceId().getType()); - - InAppMessageManager.sharedInstance().init(connectionQueue_.getContext(), billingService, - connectionQueue_.getServerURL(), connectionQueue_.getAppKey(), - connectionQueue_.getDeviceId().getId(), connectionQueue_.getDeviceId().getType()); - - return this; - } - - /** - * Immediately disables session & event tracking and clears any stored session & event data. - * This API is useful if your app has a tracking opt-out switch, and you want to immediately - * disable tracking when a user opts out. The onStart/onStop/recordEvent methods will throw - * IllegalStateException after calling this until Seeds is reinitialized by calling init - * again. - */ - public synchronized void halt() { - eventQueue_ = null; - final CountlyStore countlyStore = connectionQueue_.getCountlyStore(); - if (countlyStore != null) { - countlyStore.clear(); - } - connectionQueue_.setContext(null); - connectionQueue_.setServerURL(null); - connectionQueue_.setAppKey(null); - connectionQueue_.setCountlyStore(null); - prevSessionDurationStartTime_ = 0; - activityCount_ = 0; - } - - /** - * Tells the Seeds SDK that an Activity has started. Since Android does not have an - * easy way to determine when an application instance starts and stops, you must call this - * method from every one of your Activity's onStart methods for accurate application - * session tracking. - * @throws IllegalStateException if Seeds SDK has not been initialized - */ - public synchronized void onStart() { - if (eventQueue_ == null) { - throw new IllegalStateException("init must be called before onStart"); - } - - ++activityCount_; - if (activityCount_ == 1) { - onStartHelper(); - } - - //check if there is an install referrer data - String referrer = ReferrerReceiver.getReferrer(context_); - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.d(Seeds.TAG, "Checking referrer: " + referrer); - } - if(referrer != null){ - connectionQueue_.sendReferrerData(referrer); - ReferrerReceiver.deleteReferrer(context_); - } - - CrashDetails.inForeground(); - } - - /** - * Called when the first Activity is started. Sends a begin session event to the server - * and initializes application session tracking. - */ - void onStartHelper() { - prevSessionDurationStartTime_ = System.nanoTime(); - connectionQueue_.beginSession(); - } - - /** - * Tells the Seeds SDK that an Activity has stopped. Since Android does not have an - * easy way to determine when an application instance starts and stops, you must call this - * method from every one of your Activity's onStop methods for accurate application - * session tracking. - * @throws IllegalStateException if Seeds SDK has not been initialized, or if - * unbalanced calls to onStart/onStop are detected - */ - public synchronized void onStop() { - if (eventQueue_ == null) { - throw new IllegalStateException("init must be called before onStop"); - } - if (activityCount_ == 0) { - throw new IllegalStateException("must call onStart before onStop"); - } - - --activityCount_; - if (activityCount_ == 0) { - onStopHelper(); - } - - CrashDetails.inBackground(); - } - - /** - * Called when final Activity is stopped. Sends an end session event to the server, - * also sends any unsent custom events. - */ - void onStopHelper() { - connectionQueue_.endSession(roundedSecondsSinceLastSessionDurationUpdate()); - prevSessionDurationStartTime_ = 0; - - if (eventQueue_.size() > 0) { - connectionQueue_.recordEvents(eventQueue_.events()); - } - } - - /** - * Called when GCM Registration ID is received. Sends a token session event to the server. - */ - public void onRegistrationId(String registrationId) { - connectionQueue_.tokenSession(registrationId, messagingMode_); - } - - /** - * Records a custom event with no segmentation values, a count of one and a sum of zero. - * @param key name of the custom event, required, must not be the empty string - * @throws IllegalStateException if Seeds SDK has not been initialized - * @throws IllegalArgumentException if key is null or empty - */ - public void recordEvent(final String key) { - recordEvent(key, null, 1, 0); - } - - /** - * Records a custom event with no segmentation values, the specified count, and a sum of zero. - * @param key name of the custom event, required, must not be the empty string - * @param count count to associate with the event, should be more than zero - * @throws IllegalStateException if Seeds SDK has not been initialized - * @throws IllegalArgumentException if key is null or empty - */ - public void recordEvent(final String key, final int count) { - recordEvent(key, null, count, 0); - } - - /** - * Records a custom event with no segmentation values, and the specified count and sum. - * @param key name of the custom event, required, must not be the empty string - * @param count count to associate with the event, should be more than zero - * @param sum sum to associate with the event - * @throws IllegalStateException if Seeds SDK has not been initialized - * @throws IllegalArgumentException if key is null or empty - */ - public void recordEvent(final String key, final int count, final double sum) { - recordEvent(key, null, count, sum); - } - - private void recordGenericIAPEvent(String key, final double price, boolean seedsEvent) { - - HashMap segmentation = new HashMap(); - - if (seedsEvent) { - segmentation.put("IAP type", "Seeds"); - } else { - segmentation.put("IAP type", "Non-Seeds"); - } - - segmentation.put("item", key); - - recordEvent("IAP: " + key, segmentation, 1, price); - Log.d(TAG, "IAP: " + key + " segment: " + segmentation); - } - - /** - * Records a custom event with the specified segmentation values and count, and a sum of zero. - * @param key name of the custom event, required, must not be the empty string - * @param segmentation segmentation dictionary to associate with the event, can be null - * @param count count to associate with the event, should be more than zero - * @throws IllegalStateException if Seeds SDK has not been initialized - * @throws IllegalArgumentException if key is null or empty - */ - public void recordEvent(final String key, final Map segmentation, final int count) { - recordEvent(key, segmentation, count, 0); - } - - /** - * Records a custom event with the specified values. - * @param key name of the custom event, required, must not be the empty string - * @param segmentation segmentation dictionary to associate with the event, can be null - * @param count count to associate with the event, should be more than zero - * @param sum sum to associate with the event - * @throws IllegalStateException if Seeds SDK has not been initialized - * @throws IllegalArgumentException if key is null or empty, count is less than 1, or if - * segmentation contains null or empty keys or values - */ - public synchronized void recordEvent(final String key, final Map segmentation, final int count, final double sum) { - if (!isInitialized()) { - throw new IllegalStateException("Seeds.sharedInstance().init must be called before recordEvent"); - } - if (key == null || key.length() == 0) { - throw new IllegalArgumentException("Valid Seeds event key is required"); - } - if (count < 1) { - throw new IllegalArgumentException("Seeds event count should be greater than zero"); - } - if (segmentation != null) { - for (String k : segmentation.keySet()) { - if (k == null || k.length() == 0) { - throw new IllegalArgumentException("Seeds event segmentation key cannot be null or empty"); - } - if (segmentation.get(k) == null || segmentation.get(k).length() == 0) { - throw new IllegalArgumentException("Seeds event segmentation value cannot be null or empty"); - } - } - } - - eventQueue_.recordEvent(key, segmentation, count, sum); - sendEventsIfNeeded(); - } - - /** - * Sets information about user. Possible keys are: - *
    - *
  • - * name - (String) providing user's full name - *
  • - *
  • - * username - (String) providing user's nickname - *
  • - *
  • - * email - (String) providing user's email address - *
  • - *
  • - * organization - (String) providing user's organization's name where user works - *
  • - *
  • - * phone - (String) providing user's phone number - *
  • - *
  • - * picture - (String) providing WWW URL to user's avatar or profile picture - *
  • - *
  • - * picturePath - (String) providing local path to user's avatar or profile picture - *
  • - *
  • - * gender - (String) providing user's gender as M for male and F for female - *
  • - *
  • - * byear - (int) providing user's year of birth as integer - *
  • - *
- * @param data Map<String, String> with user data - */ - public synchronized Seeds setUserData(Map data) { - return setUserData(data, null); - } - - /** - * Sets information about user with custom properties. - * In custom properties you can provide any string key values to be stored with user - * Possible keys are: - *
    - *
  • - * name - (String) providing user's full name - *
  • - *
  • - * username - (String) providing user's nickname - *
  • - *
  • - * email - (String) providing user's email address - *
  • - *
  • - * organization - (String) providing user's organization's name where user works - *
  • - *
  • - * phone - (String) providing user's phone number - *
  • - *
  • - * picture - (String) providing WWW URL to user's avatar or profile picture - *
  • - *
  • - * picturePath - (String) providing local path to user's avatar or profile picture - *
  • - *
  • - * gender - (String) providing user's gender as M for male and F for female - *
  • - *
  • - * byear - (int) providing user's year of birth as integer - *
  • - *
- * @param data Map<String, String> with user data - * @param customdata Map<String, String> with custom key values for this user - */ - public synchronized Seeds setUserData(Map data, Map customdata) { - UserData.setData(data); - if(customdata != null) - UserData.setCustomData(customdata); - connectionQueue_.sendUserData(); - return this; - } - - /** - * Sets custom properties. - * In custom properties you can provide any string key values to be stored with user - * @param customdata Map<String, String> with custom key values for this user - */ - public synchronized Seeds setCustomUserData(Map customdata) { - if(customdata != null) - UserData.setCustomData(customdata); - connectionQueue_.sendUserData(); - return this; - } - - /** - * Set user location. - * - * Seeds detects user location based on IP address. But for geolocation-enabled apps, - * it's better to supply exact location of user. - * Allows sending messages to a custom segment of users located in a particular area. - * - * @param lat Latitude - * @param lon Longitude - */ - public synchronized Seeds setLocation(double lat, double lon) { - connectionQueue_.getCountlyStore().setLocation(lat, lon); - - if (disableUpdateSessionRequests_) { - connectionQueue_.updateSession(roundedSecondsSinceLastSessionDurationUpdate()); - } - - return this; - } - - /** - * Sets custom segments to be reported with crash reports - * In custom segments you can provide any string key values to segments crashes by - * @param segments Map<String, String> key segments and their values - */ - public synchronized Seeds setCustomCrashSegments(Map segments) { - if(segments != null) - CrashDetails.setCustomSegments(segments); - return this; - } - - /** - * Add crash breadcrumb like log record to the log that will be send together with crash report - * @param record String a bread crumb for the crash report - */ - public synchronized Seeds addCrashLog(String record) { - CrashDetails.addLog(record); - return this; - } - - /** - * Log handled exception to report it to server as non fatal crash - * @param exception Exception to log - */ - public synchronized Seeds logException(Exception exception) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - exception.printStackTrace(pw); - connectionQueue_.sendCrashReport(sw.toString(), true); - return this; - } - - /** - * Log handled exception to report it to server as non fatal crash - * @param exception Exception to log - */ - public synchronized Seeds logException(String exception) { - connectionQueue_.sendCrashReport(exception, true); - return this; - } - - /** - * Enable crash reporting to send unhandled crash reports to server - */ - public synchronized Seeds enableCrashReporting() { - //get default handler - final Thread.UncaughtExceptionHandler oldHandler = Thread.getDefaultUncaughtExceptionHandler(); - - Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); - connectionQueue_.sendCrashReport(sw.toString(), false); - - //if there was another handler before - if(oldHandler != null){ - //notify it also - oldHandler.uncaughtException(t,e); - } - } - }; - - Thread.setDefaultUncaughtExceptionHandler(handler); - return this; - } - - /** - * Disable periodic session time updates. - * By default, Seeds will send a request to the server each 30 seconds with a small update - * containing session duration time. This method allows you to disable such behavior. - * Note that event updates will still be sent every 10 events or 30 seconds after event recording. - * @param disable whether or not to disable session time updates - * @return Seeds instance for easy method chaining - */ - public synchronized Seeds setDisableUpdateSessionRequests(final boolean disable) { - disableUpdateSessionRequests_ = disable; - return this; - } - - /** - * Sets whether debug logging is turned on or off. Logging is disabled by default. - * @param enableLogging true to enable logging, false to disable logging - * @return Seeds instance for easy method chaining - */ - public synchronized Seeds setLoggingEnabled(final boolean enableLogging) { - enableLogging_ = enableLogging; - return this; - } - - public synchronized boolean isLoggingEnabled() { - return enableLogging_; - } - - /** - * Submits all of the locally queued events to the server if there are more than 10 of them. - */ - void sendEventsIfNeeded() { - if (eventQueue_.size() >= EVENT_QUEUE_SIZE_THRESHOLD) { - connectionQueue_.recordEvents(eventQueue_.events()); - } - } - - /** - * Called every 60 seconds to send a session heartbeat to the server. Does nothing if there - * is not an active application session. - */ - synchronized void onTimer() { - final boolean hasActiveSession = activityCount_ > 0; - if (hasActiveSession) { - if (!disableUpdateSessionRequests_) { - connectionQueue_.updateSession(roundedSecondsSinceLastSessionDurationUpdate()); - } - if (eventQueue_.size() > 0) { - connectionQueue_.recordEvents(eventQueue_.events()); - } - } - } - - /** - * Calculates the unsent session duration in seconds, rounded to the nearest int. - */ - int roundedSecondsSinceLastSessionDurationUpdate() { - final long currentTimestampInNanoseconds = System.nanoTime(); - final long unsentSessionLengthInNanoseconds = currentTimestampInNanoseconds - prevSessionDurationStartTime_; - prevSessionDurationStartTime_ = currentTimestampInNanoseconds; - return (int) Math.round(unsentSessionLengthInNanoseconds / 1000000000.0d); - } - - /** - * Utility method to return a current timestamp that can be used in the Count.ly API. - */ - static int currentTimestamp() { - return ((int)(System.currentTimeMillis() / 1000l)); - } - - /** - * Utility method for testing validity of a URL. - */ - static boolean isValidURL(final String urlStr) { - boolean validURL = false; - if (urlStr != null && urlStr.length() > 0) { - try { - new URL(urlStr); - validURL = true; - } - catch (MalformedURLException e) { - validURL = false; - } - } - return validURL; - } - - /** - * Allows public key pinning. - * Supply list of SSL certificates (base64-encoded strings between "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" without end-of-line) - * along with server URL starting with "https://". Seeds will only accept connections to the server - * if public key of SSL certificate provided by the server matches one provided to this method. - * @param certificates List of SSL certificates - * @return Seeds instance - */ - public static Seeds enablePublicKeyPinning(List certificates) { - publicKeyPinCertificates = certificates; - return Seeds.sharedInstance(); - } - - // for unit testing - ConnectionQueue getConnectionQueue() { - return connectionQueue_; - } - - void setConnectionQueue(final ConnectionQueue connectionQueue) { - connectionQueue_ = connectionQueue; - } - - ExecutorService getTimerService() { - return timerService_; - } - - EventQueue getEventQueue() { - return eventQueue_; - } - - void setEventQueue(final EventQueue eventQueue) { - eventQueue_ = eventQueue; - } - - long getPrevSessionDurationStartTime() { - return prevSessionDurationStartTime_; - } - - void setPrevSessionDurationStartTime(final long prevSessionDurationStartTime) { - prevSessionDurationStartTime_ = prevSessionDurationStartTime; - } - - int getActivityCount() { - return activityCount_; - } - - boolean getDisableUpdateSessionRequests() { - return disableUpdateSessionRequests_; - } - - /** - * This method is added solely for the purposes of testing - * Check: SeedsTests.java - */ - protected void clear() { - sharedInstance().eventQueue_ = null; - } - - /** - * Records an IAP event - * @param key name of the custom event, required, must not be the empty string - * @param price sum to associate with the event - * @throws IllegalStateException if Seeds SDK has not been initialized - * @throws IllegalArgumentException if key is null or empty - */ - public void recordIAPEvent(String key, final double price) { - recordGenericIAPEvent(key, price, false); - } - - /** - * Records an IAP event - * @param key name of the custom event, required, must not be the empty string - * @param price sum to associate with the event - * @throws IllegalStateException if Seeds SDK has not been initialized - * @throws IllegalArgumentException if key is null or empty - */ - public void recordSeedsIAPEvent(String key, final double price) { - recordGenericIAPEvent(key, price, true); - } - - public void requestInAppMessage(String messageId) { - InAppMessageManager.sharedInstance().requestInAppMessage(messageId, null); - } - - public void requestInAppMessage(String messageId, String manualLocalizedPrice) { - InAppMessageManager.sharedInstance().requestInAppMessage(messageId, manualLocalizedPrice); - } - - public boolean isInAppMessageLoaded(String messageId) { - return InAppMessageManager.sharedInstance().isInAppMessageLoaded(messageId); - } - - public void showInAppMessage(String messageId, String messageContext) { - InAppMessageManager.sharedInstance().showInAppMessage(messageId, messageContext); - } - - /** - * Query how many times in total a user has made IAP purchases which are tracked in Seeds - * @param listener Listener callback, first parameter is an error message and second parameter is - * the purchase count. If the error message is null, the request was successful. - */ - public void requestTotalInAppPurchaseCount(IInAppPurchaseCountListener listener) { - requestInAppPurchaseCount(null, listener); - } - - /** - * Query how many times in total a user has made IAP purchases which are tracked in Seeds - * @param key The key of the IAP which you have used in recordSeedsIAPEvent or recordIAPEvent - * @param listener Listener callback, first parameter is an error string and second parameter is - * the show count. If the error string is null, the request was successful. - */ - public void requestInAppPurchaseCount(final String key, final IInAppPurchaseCountListener listener) { - String endpoint = connectionQueue_.getServerURL() + "/o/app-user/query-iap-purchase-count"; - Uri.Builder uri = Uri.parse(endpoint).buildUpon(); - uri.appendQueryParameter("app_key", connectionQueue_.getAppKey()); - uri.appendQueryParameter("device_id", connectionQueue_.getDeviceId().getId()); - - if (key != null) - uri.appendQueryParameter("iap_key", key); - else - uri.appendPath("total"); - - asyncHttpClient.get(uri.build().toString(), new TextHttpResponseHandler() { - @Override - public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { - Log.e(TAG, "requestInAppPurchaseCount failed: " + responseString); - if (listener != null) - listener.onInAppPurchaseCount(responseString, -1, null); - } - - @Override - public void onSuccess(int statusCode, Header[] headers, String responseString) { - JsonObject jsonResponse = new JsonParser().parse(responseString).getAsJsonObject(); - if (listener != null) - listener.onInAppPurchaseCount(null, jsonResponse.get("result").getAsInt(), key); - } - }); - } - - /** - * Query how many times in total a user has seen interstitials - * @param listener Listener callback, first parameter is an error string and second parameter is - * the show count. If the error string is null, the request was successful. - */ - public void requestTotalInAppMessageShowCount(IInAppMessageShowCountListener listener) { - requestInAppMessageShowCount(null, listener); - } - - /** - * Query how many times user has seen a specific interstitial - * @param message_id The message_id of the interstitial - * @param listener Listener callback, first parameter is an error string and second parameter is - * the show count. If the error string is null, the request was successful. - */ - public void requestInAppMessageShowCount(final String message_id, final IInAppMessageShowCountListener listener) { - String endpoint = connectionQueue_.getServerURL() + "/o/app-user/query-interstitial-shown-count"; - Uri.Builder uri = Uri.parse(endpoint).buildUpon(); - uri.appendQueryParameter("app_key", connectionQueue_.getAppKey()); - uri.appendQueryParameter("device_id", connectionQueue_.getDeviceId().getId()); - - if (message_id != null) - uri.appendQueryParameter("interstitial_id", message_id); - else - uri.appendPath("total"); - - asyncHttpClient.get(uri.build().toString(), new TextHttpResponseHandler() { - @Override - public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { - Log.e(TAG, "requestInAppPurchaseCount failed: " + responseString); - if (listener != null) - listener.onInAppMessageShowCount(responseString, -1, null); - } - - @Override - public void onSuccess(int statusCode, Header[] headers, String responseString) { - JsonObject jsonResponse = new JsonParser().parse(responseString).getAsJsonObject(); - if (listener != null) - listener.onInAppMessageShowCount(null, jsonResponse.get("result").getAsInt(), message_id); - } - }); - } - - - /** - * Generalized user behaviour query - * @param queryPath The query path string for the query - * @param listener Listener callback, first parameter is an error string and second parameter is - * the result as a JsonObject. If the error string is null, the request was successful. - */ - public void requestGenericUserBehaviorQuery(final String queryPath, final IUserBehaviorQueryListener listener) { - String endpoint = connectionQueue_.getServerURL() + "/o/app-user/" + queryPath; - Uri.Builder uri = Uri.parse(endpoint).buildUpon(); - uri.appendQueryParameter("app_key", connectionQueue_.getAppKey()); - uri.appendQueryParameter("device_id", connectionQueue_.getDeviceId().getId()); - - asyncHttpClient.get(uri.build().toString(), new TextHttpResponseHandler() { - @Override - public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { - Log.e(TAG, "requestGenericUserBehaviourQuery failed: " + responseString); - if (listener != null) - listener.onUserBehaviorResponse(responseString, null, null); - } - - @Override - public void onSuccess(int statusCode, Header[] headers, String responseString) { - JsonObject jsonResponse = new JsonParser().parse(responseString).getAsJsonObject(); - if (listener != null) - listener.onUserBehaviorResponse(null, jsonResponse.get("result"), queryPath); - } - }); - } - -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/UserData.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/UserData.java deleted file mode 100755 index 8fd971028..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/UserData.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.playseeds.android.sdk; - - -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.net.URL; -import java.net.URLDecoder; -import java.util.HashMap; -import java.util.Map; - -import org.json.JSONException; -import org.json.JSONObject; - -import android.util.Log; - -public class UserData { - public static final String NAME_KEY = "name"; - public static final String USERNAME_KEY = "username"; - public static final String EMAIL_KEY = "email"; - public static final String ORG_KEY = "organization"; - public static final String PHONE_KEY = "phone"; - public static final String PICTURE_KEY = "picture"; - public static final String PICTURE_PATH_KEY = "picturePath"; - public static final String GENDER_KEY = "gender"; - public static final String BYEAR_KEY = "byear"; - public static final String CUSTOM_KEY = "custom"; - - public static String name; - public static String username; - public static String email; - public static String org; - public static String phone; - public static String picture; - public static String picturePath; - public static String gender; - public static Map custom; - public static int byear = 0; - public static boolean isSynced = true; - - /** - * Sets user data values. - * @param data Map with user data - */ - static void setData(Map data){ - if(data.containsKey(NAME_KEY)) - name = data.get(NAME_KEY); - if(data.containsKey(USERNAME_KEY)) - username = data.get(USERNAME_KEY); - if(data.containsKey(EMAIL_KEY)) - email = data.get(EMAIL_KEY); - if(data.containsKey(ORG_KEY)) - org = data.get(ORG_KEY); - if(data.containsKey(PHONE_KEY)) - phone = data.get(PHONE_KEY); - if(data.containsKey(PICTURE_PATH_KEY)) - picturePath = data.get(PICTURE_PATH_KEY); - if(picturePath != null){ - File sourceFile = new File(picturePath); - if (!sourceFile.isFile()) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(Seeds.TAG, "Provided file " + picturePath + " can not be opened"); - } - picturePath = null; - } - } - if(data.containsKey(PICTURE_KEY)) - picture = data.get(PICTURE_KEY); - if(data.containsKey(GENDER_KEY)) - gender = data.get(GENDER_KEY); - if(data.containsKey(BYEAR_KEY)){ - try { - byear = Integer.parseInt(data.get(BYEAR_KEY)); - } - catch(NumberFormatException e){ - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(Seeds.TAG, "Incorrect byear number format"); - } - byear = 0; - } - } - isSynced = false; - } - - /** - * Sets user custom properties and values. - * @param data Map with user custom key/values - */ - static void setCustomData(Map data){ - custom = new HashMap<>(); - custom.putAll(data); - isSynced = false; - } - - /** - * Returns &user_details= prefixed url to add to request data when making request to server - * @return a String user_details url part with provided user data - */ - static String getDataForRequest(){ - if(!isSynced){ - isSynced = true; - final JSONObject json = UserData.toJSON(); - if(json != null){ - String result = json.toString(); - - try { - result = java.net.URLEncoder.encode(result, "UTF-8"); - - if(result != null && !result.equals("")){ - result = "&user_details="+result; - if(picturePath != null) - result += "&"+PICTURE_PATH_KEY+"="+java.net.URLEncoder.encode(picturePath, "UTF-8"); - } - else{ - result = ""; - if(picturePath != null) - result += "&user_details&"+PICTURE_PATH_KEY+"="+java.net.URLEncoder.encode(picturePath, "UTF-8"); - } - } catch (UnsupportedEncodingException ignored) { - // should never happen because Android guarantees UTF-8 support - } - - if(result != null) - return result; - } - } - return ""; - } - - /** - * Creates and returns a JSONObject containing the user data from this object. - * @return a JSONObject containing the user data from this object - */ - static JSONObject toJSON() { - final JSONObject json = new JSONObject(); - - try { - if (name != null) - if(name.isEmpty()) - json.put(NAME_KEY, JSONObject.NULL); - else - json.put(NAME_KEY, name); - if (username != null) - if(username.isEmpty()) - json.put(USERNAME_KEY, JSONObject.NULL); - else - json.put(USERNAME_KEY, username); - if (email != null) - if(email.isEmpty()) - json.put(EMAIL_KEY, JSONObject.NULL); - else - json.put(EMAIL_KEY, email); - if (org != null) - if(org.isEmpty()) - json.put(ORG_KEY, JSONObject.NULL); - else - json.put(ORG_KEY, org); - if (phone != null) - if(phone.isEmpty()) - json.put(PHONE_KEY, JSONObject.NULL); - else - json.put(PHONE_KEY, phone); - if (picture != null) - if(picture.isEmpty()) - json.put(PICTURE_KEY, JSONObject.NULL); - else - json.put(PICTURE_KEY, picture); - if (gender != null) - if(gender.isEmpty()) - json.put(GENDER_KEY, JSONObject.NULL); - else - json.put(GENDER_KEY, gender); - if (byear != 0) - if(byear > 0) - json.put(BYEAR_KEY, byear); - else - json.put(BYEAR_KEY, JSONObject.NULL); - if(custom != null){ - if(custom.isEmpty()) - json.put(CUSTOM_KEY, JSONObject.NULL); - else - json.put(CUSTOM_KEY, new JSONObject(custom)); - } - } - catch (JSONException e) { - if (Seeds.sharedInstance().isLoggingEnabled()) { - Log.w(Seeds.TAG, "Got exception converting an UserData to JSON", e); - } - } - - return json; - } - - //for url query parsing - public static String getPicturePathFromQuery(URL url){ - String query = url.getQuery(); - String[] pairs = query.split("&"); - String ret = ""; - if(url.getQuery().contains(PICTURE_PATH_KEY)){ - for (String pair : pairs) { - int idx = pair.indexOf("="); - if(pair.substring(0, idx).equals(PICTURE_PATH_KEY)){ - try { - ret = URLDecoder.decode(pair.substring(idx + 1), "UTF-8"); - } catch (UnsupportedEncodingException e) { - ret = ""; - } - break; - } - } - } - return ret; - } -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/ClickType.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/ClickType.java deleted file mode 100755 index e4d1860da..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/ClickType.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: moved from data sub-package - */ - -package com.playseeds.android.sdk.inappmessaging; - -public enum ClickType { - INAPP, BROWSER; - - public static ClickType getValue(final String value) { - for (final ClickType clickType : ClickType.values()) - if (clickType.name().equalsIgnoreCase(value)) - return clickType; - return null; - } -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Const.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Const.java deleted file mode 100755 index 3a73cf9c7..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Const.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: Removed MRAID and video-specific code - */ - -package com.playseeds.android.sdk.inappmessaging; - -public interface Const { - - public static final String ENCODING = "UTF-8"; - public static final String RESPONSE_ENCODING = "ISO-8859-1"; - - public static final String VERSION = "6.1.0"; - - public static final String PROTOCOL_VERSION = "3.0"; - - public static final int LIVE = 0; - public static final int TEST = 1; - - public static final String IMAGE_BODY = ""; - // public static final String IMAGE_BODY = "
"; - public static final String REDIRECT_URI = "REDIRECT_URI"; - - public static final String HIDE_BORDER = ""; - public static final String INTERSTITIAL_HIDE_BORDER = ""; - - // public static final String HIDE_BORDER = ""; - - public static final int TOUCH_DISTANCE = 30; - -/* public static final long VIDEO_LOAD_TIMEOUT = 1200000;*/ - public static final int CONNECTION_TIMEOUT = 10000; // = 15 sec - public static final int SOCKET_TIMEOUT = 10000; // = 15 sec - - public static final String PREFS_DEVICE_ID = "device_id"; - - public static final String USER_AGENT_PATTERN = "Mozilla/5.0 (Linux; U; Android %1$s; %2$s; %3$s Build/%4$s) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"; - public static final String AD_EXTRA = "RICH_AD_DATA"; - public static final String AD_TYPE_EXTRA = "RICH_AD_TYPE"; - public static final int MAX_NUMBER_OF_TRACKING_RETRIES = 5; - - public static final String CONNECTION_TYPE_UNKNOWN = "UNKNOWN"; - public static final String CONNECTION_TYPE_WIFI = "WIFI"; - public static final String CONNECTION_TYPE_WIMAX = "WIMAX"; - public static final String CONNECTION_TYPE_MOBILE_UNKNOWN = "MOBILE"; - public static final String CONNECTION_TYPE_MOBILE_1xRTT = "1xRTT"; - public static final String CONNECTION_TYPE_MOBILE_CDMA = "CDMA"; - public static final String CONNECTION_TYPE_MOBILE_EDGE = "EDGE"; - public static final String CONNECTION_TYPE_MOBILE_EHRPD = "EHRPD"; - public static final String CONNECTION_TYPE_MOBILE_EVDO_0 = "EVDO_0"; - public static final String CONNECTION_TYPE_MOBILE_EVDO_A = "EVDO_A"; - public static final String CONNECTION_TYPE_MOBILE_EVDO_B = "EVDO_B"; - public static final String CONNECTION_TYPE_MOBILE_GPRS = "GPRS"; - public static final String CONNECTION_TYPE_MOBILE_HSDPA = "HSDPA"; - public static final String CONNECTION_TYPE_MOBILE_HSPA = "HSPA"; - public static final String CONNECTION_TYPE_MOBILE_HSPAP = "HSPAP"; - public static final String CONNECTION_TYPE_MOBILE_HSUPA = "HSUPA"; - public static final String CONNECTION_TYPE_MOBILE_IDEN = "IDEN"; - public static final String CONNECTION_TYPE_MOBILE_LTE = "LTE"; - public static final String CONNECTION_TYPE_MOBILE_UMTS = "UMTS"; - - public static final CharSequence LOADING = "Loading...."; - public static final long CACHE_DOWNLOAD_PERIOD = 10 * 60 * 1000; - - public final static int AD_FAILED = -1; - public final static int IMAGE = 0; - public final static int TEXT = 1; - public final static int NO_AD = 2; - -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Gender.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Gender.java deleted file mode 100755 index f4392403f..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Gender.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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.playseeds.android.sdk.inappmessaging; - -public enum Gender { - MALE("m"), - FEMALE("f"); - - private String param; - - Gender(String param) { - this.param = param; - } - - public String getServerParam() { - return this.param; - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/GeneralInAppMessageProvider.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/GeneralInAppMessageProvider.java deleted file mode 100755 index 8dd845380..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/GeneralInAppMessageProvider.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: removed video, custom event and MRAID-related code - * renamed from RequestGeneralAd - */ - -package com.playseeds.android.sdk.inappmessaging; - -import com.playseeds.android.sdk.Seeds; - -import java.io.InputStream; -import java.util.List; -import java.util.Map; - -import javax.json.Json; -import javax.json.JsonObject; -import javax.json.JsonReader; -import javax.json.JsonString; -import javax.json.JsonValue; - - -public class GeneralInAppMessageProvider extends InAppMessageProvider { - - public GeneralInAppMessageProvider() { - } - - public InAppMessageResponse parseCountlyJSON(final InputStream inputStream, Map> headers) throws RequestException { - - Log.i("Starting parseCountlyJSON"); - - final InAppMessageResponse response = new InAppMessageResponse(); - response.setType(Const.TEXT); - - ClickType clickType = ClickType.getValue("inapp"); - response.setClickType(clickType); - response.setRefresh(60); - response.setScale(false); - response.setSkipPreflight(true); - - try { - JsonReader jsonReader = Json.createReader(inputStream); - JsonObject jsonObject = jsonReader.readObject(); - response.setText(jsonObject.getString("htmlString")); - JsonValue jsonClickUrl = jsonObject.get("clickurl"); - - if (jsonClickUrl != null && !jsonClickUrl.equals(JsonValue.NULL) && - (jsonClickUrl instanceof JsonString)) { - response.setClickUrl(((JsonString) jsonClickUrl).getString()); - } else { - response.setSkipOverlay(1); - } - - JsonValue jsonProductId = jsonObject.get("productIdAndroid"); - if (jsonProductId != null && !jsonProductId.equals(JsonValue.NULL) && - (jsonProductId instanceof JsonString)) { - response.setProductId(((JsonString) jsonProductId).getString()); - } - - JsonValue jsonMessageVariant = jsonObject.get("messageVariant"); - if (jsonMessageVariant != null && !jsonMessageVariant.equals(JsonValue.NULL) && - (jsonMessageVariant instanceof JsonString)) { - response.setMessageVariant(((JsonString) jsonMessageVariant).getString()); - } - - // result of policies such as do not show to paying users - if (jsonObject.containsKey("doNotShow")) { - boolean doNotShow = jsonObject.getBoolean("doNotShow"); - InAppMessageManager.sharedInstance().doNotShow(doNotShow); - } else { - // show it! - InAppMessageManager.sharedInstance().doNotShow(false); - } - - jsonReader.close(); - - } catch (final Throwable t) { - Log.e(t.toString()); - throw new RequestException("Cannot read Response", t); - } - - return response; - } -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessage.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessage.java deleted file mode 100755 index 24b59b83d..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessage.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: renamed from Ad to InAppMessage - */ - -package com.playseeds.android.sdk.inappmessaging; - -import java.io.Serializable; - -public interface InAppMessage extends Serializable { - int getType(); - - void setType(int adType); -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageListener.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageListener.java deleted file mode 100755 index dd1ce22f1..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageListener.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: renamed from AdListener - */ - - -package com.playseeds.android.sdk.inappmessaging; - -public interface InAppMessageListener { - void inAppMessageClicked(String messageId); - - void inAppMessageDismissed(String messageId); - - void inAppMessageLoadSucceeded(String messageId); - - void inAppMessageShown(String messageId, boolean succeeded); - - void noInAppMessageFound(String messageId); - - void inAppMessageClickedWithDynamicPrice(String messageId, Double price); -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageManager.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageManager.java deleted file mode 100755 index 5df181900..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageManager.java +++ /dev/null @@ -1,571 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: removed video, MRAID and custom ad-specific code - * renamed from AdManager - */ - -package com.playseeds.android.sdk.inappmessaging; - -import static com.playseeds.android.sdk.inappmessaging.Const.AD_EXTRA; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.lang.Thread.UncaughtExceptionHandler; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.location.Location; - -import android.os.Bundle; -import android.os.Handler; - -import com.android.vending.billing.IInAppBillingService; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.playseeds.android.sdk.DeviceId; -import com.playseeds.android.sdk.Seeds; - -public class InAppMessageManager { - private String mAppKey; - private boolean adDoNotTrack; - private static Context mContext; - private static IInAppBillingService mBillingService; - private HashMap mRequestThreads; - private InAppMessageListener mListener; - private HashMap mResponses; - private String interstitialRequestURL; - private String mDeviceID; - private DeviceId.Type mIdMode; - private boolean requestedHorizontalAd; - private Gender userGender; - private int userAge; - private List keywords; - - private boolean doNotShow = false; - private HashMap mRequests = null; - private static HashMap sRunningAds = new HashMap<>(); - - /** - * Private Constructor - */ - private InAppMessageManager() { - } - - /** - * Returns the InAppMessageManager singleton. - */ - public static InAppMessageManager sharedInstance() { - return SingletonHolder.instance; - } - - // see http://stackoverflow.com/questions/7048198/thread-safe-singletons-in-java - private static class SingletonHolder { - static final InAppMessageManager instance = new InAppMessageManager(); - } - - public void init(Context context, IInAppBillingService billingService, final String interstitialRequestURL, final String appKey, final String deviceID, final DeviceId.Type idMode) { - Util.prepareAndroidAdId(context); - InAppMessageManager.setmContext(context); - InAppMessageManager.setmBillingService(billingService); - this.interstitialRequestURL = interstitialRequestURL; - mAppKey = appKey; - mDeviceID= deviceID; - mIdMode = idMode; - mContext = context; - - // Handle smoothly the case where message manager is initialized multiple times - // (don't remove already preloaded interstitials or interfere with the ongoing requests) - if (mResponses == null) mResponses = new HashMap<>(); - if (mRequestThreads == null) mRequestThreads = new HashMap<>(); - if (mRequests == null) mRequests = new HashMap<>(); - } - - public static void closeRunningInAppMessage(InAppMessageResponse ad) { - InAppMessageManager inAppMessageManager = sRunningAds.remove(ad.getTimestamp()); - - if (inAppMessageManager == null) { - Log.d("Cannot find InAppMessageManager with running ad:" + ad.getTimestamp()); - return; - } - } - - public static void notifyInAppMessageClick(InAppMessageResponse ad) { - InAppMessageManager inAppMessageManager = sRunningAds.get(ad.getTimestamp()); - if (inAppMessageManager != null) { - inAppMessageManager.notifyAdClicked(ad); - } - } - - public void requestInAppMessage(String messageId, String manualLocalizedPrice) { - requestInAppMessageInternal(messageId, manualLocalizedPrice); - } - - - private void requestInAppMessageInternal(final String messageId, final String manualLocalizedPrice) { - if (mRequestThreads.get(messageId) == null) { - Log.d("Requesting InAppMessage (v" + Const.VERSION + "-" + Const.PROTOCOL_VERSION + ")"); - mResponses.remove(messageId); - mRequestThreads.put(messageId, getRequestThread(messageId, manualLocalizedPrice)); - mRequestThreads.get(messageId).setUncaughtExceptionHandler(new UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread thread, Throwable ex) { - InAppMessageResponse mResponse = new InAppMessageResponse(); - mResponse.setType(Const.AD_FAILED); - mResponse.setMessageId(messageId); - mResponses.put(messageId, mResponse); - Log.e("Handling exception in ad request thread", ex); - mRequestThreads.remove(messageId); - } - }); - - mRequestThreads.get(messageId).start(); - } else { - Log.w("Request thread already running"); - } - } - - private Thread getRequestThread(final String messageId, final String manualLocalizedPrice) { - return new Thread(new Runnable() { - @Override - public void run() { - while (ResourceManager.isDownloading()) { - try { - Thread.sleep(200); - } catch (InterruptedException e) { - } - } - Log.d("starting request thread"); - try { - GeneralInAppMessageProvider requestAd = new GeneralInAppMessageProvider(); - mRequests.put(messageId, getInterstitialRequest(messageId)); - - try { - mResponses.put(messageId, requestAd.obtainInAppMessage(mRequests.get(messageId))); - mResponses.get(messageId).setMessageId(messageId); - mResponses.get(messageId).setManualLocalizedPrice(manualLocalizedPrice); - } catch (Exception e) { - File cachedInAppMessageFile = new File(mContext.getCacheDir(), - URLEncoder.encode(mRequests.get(messageId).countlyUriToString(), "UTF-8")); - if (cachedInAppMessageFile.exists()) { - BufferedReader cacheReader = new BufferedReader(new FileReader(cachedInAppMessageFile)); - mResponses.put(messageId, new Gson().fromJson(cacheReader, InAppMessageResponse.class)); - cacheReader.close(); - } else { - throw e; - } - } - - if (mResponses.get(messageId).getProductId() != null && (mBillingService != null)) { - try { - ArrayList productsList = new ArrayList(); - productsList.add(mResponses.get(messageId).getProductId()); - Bundle skuBundle = new Bundle(); - skuBundle.putStringArrayList("ITEM_ID_LIST", productsList); - - Bundle productsDetails; - - productsDetails = mBillingService.getSkuDetails(3, - mContext.getPackageName(), "inapp", skuBundle); - - if (productsDetails.getInt("RESPONSE_CODE", -1) == 0) { - ArrayList productsDetailsCollection - = productsDetails.getStringArrayList("DETAILS_LIST"); - Log.i("detailsCollection = " + productsDetailsCollection); - - for (String productDetails : productsDetailsCollection) { - JsonObject jsonProductDetails = new JsonParser().parse(productDetails).getAsJsonObject(); - - String productId = jsonProductDetails.get("productId").getAsString(); - if (!mResponses.get(messageId).getProductId().equals(productId)) - continue; - - String formattedPrice = jsonProductDetails.get("price").getAsString(); - mResponses.get(messageId).setFormattedPrice(formattedPrice); - - break; - } - } - } catch (Exception e) { - Log.e("BillingService", e); - } - } - String text = mResponses.get(messageId).getText(); - text = text.replace("%{LocalizedPrice}", - mResponses.get(messageId).getFormattedPrice() != null - ? mResponses.get(messageId).getFormattedPrice() - : "BUY"); - mResponses.get(messageId).setText(text); - - //TODO: remove debug code - Log.i("mResponse is: " + mResponses.get(messageId)); - - if ((mResponses.get(messageId).getType() == Const.TEXT || - mResponses.get(messageId).getType() == Const.IMAGE) && - messageId.equals(mResponses.get(messageId).getMessageId())) { - - notifyAdLoaded(mResponses.get(messageId)); - - BufferedWriter cacheWriter = null; - try { - File cachedInAppMessageFile = new File(mContext.getCacheDir(), - URLEncoder.encode(mRequests.get(messageId).countlyUriToString(), "UTF-8")); - cacheWriter = new BufferedWriter(new FileWriter(cachedInAppMessageFile)); - cacheWriter.write(new Gson().toJson(mResponses.get(messageId))); - } catch (Exception e) { - Log.e("Cache", e); - } finally { - try { - // Close the writer regardless of what happens... - cacheWriter.close(); - } catch (Exception e) { - } - } - } else if (mResponses.get(messageId).getType() == Const.NO_AD) { - Log.d("response NO AD received"); - notifyNoAdFound(messageId); - } else { - notifyNoAdFound(messageId); - } - } catch (Throwable t) { - Log.e("ad request failed", t); - - mResponses.put(messageId, new InAppMessageResponse()); - mResponses.get(messageId).setType(Const.AD_FAILED); - mResponses.get(messageId).setMessageId(messageId); - notifyNoAdFound(messageId); - } - Log.d("finishing ad request thread"); - mRequestThreads.remove(messageId); - } - }); - } - - public void showInAppMessage(String messageId, String messageContext) { - InAppMessageResponse mResponse = mResponses.get(messageId); - - if (((mResponse == null) - || (mResponse.getType() == Const.NO_AD) - || (mResponse.getType() == Const.AD_FAILED)) - || doNotShow) { - notifyAdShown(mResponse, false); - return; - } - - if (messageId != null && !messageId.equals(mResponse.getMessageId())) { - notifyAdShown(mResponse, false); - return; - } - - InAppMessageResponse ad = mResponse; - boolean result = false; - try { - ad.setTimestamp(System.currentTimeMillis()); - ad.setHorizontalOrientationRequested(requestedHorizontalAd); - ad.setMessageId(messageId); - ad.setMessageContext(messageContext); - - Log.v("Showing InAppMessage:" + ad); - - Intent intent = new Intent(getContext(), RichMediaActivity.class); - intent.putExtra(AD_EXTRA, ad); - getContext().startActivity(intent); - - result = true; - sRunningAds.put(ad.getTimestamp(), this); - } catch (Exception e) { - Log.e("Unknown exception when showing InAppMessage", e); - } finally { - notifyAdShown(ad, result); - } - } - - public boolean isInAppMessageLoaded(String messageId) { - InAppMessageResponse mResponse = mResponses.get(messageId); - - if (mResponse == null - || (mResponse.getType() == Const.NO_AD) - || (mResponse.getType() == Const.AD_FAILED)) - return false; - - return messageId == null || messageId.equals(mResponse.getMessageId()); - } - - private void notifyNoAdFound(final String messageId) { - if (mListener != null) { - Log.d("No ad found " + messageId); - sendNotification(new Runnable() { - @Override - public void run() { - mListener.noInAppMessageFound(messageId); - } - }); - } - this.mResponses.put(messageId, null); - } - - private void notifyAdClicked(final InAppMessageResponse ad) { - // Here the dynamic inAppMessageClickedWithDynamicPrice - String linkUrl = ad.getSeedsLinkUrl(); - - if (mListener != null) { - if (linkUrl != null && linkUrl.contains("/price/")) { - final Double price = Double.parseDouble(linkUrl.substring(linkUrl.lastIndexOf('/') + 1)); - HashMap customSegments = new HashMap<>(); - customSegments.put("price", price.toString()); - recordInterstitialEvent("dynamic price clicked", ad, customSegments); - - sendNotification(new Runnable() { - @Override - public void run() { - mListener.inAppMessageClickedWithDynamicPrice(ad.getMessageId(), price); - } - }); - } else if (linkUrl != null && linkUrl.contains("/social-share")) { - final String shareUrl = "http://playseeds.com/" + linkUrl.substring(linkUrl.lastIndexOf('/') + 1); - - Intent i = new Intent(Intent.ACTION_SEND); - i.setType("text/plain"); - i.putExtra(Intent.EXTRA_TEXT, shareUrl); - mContext.startActivity(Intent.createChooser(i, "Share URL")); - - recordInterstitialEvent("social share clicked", ad); - - sendNotification(new Runnable() { - @Override - public void run() { - mListener.inAppMessageClicked(ad.getMessageId()); - } - }); - } else if (linkUrl != null && linkUrl.contains("show-more")) { - recordInterstitialEvent("show more clicked", ad); - } else if (linkUrl != null && linkUrl.equals("about:close")) { - recordInterstitialEvent("message dismissed", ad); - sendNotification(new Runnable() { - @Override - public void run() { - mListener.inAppMessageDismissed(ad.getMessageId()); - } - }); - } else { - recordInterstitialEvent("message clicked", ad); - sendNotification(new Runnable() { - @Override - public void run() { - mListener.inAppMessageClicked(ad.getMessageId()); - } - }); - } - } - } - - private void notifyAdLoaded(final InAppMessageResponse ad) { - if (mListener != null) { - sendNotification(new Runnable() { - @Override - public void run() { - mListener.inAppMessageLoadSucceeded(ad.getMessageId()); - } - }); - } - } - - private void notifyAdShown(final InAppMessageResponse ad, final boolean ok) { - if (mListener != null) { - Log.d("InAppMessage Shown. Result:" + ok); - sendNotification(new Runnable() { - @Override - public void run() { - mListener.inAppMessageShown(ad.getMessageId(), ok); - } - }); - } - - if (ok) { - recordInterstitialEvent("message shown", ad); - } - } - - private void notifyAdDismiss(final InAppMessageResponse ad, final boolean ok) { - if (mListener != null) { - Log.d("InAppMessage Close. Result:" + ok); - sendNotification(new Runnable() { - @Override - public void run() { - // TODO: Trigger this only when the interstitial is being dismissed - - } - }); - } - } - - private InAppMessageRequest getInterstitialRequest(String messageId) { - if (this.mRequests.get(messageId) == null) { - this.mRequests.put(messageId, new InAppMessageRequest()); - - mRequests.get(messageId).setAdDoNotTrack(adDoNotTrack); - mRequests.get(messageId).setUserAgent(Util.getDefaultUserAgentString()); - mRequests.get(messageId).setUserAgent2(Util.buildUserAgent()); - } - - InAppMessageRequest request = this.mRequests.get(messageId); - - Location location = null; - - // TODO Atte Keinänen 12/8/16 - // Reduce the count of needed permissions by removing location tracking - request.setLatitude(0.0); - request.setLongitude(0.0); - - if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - requestedHorizontalAd = true; - } else { - requestedHorizontalAd = false; - } - - request.setAdspaceStrict(false); - - request.setConnectionType(Util.getConnectionType(getContext())); - request.setIpAddress(Util.getLocalIpAddress()); - request.setTimestamp(System.currentTimeMillis()); - request.setRequestURL(interstitialRequestURL); - request.setOrientation(getOrientation()); - request.setAppKey(mAppKey); - request.setDeviceId(mDeviceID); - request.setIdMode(mIdMode); - request.setMessageId(messageId); - return request; - } - - public void showInAppMessage() { - InAppMessageResponse mResponse = mResponses.get(null); - InAppMessageResponse ad = mResponse; - - boolean result = false; - - if (((mResponse == null) - || (mResponse.getType() == Const.NO_AD) - || (mResponse.getType() == Const.AD_FAILED)) - || doNotShow) { - notifyAdShown(mResponse, false); - return; - } - - try { - if (Util.isNetworkAvailable(getContext())) { - ad.setTimestamp(System.currentTimeMillis()); - ad.setHorizontalOrientationRequested(requestedHorizontalAd); - Log.v("Showing InAppMessage:" + ad); - - Intent intent = new Intent(getContext(), RichMediaActivity.class); - intent.putExtra(AD_EXTRA, ad); - getContext().startActivity(intent); - - result = true; - sRunningAds.put(ad.getTimestamp(), this); - } else { - Log.d("No network available. Cannot show InAppMessage."); - } - } catch (Exception e) { - Log.e("Unknown exception when showing InAppMessage", e); - } finally { - notifyAdShown(ad, result); - } - } - - /** - * This method handles sending notifications to the listeners - */ - private void sendNotification(Runnable runnable) { - // added for testing purposes - if (mContext.getPackageName().equals("com.playseeds.android.sdk")) { - new Thread(runnable).start(); - } - - Handler mainHandler = new Handler(mContext.getMainLooper()); - mainHandler.post(runnable); - } - - public void setListener(InAppMessageListener listener) { - this.mListener = listener; - } - - protected void setRunningAds(HashMap ads) { - sRunningAds = ads; - } - - private String getOrientation() { - if (requestedHorizontalAd) { - return "landscape"; - } else { - return "portrait"; - } - } - - private Context getContext() { - return getmContext(); - } - - private static Context getmContext() { - return mContext; - } - - private static void setmContext(Context mContext) { - InAppMessageManager.mContext = mContext; - } - - private static void setmBillingService(IInAppBillingService mBillingService) { - InAppMessageManager.mBillingService = mBillingService; - } - - public void setUserGender(Gender userGender) { - this.userGender = userGender; - } - - public void setUserAge(int userAge) { - this.userAge = userAge; - } - - public void setKeywords(List keywords) { - this.keywords = keywords; - } - - public void recordInterstitialEvent(String key, InAppMessageResponse ad) { - recordInterstitialEvent(key, ad, null); - } - - public void recordInterstitialEvent(String key, InAppMessageResponse ad, HashMap customSegments) { - HashMap segmentation = new HashMap<>(); - segmentation.put("message", ad.getMessageId()); - if (ad.getMessageContext().length() > 0) segmentation.put("context", ad.getMessageContext()); - if (ad.getMessageVariant().length() > 0) segmentation.put("variant", ad.getMessageVariant()); - if (customSegments != null) segmentation.putAll(customSegments); - - Seeds.sharedInstance().recordEvent(key, segmentation, 1); - } - - public void doNotShow(boolean doNotShow) { - this.doNotShow = doNotShow; - } -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageProvider.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageProvider.java deleted file mode 100755 index 1221bda2e..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageProvider.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: renamed from RequestAd - * - */ - -package com.playseeds.android.sdk.inappmessaging; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.List; -import java.util.Map; - -public abstract class InAppMessageProvider { - - public T obtainInAppMessage(InAppMessageRequest request) throws RequestException { - Log.i("sendCountlyRequest"); - Log.d("Parse Real"); - - String url = request.countlyUriToString(); - HttpURLConnection urlConnection = null; - - Log.i("InAppMessage RequestPerform HTTP Get Url: " + url); - - try { - urlConnection = (HttpURLConnection) new URL(url).openConnection(); - //urlConnection.setRequestProperty("User-Agent", System.getProperty("http.agent")); - InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream()); - int responseCode = urlConnection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - return parseCountlyJSON(inputStream, urlConnection.getHeaderFields()); - } else { - throw new RequestException("Server Error. Response code:" - + responseCode); - } - } catch (Throwable t) { - throw new RequestException("Error in HTTP request", t); - } finally { - if (urlConnection != null) - urlConnection.disconnect(); - } - } - - protected abstract T parseCountlyJSON(InputStream inputStream, Map> headers) throws RequestException; -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageRequest.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageRequest.java deleted file mode 100755 index f498ef313..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageRequest.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: removed video, MRAID and custom ad-specific code - * renamed from AdRequest - */ - -package com.playseeds.android.sdk.inappmessaging; - -import java.util.List; - -import android.net.Uri; -import android.os.Build; - -import com.playseeds.android.sdk.DeviceId; - -public class InAppMessageRequest { - private String userAgent; - private String userAgent2; - private String headers; - private String listAds; - private String requestURL; - private String protocolVersion; - private String appKey; - private String deviceId; - private DeviceId.Type idMode; - private String messageId; - - private double longitude = 0.0; - private double latitude = 0.0; - private boolean adspaceStrict; - private int adspaceWidth; - private int adspaceHeight; - private Gender gender; - private int userAge; - private List keywords; - private String ipAddress; - private String connectionType; - private long timestamp; - private String orientation; - private String androidAdId = ""; - private boolean adDoNotTrack = false; - private static final String REQUEST_TYPE_ANDROID = "android_app"; - // sleep in ms if device_id not loaded yet - private static final int DEVICE_ID_MS_DELAY = 500; - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public void setGender(Gender gender) { - this.gender = gender; - } - - public String getMessageId() { - return messageId; - } - - public void setMessageId(String messageId) { - this.messageId = messageId; - } - - public void setUserAge(int userAge) { - this.userAge = userAge; - } - - public void setKeywords(List keywords) { - this.keywords = keywords; - } - - public void setIdMode(DeviceId.Type idMode) { - this.idMode = idMode; - } - - public void setConnectionType(final String connectionType) { - this.connectionType = connectionType; - } - - public void setAdDoNotTrack(boolean adDoNotTrack) { - this.adDoNotTrack = adDoNotTrack; - } - - public void setOrientation(String orientation) { - this.orientation = orientation; - } - - public void setHeaders(final String headers) { - this.headers = headers; - } - - public void setIpAddress(final String ipAddress) { - this.ipAddress = ipAddress; - } - - public void setLatitude(final double latitude) { - this.latitude = latitude; - } - - public void setListAds(final String listAds) { - this.listAds = listAds; - } - - public void setLongitude(final double longitude) { - this.longitude = longitude; - } - - public void setProtocolVersion(final String protocolVersion) { - this.protocolVersion = protocolVersion; - } - - public void setAppKey(final String appKey) { - this.appKey = appKey; - } - - public void setTimestamp(final long timestamp) { - this.timestamp = timestamp; - } - - public void setUserAgent(final String userAgent) { - this.userAgent = userAgent; - } - - public void setUserAgent2(final String userAgent) { - this.userAgent2 = userAgent; - } - - public void setRequestURL(String requestURL) { - this.requestURL = requestURL; - } - - public void setAdspaceStrict(boolean adspaceStrict) { - this.adspaceStrict = adspaceStrict; - } - - public void setAdspaceWidth(int adspaceWidth) { - this.adspaceWidth = adspaceWidth; - } - - public void setAdspaceHeight(int adspaceHeight) { - this.adspaceHeight = adspaceHeight; - } - - public void setAndroidAdId(String androidAdId) { - this.androidAdId = androidAdId; - } - - public String countlyUriToString() { - return this.toCountlyUri().toString(); - } - - //TODO: make sure "deviceCategory" gets to server code - public Uri toCountlyUri() { - String countlyURL = requestURL; - String path = "/o/messages"; - final Uri.Builder b = Uri.parse((countlyURL + path)).buildUpon(); - - b.appendQueryParameter("app_key", appKey); - b.appendQueryParameter("orientation", orientation); - - if (deviceId == null || deviceId.isEmpty()) { - deviceId = Util.getAndroidAdId(); - if (deviceId == null || deviceId.isEmpty()) { - try { - Thread.sleep(DEVICE_ID_MS_DELAY); - } catch (InterruptedException e) { - Log.e("Sleep interrupted: " + e); - } - deviceId = Util.getAndroidAdId(); - } - } - - if (deviceId == null || deviceId.isEmpty()) { - Log.e("Device Id could not be set"); - } - b.appendQueryParameter("device_id", deviceId); - //b.appendQueryParameter("device_id_type", idMode.toString()); //currently unused - if (messageId != null) - b.appendQueryParameter("message_id", messageId); - - return b.build(); - } - - public String getRequestURL() { - return requestURL; - } - - public boolean isAdspaceStrict() { - return adspaceStrict; - } - - public int getAdspaceWidth() { - return adspaceWidth; - } - - public int getAdspaceHeight() { - return adspaceHeight; - } - - public String getAndroidAdId() { - return androidAdId; - } - - public Boolean hasAdDoNotTrack() { - return adDoNotTrack; - } - - public String getOrientation() { - return orientation; - } - - public DeviceId.Type getIdMode() { - return idMode; - } - - public String getAndroidVersion() { - return Build.VERSION.RELEASE; - } - - public String getConnectionType() { - return this.connectionType; - } - - public String getDeviceMode() { - return Build.MODEL; - } - - public String getDeviceId() { - return deviceId; - } - - - public String getHeaders() { - if (this.headers == null) - return ""; - return this.headers; - } - - public String getIpAddress() { - if (this.ipAddress == null) - return ""; - return this.ipAddress; - } - - public double getLatitude() { - return this.latitude; - } - - public String getListAds() { - if (this.listAds != null) - return this.listAds; - else - return ""; - } - - public double getLongitude() { - return this.longitude; - } - - public String getProtocolVersion() { - if (this.protocolVersion == null) - return Const.VERSION; - else - return this.protocolVersion; - } - - public String getAppKey() { - if (this.appKey == null) - return ""; - return this.appKey; - } - - public String getRequestType() { - return InAppMessageRequest.REQUEST_TYPE_ANDROID; - } - - public long getTimestamp() { - return this.timestamp; - } - - public String getUserAgent() { - if (this.userAgent == null) - return ""; - return this.userAgent; - } - - public String getUserAgent2() { - if (this.userAgent2 == null) - return ""; - return this.userAgent2; - } - - public Gender getGender() { - return this.gender; - } - - public int getUserAge() { - return this.userAge; - } - - public List getKeywords() { - return this.keywords; - } -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageResponse.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageResponse.java deleted file mode 100755 index e549261c0..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageResponse.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: removed video and custom ad-specific code - * renamed from AdResponse - */ - -package com.playseeds.android.sdk.inappmessaging; - -public class InAppMessageResponse implements InAppMessage { - public static final String OTHER = "other"; - private static final long serialVersionUID = 3271938798582141269L; - private int type; - private int bannerWidth; - private int bannerHeight; - private String text; - private int skipOverlay = 0; - private String imageUrl; - private String productId; - private String formattedPrice; - private String manualLocalizedPrice; - - public String getFormattedPrice() { - boolean overrideWithManuallyEnteredPrice = manualLocalizedPrice != null; - - if (overrideWithManuallyEnteredPrice) { - return manualLocalizedPrice; - } else { - return formattedPrice; - } - } - - public InAppMessageResponse setFormattedPrice(String formattedPrice) { - this.formattedPrice = formattedPrice; - return this; - } - - private ClickType clickType; - private String clickUrl; - private String urlType; - private String seedsLinkUrl; - private int refresh; - private boolean scale; - private boolean skipPreflight; - private long timestamp; - private boolean horizontalOrientationRequested; - private String messageId; - private String messageVariant; - private String messageContext; - - public String getProductId() { - return this.productId; - } - - public boolean isScale() { - return this.scale; - } - - public boolean isSkipPreflight() { - return this.skipPreflight; - } - - public boolean isHorizontalOrientationRequested() { - return horizontalOrientationRequested; - } - - public void setBannerHeight(final int bannerHeight) { - this.bannerHeight = bannerHeight; - } - - public void setBannerWidth(final int bannerWidth) { - this.bannerWidth = bannerWidth; - } - - public void setClickType(final ClickType clickType) { - this.clickType = clickType; - } - - public void setClickUrl(final String clickUrl) { - this.clickUrl = clickUrl; - } - - public void setSeedsLinkUrl(final String seedsLinkUrl) { - this.seedsLinkUrl = seedsLinkUrl; - } - - public void setProductId(final String productId) { - this.productId = productId; - } - - public void setImageUrl(final String imageUrl) { - this.imageUrl = imageUrl; - } - - public void setRefresh(final int refresh) { - this.refresh = refresh; - } - - public void setScale(final boolean scale) { - this.scale = scale; - } - - public void setSkipPreflight(final boolean skipPreflight) { - this.skipPreflight = skipPreflight; - } - - public void setText(final String text) { - this.text = text; - } - - public void setUrlType(final String urlType) { - this.urlType = urlType; - } - - @Override - public void setType(final int adType) { - this.type = adType; - } - - public void setSkipOverlay(int skipOverlay) { - this.skipOverlay = skipOverlay; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - public void setHorizontalOrientationRequested(boolean horizontalOrientationRequested) { - this.horizontalOrientationRequested = horizontalOrientationRequested; - } - - public long getTimestamp() { - return timestamp; - } - - public int getBannerHeight() { - return this.bannerHeight; - } - - public int getBannerWidth() { - return this.bannerWidth; - } - - public ClickType getClickType() { - return this.clickType; - } - - public String getClickUrl() { - return this.clickUrl; - } - - public String getSeedsLinkUrl() { - return this.seedsLinkUrl; - } - - public String getImageUrl() { - return this.imageUrl; - } - - public int getRefresh() { - return this.refresh; - } - - public String getText() { - return this.text; - } - - public String getUrlType() { - return this.urlType; - } - - @Override - public int getType() { - return this.type; - } - - public String getString() { - return "Response [refresh=" + this.refresh + ", type=" + this.type - + ", bannerWidth=" + this.bannerWidth + ", bannerHeight=" - + this.bannerHeight + ", text=" + this.text + ", imageUrl=" - + this.imageUrl + ", clickType=" + this.clickType - + ", clickUrl=" + this.clickUrl + ", urlType=" + this.urlType - + ", scale=" + this.scale + ", skipPreflight=" - + this.skipPreflight + "]"; - } - - public int getSkipOverlay() { - return skipOverlay; - } - - public String getMessageId() { - return messageId; - } - - public void setMessageId(String messageIdRequested) { - this.messageId = messageIdRequested; - } - - public String getMessageVariant() { - return messageVariant != null ? messageVariant : ""; - } - - public void setMessageVariant(String messageVariant) { - this.messageVariant = messageVariant; - } - - public String getMessageContext() { - return messageContext != null ? messageContext : ""; - } - - public void setMessageContext(String messageContext) { - this.messageContext = messageContext; - } - - - public void setManualLocalizedPrice(String manualLocalizedPrice) { - this.manualLocalizedPrice = manualLocalizedPrice; - } -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageView.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageView.java deleted file mode 100755 index c9b3f85f7..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppMessageView.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: moved from banner sub-package - * renamed from BannerAdView - * removed HttpClient - */ - -package com.playseeds.android.sdk.inappmessaging; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.text.MessageFormat; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.graphics.Canvas; -import android.graphics.Color; -import android.net.Uri; -import android.os.AsyncTask; -import android.view.MotionEvent; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.RelativeLayout; - -@SuppressLint({ "ViewConstructor", "SetJavaScriptEnabled" }) -public class InAppMessageView extends RelativeLayout { - private boolean animation; - private InAppMessageResponse response; - private WebSettings webSettings; - private WebView webView; - private int width; - private int height; - private BannerAdViewListener adListener; - private static Method mWebView_SetLayerType; - private static Field mWebView_LAYER_TYPE_SOFTWARE; - - private boolean wasUserAction = false; - private Animation fadeInAnimation = null; - private Context mContext = null; - - public InAppMessageView(final Context context, final InAppMessageResponse response, int width, int height, final boolean animation, final BannerAdViewListener adListener) { - super(context); - mContext = context; - this.response = response; - this.width = width; - this.height = height; - this.animation = animation; - this.adListener = adListener; - this.initialize(); - } - - private WebView createWebView() { - final WebView webView = new WebView(this.getContext()) { - @Override - public boolean onTouchEvent(MotionEvent event) { - wasUserAction = true; - return super.onTouchEvent(event); - } - - @Override - public void draw(final Canvas canvas) { - if (this.getWidth() > 0 && this.getHeight() > 0) - super.draw(canvas); - } - }; - - this.webSettings = webView.getSettings(); - this.webSettings.setJavaScriptEnabled(true); - webView.setBackgroundColor(Color.TRANSPARENT); - setLayer(webView); - - webView.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(final WebView view, final String url) { - if (wasUserAction) { - if (response.getSkipOverlay() == 1) { - doOpenUrl(url); - return true; - } - openLink(); - return true; - } - return false; - } - }); - - webView.setVerticalScrollBarEnabled(false); - webView.setHorizontalScrollBarEnabled(false); - - return webView; - } - - private void doOpenUrl(final String url) { - if (this.response.getClickUrl() != null && this.response.getSkipOverlay() == 1) { - makeTrackingRequest(this.response.getClickUrl()); - } - - if (this.response.getClickType() != null && this.response.getClickType().equals(ClickType.INAPP) && (url.startsWith("http://") || url.startsWith("https://"))) { - if (url.endsWith(".mp4")) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType(Uri.parse(url), "video/mp4"); - startActivity(i); - } else { - final Intent intent = new Intent(this.getContext(), InAppWebView.class); - intent.putExtra(Const.REDIRECT_URI, url); - startActivity(intent); - } - } else { - this.response.setSeedsLinkUrl(url); - adListener.onClick(); - - // TODO Atte Keinänen 9/14 - // When implementing a more comprehensive interstitial link protocol, - // refactor this to a higher abstraction layer - final boolean linkClosesInAppMessage = - !(url.contains("social-share") || url.contains("show-more")) || url.equals("about:close"); - - if (linkClosesInAppMessage) { - RichMediaActivity activity = (RichMediaActivity)getContext(); - activity.close(); - } - } - } - - /** - * This method allows the activity to be started from outside an activity - */ - private void startActivity(Intent i) { - if (!(getContext() instanceof Activity)) { - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - - try { - getContext().startActivity(i); - } catch (Exception e) { - Log.e("Failed to start activity using " + i.toString(), e); - adListener.onError(); - } - } - - private void makeTrackingRequest(final String clickUrl) { - AsyncTask task = new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - if (clickUrl.startsWith("market")) { // just to stay safe - return null; - } - HttpURLConnection urlConnection = null; - try { - urlConnection = (HttpURLConnection) new URL(clickUrl).openConnection(); - urlConnection.setRequestProperty("User-Agent", System.getProperty("http.agent")); - InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream()); - byte[] data = new byte[16384]; - while (inputStream.read(data, 0, data.length) != -1); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (urlConnection != null) - urlConnection.disconnect(); - } - return null; - } - }; - task.execute(); - } - - static { - initCompatibility(); - } - - private static void initCompatibility() { - try { - for (Method m : WebView.class.getMethods()) { - if (m.getName().equals("setLayerType")) { - mWebView_SetLayerType = m; - break; - } - } - - Log.v("set layer " + mWebView_SetLayerType); - mWebView_LAYER_TYPE_SOFTWARE = WebView.class.getField("LAYER_TYPE_SOFTWARE"); - Log.v("set1 layer " + mWebView_LAYER_TYPE_SOFTWARE); - - } catch (SecurityException e) { - Log.v("SecurityException"); - } catch (NoSuchFieldException e) { - Log.v("NoSuchFieldException"); - } - } - - private static void setLayer(WebView webView) { - if (mWebView_SetLayerType != null && mWebView_LAYER_TYPE_SOFTWARE != null) { - try { - Log.v("Set Layer is supported"); - mWebView_SetLayerType.invoke(webView, mWebView_LAYER_TYPE_SOFTWARE.getInt(WebView.class), null); - } catch (InvocationTargetException ite) { - Log.v("Set InvocationTargetException"); - } catch (IllegalArgumentException e) { - Log.v("Set IllegalArgumentException"); - } catch (IllegalAccessException e) { - Log.v("Set IllegalAccessException"); - } - } else { - Log.v("Set Layer is not supported"); - } - } - - private void buildBannerView() { - this.webView = this.createWebView(); - Log.d("Create view flipper"); - if (this.response != null && this.response.getClickUrl() != null) { - final float scale = mContext.getResources().getDisplayMetrics().density; - if (width > 0 && height > 0) { - this.setLayoutParams(new RelativeLayout.LayoutParams((int) (width * scale + 0.5f), (int) (height * scale + 0.5f))); - } else { - this.setLayoutParams(new RelativeLayout.LayoutParams((int) (300 * scale + 0.5f), (int) (50 * scale + 0.5f))); - } - } else { - this.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - } - - final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - this.addView(this.webView, params); - - Log.d("animation: " + this.animation); - if (this.animation) { - this.fadeInAnimation = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, +1.0f, Animation.RELATIVE_TO_PARENT, 0.0f); - this.fadeInAnimation.setDuration(1000); - this.webView.setAnimation(fadeInAnimation); - } - } - - private void initialize() { - initCompatibility(); - buildBannerView(); - } - - private void openLink() { - if (this.response != null && this.response.getClickUrl() != null) - this.doOpenUrl(this.response.getClickUrl()); - } - - public void showContent() { - try { - if (this.response.getType() == Const.IMAGE) { - - String text = MessageFormat.format(Const.IMAGE_BODY, this.response.getImageUrl(), this.response.getBannerWidth(), this.response.getBannerHeight()); - Log.d("set image: " + text); - if (!text.contains("")) { - text = "" + Const.HIDE_BORDER + text + ""; - } - webView.getSettings().setDefaultTextEncodingName("utf-8"); - webView.loadData(text, "text/html; charset=utf-8", Const.ENCODING); - adListener.onLoad(); - } else if (this.response.getType() == Const.TEXT) { - String text = this.response.getText(); - Log.d("set text: " + text); - if (!text.contains("")) { - text = "" + Const.HIDE_BORDER + text + ""; - } - webView.getSettings().setDefaultTextEncodingName("utf-8"); - webView.loadData(text, "text/html; charset=utf-8", Const.ENCODING); - adListener.onLoad(); - } - if (animation) { - webView.startAnimation(fadeInAnimation); - } - } catch (final Throwable t) { - Log.e("Exception in show content", t); - } - } - - public interface BannerAdViewListener { - void onLoad(); - - void onClick(); - - void onError(); - } - - /** - * This method is added to enable testing - * See: InAppMessageViewTest - * @return BannerAdViewListener - */ - public BannerAdViewListener getAdListener() { - return adListener; - } - - /** - * This method is added to enable testing - * See: InAppMessageViewTest - * @return InAppMessageResponse - */ - public InAppMessageResponse getResponse() { - return response; - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppWebView.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppWebView.java deleted file mode 100755 index f0acc3804..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/InAppWebView.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: moved from banner sub-package - */ - -package com.playseeds.android.sdk.inappmessaging; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.Window; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.Toast; - -public class InAppWebView extends Activity { - - public static final String URL_EXTRA = "extra_url"; - - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getWindow().requestFeature(Window.FEATURE_PROGRESS); - getWindow().setFeatureInt(Window.FEATURE_PROGRESS, Window.PROGRESS_VISIBILITY_ON); - - final Intent intent = this.getIntent(); - - initializeWebView(intent); - } - - @SuppressLint("SetJavaScriptEnabled") - private void initializeWebView(Intent intent) { - WebView webView = new WebView(this); - this.setContentView(webView); - WebSettings webSettings = webView.getSettings(); - - webSettings.setJavaScriptEnabled(true); - - webSettings.setSupportZoom(true); - webSettings.setBuiltInZoomControls(true); - webSettings.setUseWideViewPort(true); - webView.setWebViewClient(new WebViewClient() { - @Override - public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - Activity a = (Activity) view.getContext(); - Toast.makeText(a, "MRAID error: " + description, Toast.LENGTH_SHORT).show(); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (url == null) - return false; - - Uri uri = Uri.parse(url); - String host = uri.getHost(); - - if ((url.startsWith("http:") || url.startsWith("https:")) && !"play.google.com".equals(host) && !"market.android.com".equals(host) && !url.endsWith(".apk")) { - view.loadUrl(url); - return true; - } - - try { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); - } catch (ActivityNotFoundException exception) { - Log.w("MoPub: Unable to start activity for " + url + ". " + "Ensure that your phone can handle this intent."); - } - - finish(); - return true; - } - }); - - webView.setWebChromeClient(new WebChromeClient() { - public void onProgressChanged(WebView view, int progress) { - Activity a = (Activity) view.getContext(); - a.setTitle("Loading..."); - a.setProgress(progress * 100); - if (progress == 100) - a.setTitle(view.getUrl()); - } - }); - webView.loadUrl(intent.getStringExtra(Const.REDIRECT_URI)); - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Log.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Log.java deleted file mode 100755 index c335201f8..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Log.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: TAG variable value - */ - -package com.playseeds.android.sdk.inappmessaging; - -public final class Log { - - /* - * Enable logging DEBUG logs on a device: - * adb shell setprop log.tag.ADSDK DEBUG - */ - - public static final String TAG = "Seeds IAM"; - public static final boolean LOG_AD_RESPONSES = false; - - public static boolean isLoggable(int logLevel) { -// return android.util.Log.isLoggable(TAG, logLevel); - return true; - } - - public static void d(final String msg) { - if (isLoggable(android.util.Log.DEBUG)) { - android.util.Log.d(TAG, msg); - } - } - - public static void d(final String msg, final Throwable tr) { - if (isLoggable(android.util.Log.DEBUG)) { - android.util.Log.d(TAG, msg, tr); - } - } - - public static void e(final String msg) { - if (isLoggable(android.util.Log.ERROR)) { - android.util.Log.e(TAG, msg); - } - } - - public static void e(final String msg, final Throwable tr) { - if (isLoggable(android.util.Log.ERROR)) { - android.util.Log.w(TAG, msg, tr); - } - } - - public static void i(final String msg) { - if (isLoggable(android.util.Log.INFO)) { - android.util.Log.i(TAG, msg); - } - } - - public static void i(final String msg, final Throwable tr) { - if (isLoggable(android.util.Log.INFO)) { - android.util.Log.i(TAG, msg, tr); - } - } - - public static void v(final String msg) { - if (isLoggable(android.util.Log.VERBOSE)) { - android.util.Log.v(TAG, msg); - } - } - - public static void v(final String msg, final Throwable tr) { - if (isLoggable(android.util.Log.VERBOSE)) { - android.util.Log.v(TAG, msg, tr); - } - } - - public static void w(final String msg) { - if (isLoggable(android.util.Log.WARN)) { - android.util.Log.w(TAG, msg); - } - } - - public static void w(final String msg, final Throwable tr) { - if (isLoggable(android.util.Log.WARN)) { - android.util.Log.w(TAG, msg, tr); - } - } -} \ No newline at end of file diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/RequestException.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/RequestException.java deleted file mode 100755 index a48890206..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/RequestException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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.playseeds.android.sdk.inappmessaging; - -public class RequestException extends Exception { - - public RequestException() { - super(); - } - - public RequestException(final String detailMessage) { - super(detailMessage); - } - - public RequestException(final String detailMessage, - final Throwable throwable) { - super(detailMessage, throwable); - } - - public RequestException(final Throwable throwable) { - super(throwable); - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/ResourceManager.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/ResourceManager.java deleted file mode 100755 index 8007fd2c4..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/ResourceManager.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: moved from video sub-package; removed HttpClient - */ - -package com.playseeds.android.sdk.inappmessaging; - -import java.io.FileNotFoundException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map.Entry; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -import com.playseeds.android.sdk.BuildConfig; - -public class ResourceManager { - public static final int RESOURCE_LOADED_MSG = 100; - public static boolean sDownloading = false; - public static boolean sCancel = false; - public static final int DEFAULT_TOPBAR_BG_RESOURCE_ID = -1; - public static final int DEFAULT_BOTTOMBAR_BG_RESOURCE_ID = -2; - public static final int DEFAULT_PLAY_IMAGE_RESOURCE_ID = -11; - public static final int DEFAULT_PAUSE_IMAGE_RESOURCE_ID = -12; - public static final int DEFAULT_REPLAY_IMAGE_RESOURCE_ID = -13; - public static final int DEFAULT_BACK_IMAGE_RESOURCE_ID = -14; - public static final int DEFAULT_FORWARD_IMAGE_RESOURCE_ID = -15; - public static final int DEFAULT_RELOAD_IMAGE_RESOURCE_ID = -16; - public static final int DEFAULT_EXTERNAL_IMAGE_RESOURCE_ID = -17; - public static final int DEFAULT_SKIP_IMAGE_RESOURCE_ID = -18; - public static final int DEFAULT_CLOSE_BUTTON_NORMAL_RESOURCE_ID = -29; - public static final int DEFAULT_CLOSE_BUTTON_PRESSED_RESOURCE_ID = -30; - public static final String PLAY_IMAGE_DRAWABLE = "video_play"; - public static final String PAUSE_IMAGE_DRAWABLE = "video_pause"; - public static final String REPLAY_IMAGE_DRAWABLE = "video_replay"; - public static final String BACK_IMAGE_DRAWABLE = "browser_back"; - public static final String FORWARD_IMAGE_DRAWABLE = "browser_forward"; - public static final String RELOAD_IMAGE_DRAWABLE = "browser_reload"; - public static final String EXTERNAL_IMAGE_DRAWABLE = "browser_external"; - public static final String SKIP_IMAGE_DRAWABLE = "skip"; - public static final String BAR_IMAGE_DRAWABLE = "bar"; - public static final String CLOSE_BUTTON_NORMAL_IMAGE_DRAWABLE = "close_button_normal"; - public static final String CLOSE_BUTTON_PRESSED_IMAGE_DRAWABLE = "close_button_pressed"; - private static HashMap sResources = new HashMap<>(); - private HashMap mResources = new HashMap<>(); - - public ResourceManager() { - } - - public void releaseInstance(){ - Iterator> it = mResources.entrySet().iterator(); - while(it.hasNext()) { - Entry pairsEntry = (Entry)it.next(); - it.remove(); - BitmapDrawable d = (BitmapDrawable) pairsEntry.getValue(); - - } - assert(mResources.size()==0); - System.gc(); - } - - private static void initDefaultResource(Context ctx, int resource) { - switch (resource) { - case DEFAULT_PLAY_IMAGE_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_PLAY_IMAGE_RESOURCE_ID, PLAY_IMAGE_DRAWABLE); - break; - case DEFAULT_PAUSE_IMAGE_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_PAUSE_IMAGE_RESOURCE_ID, PAUSE_IMAGE_DRAWABLE); - break; - case DEFAULT_REPLAY_IMAGE_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_REPLAY_IMAGE_RESOURCE_ID, REPLAY_IMAGE_DRAWABLE); - break; - case DEFAULT_BACK_IMAGE_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_BACK_IMAGE_RESOURCE_ID, BACK_IMAGE_DRAWABLE); - break; - case DEFAULT_FORWARD_IMAGE_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_FORWARD_IMAGE_RESOURCE_ID, FORWARD_IMAGE_DRAWABLE); - break; - case DEFAULT_RELOAD_IMAGE_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_RELOAD_IMAGE_RESOURCE_ID, RELOAD_IMAGE_DRAWABLE); - break; - case DEFAULT_EXTERNAL_IMAGE_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_EXTERNAL_IMAGE_RESOURCE_ID, EXTERNAL_IMAGE_DRAWABLE); - break; - case DEFAULT_SKIP_IMAGE_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_SKIP_IMAGE_RESOURCE_ID, SKIP_IMAGE_DRAWABLE); - break; - case DEFAULT_TOPBAR_BG_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_TOPBAR_BG_RESOURCE_ID, BAR_IMAGE_DRAWABLE); - break; - case DEFAULT_BOTTOMBAR_BG_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_BOTTOMBAR_BG_RESOURCE_ID, BAR_IMAGE_DRAWABLE); - break; - case DEFAULT_CLOSE_BUTTON_NORMAL_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_CLOSE_BUTTON_NORMAL_RESOURCE_ID, - CLOSE_BUTTON_NORMAL_IMAGE_DRAWABLE); - break; - case DEFAULT_CLOSE_BUTTON_PRESSED_RESOURCE_ID: - registerImageResource(ctx, DEFAULT_CLOSE_BUTTON_PRESSED_RESOURCE_ID, - CLOSE_BUTTON_PRESSED_IMAGE_DRAWABLE); - break; - } - } - - private static void registerImageResource(Context ctx, int resId, - String drawableName) { - Drawable d = buildDrawable(ctx, drawableName); - if (d != null) { - sResources.put(resId, d); - } else { - Log.i("registerImageResource: drawable was null " + drawableName); - Log.i("Context was " + ctx); - } - } - - private static Drawable buildDrawable(Context ctx, String drawableName) { - try { - int resourceId = ctx.getResources().getIdentifier(drawableName, "drawable", - BuildConfig.APPLICATION_ID); - if (resourceId == 0) { - resourceId = ctx.getResources().getIdentifier(drawableName, "drawable", - ctx.getApplicationContext().getPackageName()); - } - - Bitmap b = BitmapFactory.decodeResource(ctx.getResources(), resourceId); - if (b != null) { - - DisplayMetrics m = ctx.getResources().getDisplayMetrics(); - int w = b.getWidth(); - int h = b.getHeight(); - int imageWidth = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, w, m); - int imageHeight = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, h, m); - if ((imageWidth != w) || (imageHeight != h)) { - b = Bitmap.createScaledBitmap(b, imageWidth, imageHeight, - false); - } - return new BitmapDrawable(ctx.getResources(), b); - } else { - throw new FileNotFoundException(); - } - } catch (Exception e) { - Log.i("ResourceManager cannot find resource " + drawableName); - } - return null; - } - - public static boolean isDownloading() { - return sDownloading; - } - - public static void cancel() { - sCancel = true; - sResources.clear(); - } - - public Drawable getResource(Context ctx, int resourceId) { - BitmapDrawable d; - d = (BitmapDrawable) mResources.get(resourceId); - if(d!=null){ - return d; - } - return ResourceManager.getStaticResource(ctx, resourceId); - } - - public static Drawable getStaticResource(Context ctx, int resourceId) { - BitmapDrawable d = (BitmapDrawable) sResources.get(resourceId); - boolean isNull = d == null; - if (isNull || d.getBitmap().isRecycled()) { - - initDefaultResource(ctx, resourceId); - - d = (BitmapDrawable) sResources.get(resourceId); - } - return d; - } - - protected HashMap getsResources() { - return sResources; - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/RichMediaActivity.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/RichMediaActivity.java deleted file mode 100755 index 7f47803a0..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/RichMediaActivity.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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. - * - * Changes: moved from video sub-package, removed video, MRAID and custome event - * code - */ - -package com.playseeds.android.sdk.inappmessaging; - -import java.lang.ref.WeakReference; - -import java.util.TimerTask; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.graphics.Color; - -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.Display; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; - -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.playseeds.android.sdk.inappmessaging.InAppMessageView.BannerAdViewListener; - -public class RichMediaActivity extends Activity { - private ResourceManager mResourceManager; - private FrameLayout mRootLayout; - private ImageView mSkipButton; - private InAppMessageResponse mAd; - private int mType; - private boolean mResult; - - int skipButtonSizeLand = 40; - int skipButtonSizePort = 40; - public static final int TYPE_UNKNOWN = -1; - public static final int TYPE_BROWSER = 0; - public static final int TYPE_INTERSTITIAL = 2; - - DisplayMetrics metrics; - - static class ResourceHandler extends Handler { - WeakReference richMediaActivity; - - public ResourceHandler(RichMediaActivity activity) { - richMediaActivity = new WeakReference<>(activity); - } - - @Override - public void handleMessage(final Message msg) { - RichMediaActivity wRichMediaActivity = richMediaActivity.get(); - if (wRichMediaActivity != null) { - wRichMediaActivity.handleMessage(msg); - } - } - } - - public void handleMessage(final Message msg) { - switch (msg.what) { - case ResourceManager.RESOURCE_LOADED_MSG: - switch (msg.arg1) { - case ResourceManager.DEFAULT_SKIP_IMAGE_RESOURCE_ID: - if (RichMediaActivity.this.mSkipButton != null) { - RichMediaActivity.this.mSkipButton.setImageDrawable(mResourceManager.getResource(this, ResourceManager.DEFAULT_SKIP_IMAGE_RESOURCE_ID)); - } - break; - } - break; - } - } - - OnClickListener mOnInterstitialSkipListener = new OnClickListener() { - @Override - public void onClick(final View v) { - Log.v("###########TRACKING SKIP INTERSTITIAL"); - RichMediaActivity.this.close(); - } - }; - - - @Override - public void finish() { - super.finish(); - - if (this.mAd != null) { - Log.d("Finish Activity type:" + this.mType + " ad Type:" + this.mAd.getType()); - InAppMessageManager.closeRunningInAppMessage(this.mAd); - } - } - - public void goBack() { - switch (this.mType) { - case TYPE_INTERSTITIAL: - this.mResult = true; - this.setResult(Activity.RESULT_OK); - this.finish(); - break; - case TYPE_BROWSER: - this.finish(); - break; - } - } - - private void initInterstitialFromBannerView() { - final FrameLayout layout = new FrameLayout(this); - if (mAd.getType() == Const.TEXT || mAd.getType() == Const.IMAGE) { - final DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics(); - final float scale = displayMetrics.density; - - int adWidth, adHeight; - if (mAd.isHorizontalOrientationRequested()) { - adWidth = 1080; - adHeight = 720; - } else { - adWidth = 720; - adHeight = 1080; - } - - int width = (int)(displayMetrics.widthPixels / displayMetrics.density); - int height = (int)(displayMetrics.heightPixels / displayMetrics.density); - - final float adAspectRatio = adWidth / (float) adHeight; - if (adAspectRatio >= 1.0f) - width = (int)(height * adAspectRatio + 0.5f); - else - height = (int)(width / adAspectRatio + 0.5f); - - InAppMessageView banner = new InAppMessageView(this, mAd, width, height, false, createLocalAdListener()); - - if (mAd != null && mAd.getClickUrl() != null) { - banner.setLayoutParams(new FrameLayout.LayoutParams( - (int) (width * scale + 0.5f), - (int) (height * scale + 0.5f), - Gravity.CENTER)); - } else { - banner.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - Gravity.CENTER)); - } - banner.showContent(); - layout.addView(banner); - } - - if (mAd.getClickUrl() != null) { - this.mSkipButton = new ImageView(this); - this.mSkipButton.setAdjustViewBounds(false); - - int buttonSize; - if (mAd.isHorizontalOrientationRequested()) { - buttonSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.skipButtonSizeLand, this.getResources().getDisplayMetrics()); - } else { - buttonSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.skipButtonSizePort, this.getResources().getDisplayMetrics()); - } - - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(buttonSize, buttonSize, Gravity.TOP | Gravity.RIGHT); - - final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, this.getResources().getDisplayMetrics()); - params.topMargin = margin; - params.rightMargin = margin; - - this.mSkipButton.setImageDrawable(mResourceManager.getResource(this, ResourceManager.DEFAULT_SKIP_IMAGE_RESOURCE_ID)); - - this.mSkipButton.setOnClickListener(this.mOnInterstitialSkipListener); - - this.mSkipButton.setVisibility(View.VISIBLE); - - layout.addView(this.mSkipButton, params); - } - - this.mRootLayout.addView(layout); - } - - private BannerAdViewListener createLocalAdListener() { - return new BannerAdViewListener() { - - @Override - public void onLoad() { - } - - @Override - public void onClick() { - InAppMessageManager.notifyInAppMessageClick(mAd); - } - - @Override - public void onError() { - finish(); - } - }; - } - - private void initRootLayout() { - this.mRootLayout = new FrameLayout(this); - this.mRootLayout.setBackgroundColor(Color.argb(128, 0, 0, 0)); - } - - @Override - public void onConfigurationChanged(final Configuration newConfig) { - super.onConfigurationChanged(newConfig); - Log.d("RichMediaActivity onConfigurationChanged"); - } - - @SuppressWarnings("deprecation") - @Override - public void onCreate(final Bundle icicle) { - super.onCreate(icicle); - - try { - final Window win = this.getWindow(); - final WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE); - final Display display = this.getWindowManager().getDefaultDisplay(); - int mWindowWidth = display.getWidth(); - int mWindowHeight = display.getHeight(); - this.metrics = new DisplayMetrics(); - - this.mResult = false; - this.setResult(Activity.RESULT_CANCELED); - this.requestWindowFeature(Window.FEATURE_NO_TITLE); - win.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); - win.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - wm.getDefaultDisplay().getMetrics(this.metrics); - win.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - - Log.d("RichMediaActivity Window Size:(" + mWindowWidth + "," + mWindowHeight + ")"); -/* - this.setVolumeControlStream(AudioManager.STREAM_MUSIC); -*/ - this.mType = RichMediaActivity.TYPE_UNKNOWN; - final Intent intent = this.getIntent(); - final Bundle extras = intent.getExtras(); - if (extras == null || extras.getSerializable(Const.AD_EXTRA) == null) { - Uri uri = intent.getData(); - Log.d("uri " + uri); - - if (uri == null) { - this.finish(); - return; - } - - this.mType = RichMediaActivity.TYPE_BROWSER; - } else { - this.requestWindowFeature(Window.FEATURE_NO_TITLE); - } - - ResourceHandler mHandler = new ResourceHandler(this); - this.mResourceManager = new ResourceManager(); - this.initRootLayout(); - - this.mAd = (InAppMessageResponse) extras.getSerializable(Const.AD_EXTRA); - - this.mType = extras.getInt(Const.AD_TYPE_EXTRA, -1); - if (this.mType == -1) { - switch (this.mAd.getType()) { - case Const.TEXT: - case Const.IMAGE: - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.GINGERBREAD) { - setOrientationOldApi(); - } else { - setOrientation(); - } - this.mType = TYPE_INTERSTITIAL; - break; - } - } - switch (this.mType) { - case TYPE_INTERSTITIAL: - Log.v("Type interstitial like banner"); - this.initInterstitialFromBannerView(); - break; - } - - this.setContentView(this.mRootLayout); - Log.d("RichMediaActivity onCreate done"); - - } catch (Exception e) { - // in unlikely case something goes terribly wrong - finish(); - } - } - - private void setOrientationOldApi() { - if (mAd.isHorizontalOrientationRequested()) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } else { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - } - - @TargetApi(Build.VERSION_CODES.GINGERBREAD) - private void setOrientation() { - if (mAd.isHorizontalOrientationRequested()) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); - } else { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - if (mResourceManager != null) { - mResourceManager.releaseInstance(); - } - } - - @Override - public boolean onKeyDown(final int keyCode, final KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - this.goBack(); - return true; - } - return super.onKeyDown(keyCode, event); - } - - @Override - protected void onPause() { - super.onPause(); - Log.d("RichMediaActivity onPause"); - } - - @Override - protected void onResume() { - super.onResume(); - - Log.d("RichMediaActivity onResume"); - switch (this.mType) { - case TYPE_BROWSER: - break; - } - } - - public void close() { - mResult = true; - setResult(Activity.RESULT_OK); - finish(); - } - - protected void setTypeInterstitial(int type) { - mType = type; - } -} diff --git a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Util.java b/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Util.java deleted file mode 100755 index 6649dba84..000000000 --- a/seeds-sdk/src/main/java/com/playseeds/android/sdk/inappmessaging/Util.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright 2015 MobFox - * 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.playseeds.android.sdk.inappmessaging; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.net.URL; -import java.util.Enumeration; -import java.util.Locale; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.content.Context; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.location.Location; -import android.location.LocationManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.AsyncTask; -import android.os.Build; -import android.telephony.TelephonyManager; - -import com.google.android.gms.ads.identifier.AdvertisingIdClient; -import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GooglePlayServicesNotAvailableException; -import com.google.android.gms.common.GooglePlayServicesRepairableException; -import com.google.android.gms.common.GooglePlayServicesUtil; - -public class Util { - private static String androidAdId; - private static boolean adDoNotTrack = false; - private static final float MINIMAL_ACCURACY = 1000; - private static final long MINIMAL_TIME_FROM_FIX = 1000 * 60 * 20; - - public static boolean isNetworkAvailable(Context ctx) { - int networkStatePermission = ctx.checkCallingOrSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE); - - if (networkStatePermission == PackageManager.PERMISSION_GRANTED) { - - ConnectivityManager mConnectivity = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); - - // Skip if no connection, or background data disabled - NetworkInfo info = mConnectivity.getActiveNetworkInfo(); - if (info == null) { - return false; - } - // Only update if WiFi - int netType = info.getType(); - // int netSubtype = info.getSubtype(); - if ((netType == ConnectivityManager.TYPE_WIFI) || (netType == ConnectivityManager.TYPE_MOBILE)) { - return info.isConnected(); - } else { - return false; - } - } else { - return true; - } - } - - public static String getConnectionType(Context context) { - int networkStatePermission = context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE); - - if (networkStatePermission == PackageManager.PERMISSION_GRANTED) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo info = cm.getActiveNetworkInfo(); - if (info == null) { - return Const.CONNECTION_TYPE_UNKNOWN; - } - int netType = info.getType(); - int netSubtype = info.getSubtype(); - if (netType == ConnectivityManager.TYPE_WIFI) { - return Const.CONNECTION_TYPE_WIFI; - } else if (netType == 6) { - return Const.CONNECTION_TYPE_WIMAX; - } else if (netType == ConnectivityManager.TYPE_MOBILE) { - switch (netSubtype) { - case TelephonyManager.NETWORK_TYPE_1xRTT: - return Const.CONNECTION_TYPE_MOBILE_1xRTT; - case TelephonyManager.NETWORK_TYPE_CDMA: - return Const.CONNECTION_TYPE_MOBILE_CDMA; - case TelephonyManager.NETWORK_TYPE_EDGE: - return Const.CONNECTION_TYPE_MOBILE_EDGE; - case TelephonyManager.NETWORK_TYPE_EVDO_0: - return Const.CONNECTION_TYPE_MOBILE_EVDO_0; - case TelephonyManager.NETWORK_TYPE_EVDO_A: - return Const.CONNECTION_TYPE_MOBILE_EVDO_A; - case TelephonyManager.NETWORK_TYPE_GPRS: - return Const.CONNECTION_TYPE_MOBILE_GPRS; - case TelephonyManager.NETWORK_TYPE_UMTS: - return Const.CONNECTION_TYPE_MOBILE_UMTS; - case 14: - return Const.CONNECTION_TYPE_MOBILE_EHRPD; - case 12: - return Const.CONNECTION_TYPE_MOBILE_EVDO_B; - case 8: - return Const.CONNECTION_TYPE_MOBILE_HSDPA; - case 10: - return Const.CONNECTION_TYPE_MOBILE_HSPA; - case 15: - return Const.CONNECTION_TYPE_MOBILE_HSPAP; - case 9: - return Const.CONNECTION_TYPE_MOBILE_HSUPA; - case 11: - return Const.CONNECTION_TYPE_MOBILE_IDEN; - case 13: - return Const.CONNECTION_TYPE_MOBILE_LTE; - default: - return Const.CONNECTION_TYPE_MOBILE_UNKNOWN; - } - } else { - return Const.CONNECTION_TYPE_UNKNOWN; - } - } else { - return Const.CONNECTION_TYPE_UNKNOWN; - } - } - - public static String getLocalIpAddress() { - try { - for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { - NetworkInterface intf = en.nextElement(); - for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { - InetAddress inetAddress = enumIpAddr.nextElement(); - if (!inetAddress.isLoopbackAddress()) { - return inetAddress.getHostAddress().toString(); - } - } - } - } catch (SocketException ex) { - Log.e(ex.toString()); - } - return null; - } - - public static Location getLocation(Context context) { - boolean HasFineLocationPermission = false; - boolean HasCoarseLocationPermission = false; - - if (context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - HasFineLocationPermission = true; - HasCoarseLocationPermission = true; - } else if (context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - HasCoarseLocationPermission = true; - } - - LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - - if (locationManager != null && HasFineLocationPermission && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { - Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - - if (location == null) { - return null; - } - - long timeFromFix = Math.abs(System.currentTimeMillis() - location.getTime()); - if (location.hasAccuracy() && location.getAccuracy() < MINIMAL_ACCURACY && timeFromFix < MINIMAL_TIME_FROM_FIX) { - return location; - } - } - if (locationManager != null && HasCoarseLocationPermission && locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { - Location location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - - if (location == null) { - return null; - } - long timeFromFix = Math.abs(System.currentTimeMillis() - location.getTime()); - if (location.hasAccuracy() && location.getAccuracy() < MINIMAL_ACCURACY && timeFromFix < MINIMAL_TIME_FROM_FIX) { - return location; - } - } - return null; - } - - public static String getDefaultUserAgentString() { - String userAgent = System.getProperty("http.agent"); - return userAgent; - } - - @SuppressLint("DefaultLocale") - public static String buildUserAgent() { - String androidVersion = Build.VERSION.RELEASE; - String model = Build.MODEL; - String androidBuild = Build.ID; - final Locale l = Locale.getDefault(); - final String language = l.getLanguage(); - String locale = "en"; - if (language != null) { - locale = language.toLowerCase(); - final String country = l.getCountry(); - if (country != null) { - locale += "-" + country.toLowerCase(); - } - } - - String userAgent = String.format(Const.USER_AGENT_PATTERN, androidVersion, locale, model, androidBuild); - return userAgent; - } - - public static int getMemoryClass(Context context) { - try { - Method getMemoryClassMethod = ActivityManager.class.getMethod("getMemoryClass"); - ActivityManager ac = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - return (Integer) getMemoryClassMethod.invoke(ac, new Object[] {}); - } catch (Exception ex) { - return 16; - } - } - - public static void prepareAndroidAdId(final Context context) { - if (androidAdId == null && GooglePlayServicesUtil.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) { - Log.d("GooglePlayServices connected"); - AsyncTask task = new AsyncTask() { - - @Override - protected Void doInBackground(Void... params) { - Info adInfo = null; - try { - adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context); - Log.d("adInfo " + adInfo); - androidAdId = adInfo.getId(); - Log.d("adId " + androidAdId); - adDoNotTrack = adInfo.isLimitAdTrackingEnabled(); - } catch (IOException e) { - e.printStackTrace(); - } catch (GooglePlayServicesNotAvailableException e) { - e.printStackTrace(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } catch (GooglePlayServicesRepairableException e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - }; - task.execute(); - } - } - - public static String getAndroidAdId() { - if (androidAdId == null) { - return ""; - } - return androidAdId; - } - - public static Bitmap loadBitmap (String url) { - Bitmap bitmap = null; - try { - InputStream in = new URL(url).openStream(); - bitmap = BitmapFactory.decodeStream(in); - } catch (Throwable t) { //to catch also out of memory error when decoding bitmap. - bitmap = null; - Log.e("Decoding bitmap failed!"); - } - - return bitmap; - } -} diff --git a/seeds-sdk/src/main/java/org/openudid/OpenUDID_manager.java b/seeds-sdk/src/main/java/org/openudid/OpenUDID_manager.java deleted file mode 100755 index ed8289e52..000000000 --- a/seeds-sdk/src/main/java/org/openudid/OpenUDID_manager.java +++ /dev/null @@ -1,200 +0,0 @@ -package org.openudid; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.IBinder; -import android.os.RemoteException; -import android.provider.Settings.Secure; -import android.util.Log; - -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.TreeMap; - - -public class OpenUDID_manager implements ServiceConnection{ - public final static String PREF_KEY = "openudid"; - public final static String PREFS_NAME = "openudid_prefs"; - public final static String TAG = "OpenUDID"; - - private final static boolean LOG = true; //Display or not debug message - - private final Context mContext; //Application context - private List mMatchingIntents; //List of available OpenUDID Intents - private Map mReceivedOpenUDIDs; //Map of OpenUDIDs found so far - - private final SharedPreferences mPreferences; //Preferences to store the OpenUDID - private final Random mRandom; - - private OpenUDID_manager(Context context) { - mPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - mContext = context; - mRandom = new Random(); - mReceivedOpenUDIDs = new HashMap(); - } - - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - //Get the OpenUDID from the remote service - try { - //Send a random number to the service - android.os.Parcel data = android.os.Parcel.obtain(); - data.writeInt(mRandom.nextInt()); - android.os.Parcel reply = android.os.Parcel.obtain(); - service.transact(1, android.os.Parcel.obtain(), reply, 0); - if (data.readInt() == reply.readInt()) //Check if the service returns us this number - { - final String _openUDID = reply.readString(); - if (_openUDID != null) { //if valid OpenUDID, save it - if (LOG) Log.d(TAG, "Received " + _openUDID); - - if (mReceivedOpenUDIDs.containsKey(_openUDID)) mReceivedOpenUDIDs.put(_openUDID, mReceivedOpenUDIDs.get(_openUDID) + 1); - else mReceivedOpenUDIDs.put(_openUDID, 1); - - } - } - } catch (RemoteException e) {if (LOG) Log.e(TAG, "RemoteException: " + e.getMessage());} - mContext.unbindService(this); - - startService(); //Try the next one - } - - @Override - public void onServiceDisconnected(ComponentName className) {} - - private void storeOpenUDID() { - final Editor e = mPreferences.edit(); - e.putString(PREF_KEY, OpenUDID); - e.commit(); - } - - /* - * Generate a new OpenUDID - */ - private void generateOpenUDID() { - if (LOG) Log.d(TAG, "Generating openUDID"); - //Try to get the ANDROID_ID - OpenUDID = Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID); - if (OpenUDID == null || OpenUDID.equals("9774d56d682e549c") || OpenUDID.length() < 15 ) { - //if ANDROID_ID is null, or it's equals to the GalaxyTab generic ANDROID_ID or bad, generates a new one - final SecureRandom random = new SecureRandom(); - OpenUDID = new BigInteger(64, random).toString(16); - } - } - - - /* - * Start the oldest service - */ - private void startService() { - if (mMatchingIntents.size() > 0) { //There are some Intents untested - if (LOG) Log.d(TAG, "Trying service " + mMatchingIntents.get(0).loadLabel(mContext.getPackageManager())); - - final ServiceInfo servInfo = mMatchingIntents.get(0).serviceInfo; - final Intent i = new Intent(); - i.setComponent(new ComponentName(servInfo.applicationInfo.packageName, servInfo.name)); - mMatchingIntents.remove(0); - try { // try added by Lionscribe - mContext.bindService(i, this, Context.BIND_AUTO_CREATE); - } - catch (SecurityException e) { - startService(); // ignore this one, and start next one - } - } else { //No more service to ly.count.android.sdk.test - - getMostFrequentOpenUDID(); //Choose the most frequent - - if (OpenUDID == null) //No OpenUDID was chosen, generate one - generateOpenUDID(); - if (LOG) Log.d(TAG, "OpenUDID: " + OpenUDID); - - storeOpenUDID();//Store it locally - mInitialized = true; - } - } - - private void getMostFrequentOpenUDID() { - if (mReceivedOpenUDIDs.isEmpty() == false) { - final TreeMap sorted_OpenUDIDS = new TreeMap(new ValueComparator()); - sorted_OpenUDIDS.putAll(mReceivedOpenUDIDs); - - OpenUDID = sorted_OpenUDIDS.firstKey(); - } - } - - - private static String OpenUDID = null; - private static boolean mInitialized = false; - - /** - * The Method to call to get OpenUDID - * @return the OpenUDID - */ - public static String getOpenUDID() { - if (!mInitialized) Log.e("OpenUDID", "Initialisation isn't done"); - return OpenUDID; - } - - /** - * The Method to call to get OpenUDID - * @return the OpenUDID - */ - public static boolean isInitialized() { - return mInitialized; - } - - /** - * The Method the call at the init of your app - * @param context you current context - */ - public static void sync(Context context) { - //Initialise the Manager - OpenUDID_manager manager = new OpenUDID_manager(context); - - //Try to get the openudid from local preferences - OpenUDID = manager.mPreferences.getString(PREF_KEY, null); - if (OpenUDID == null) //Not found - { - //Get the list of all OpenUDID services available (including itself) - manager.mMatchingIntents = context.getPackageManager().queryIntentServices(new Intent("org.OpenUDID.GETUDID"), 0); - if (LOG) Log.d(TAG, manager.mMatchingIntents.size() + " services matches OpenUDID"); - - if (manager.mMatchingIntents != null) - //Start services one by one - manager.startService(); - - } else {//Got it, you can now call getOpenUDID() - if (LOG) Log.d(TAG, "OpenUDID: " + OpenUDID); - mInitialized = true; - } - } - - - - /* - * Used to sort the OpenUDIDs collected by occurrence - */ - private class ValueComparator implements Comparator { - public int compare(Object a, Object b) { - - if(mReceivedOpenUDIDs.get(a) < mReceivedOpenUDIDs.get(b)) { - return 1; - } else if(mReceivedOpenUDIDs.get(a) == mReceivedOpenUDIDs.get(b)) { - return 0; - } else { - return -1; - } - } - } -} diff --git a/seeds-sdk/src/main/java/org/openudid/OpenUDID_service.java b/seeds-sdk/src/main/java/org/openudid/OpenUDID_service.java deleted file mode 100755 index 53c770f31..000000000 --- a/seeds-sdk/src/main/java/org/openudid/OpenUDID_service.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.openudid; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Binder; -import android.os.IBinder; - - -/* - * You have to add this in your manifest - - - - - - - -*/ - - -public class OpenUDID_service extends Service{ - @Override - public IBinder onBind(Intent arg0) { - return new Binder() { - @Override - public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) { - final SharedPreferences preferences = getSharedPreferences(OpenUDID_manager.PREFS_NAME, Context.MODE_PRIVATE); - - reply.writeInt(data.readInt()); //Return to the sender the input random number - reply.writeString(preferences.getString(OpenUDID_manager.PREF_KEY, null)); - return true; - } - }; - } -} diff --git a/seeds-sdk/src/test/AndroidManifest.xml b/seeds-sdk/src/test/AndroidManifest.xml deleted file mode 100644 index 74e6f0d87..000000000 --- a/seeds-sdk/src/test/AndroidManifest.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 60df51733..aa64f9dd7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ include ':shared' -include 'Habitica', ':Habitica', ':seeds-sdk' \ No newline at end of file +include 'Habitica', ':Habitica' \ No newline at end of file