Remove Seeds SDK

This commit is contained in:
Phillip Thelen 2019-09-09 12:05:32 +02:00
parent b67d6c3edf
commit bdf07b29f6
60 changed files with 3 additions and 7527 deletions

View file

@ -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'

View file

@ -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(...);

View file

@ -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" />
</LinearLayout>
</LinearLayout>
<TextView

View file

@ -39,15 +39,4 @@
android:background="@drawable/purchase_button_background"
android:textColor="@color/white"
tools:text="$ 4.99"/>
<ImageButton
android:id="@+id/seedsImageButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:src="@drawable/seeds_badge_store"
android:scaleType="fitCenter"
android:layout_marginTop="4dp"
android:visibility="gone"
/>
</LinearLayout>

View file

@ -31,7 +31,6 @@
<attr name="gemAmount" format="integer" />
<attr name="priceText" format="string" />
<attr name="gemDrawable" format="integer" />
<attr name="showSeedsPromo" format="boolean" />
</declare-styleable>
<declare-styleable name="MaxHeightLinearLayout">

View file

@ -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;

View file

@ -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) {

View file

@ -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<Toolbar>(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

View file

@ -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() {

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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"
}

Binary file not shown.

View file

@ -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

160
seeds-sdk/gradlew vendored
View file

@ -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 "$@"

90
seeds-sdk/gradlew.bat vendored
View file

@ -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

View file

@ -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 *;
#}

View file

@ -1,30 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.playseeds.android.sdk">
<!-- Permissions collected from Countly plugin and MobFox plugin -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<application
android:name="androidx.multidex.MultiDexApplication">
<!-- MobFox activities declaration -->
<activity android:name="com.playseeds.android.sdk.inappmessaging.RichMediaActivity"
android:theme="@android:style/Theme.Translucent"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
android:hardwareAccelerated="false" />
<activity android:name="com.playseeds.android.sdk.inappmessaging.InAppWebView"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" />
<!-- Required to use Google Play Services, but is already provided by GPS base AAR -->
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<!--<uses-library android:name="android.test.runner"/>-->
</application>
<!--<instrumentation android:name="com.playseeds.android.sdk.test.InstrumentationTestRunner"-->
<!--android:targetPackage="ly.count.android.sdk"/>-->
</manifest>

View file

@ -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;
}
}

View file

@ -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<byte[]> keys;
public CertificateTrustManager(List<String> 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;
}
}

View file

@ -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 &amp; 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_; }
}

View file

@ -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_
+ "&timestamp=" + 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_
+ "&timestamp=" + 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_
+ "&timestamp=" + 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_
+ "&timestamp=" + 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_
+ "&timestamp=" + 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_
+ "&timestamp=" + 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_
+ "&timestamp=" + 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_
+ "&timestamp=" + 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; }
}

View file

@ -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 &amp; 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<Event> eventsList() {
final String[] array = events();
final List<Event> 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<Event>() {
@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<String> 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<String> 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<Event> 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<String, String> 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<Event> eventsToRemove) {
if (eventsToRemove != null && eventsToRemove.size() > 0) {
final List<Event> 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<Event> collection, final String delimiter) {
final List<String> 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<String> 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();
}
}
}

View file

@ -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<String> logs = new ArrayList<>();
private static int startTime = Seeds.currentTimestamp();
private static Map<String,String> 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<String,String> 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
}
}
}

View file

@ -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;
}
}
}

View file

@ -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
}
}
}

View file

@ -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<String, String> 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<String, String> 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);
}
}

View file

@ -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<Event> 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<String, String> 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_;
}
}

View file

@ -1,5 +0,0 @@
package com.playseeds.android.sdk;
public interface IInAppMessageShowCountListener {
void onInAppMessageShowCount(String errorMessage, int showCount, String message_id);
}

View file

@ -1,5 +0,0 @@
package com.playseeds.android.sdk;
public interface IInAppPurchaseCountListener {
void onInAppPurchaseCount(String errorMessage, int purchasesCount, String key);
}

View file

@ -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);
}

View file

@ -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
}
}

View file

@ -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<? extends Activity> 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;
}
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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<String, String> 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<String, String> 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<String, String> 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;
}
}

View file

@ -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;
}
}

View file

@ -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 = "<body style='\"'margin: 0px; padding: 0px; text-align:center;'\"'><img src='\"'{0}'\"' width='\"'{1}'dp\"' height='\"'{2}'dp\"'/></body>";
// public static final String IMAGE_BODY = "<body style='\"'margin: 0px; padding: 0px; text-align:center;'\"'><div style=\"background-image:{0} width='\"'{1}'dp\"' height='\"'{2}'dp\"'></div></body>";
public static final String REDIRECT_URI = "REDIRECT_URI";
public static final String HIDE_BORDER = "<style>* { -webkit-tap-highlight-color: rgba(0,0,0,0);} img {width:100%;height:100%} body {margin: 0; padding: 0}</style>";
public static final String INTERSTITIAL_HIDE_BORDER = "<style>* { -webkit-tap-highlight-color: rgba(0,0,0,0);} body {height:100%; width:100%;} img {max-width:100%; max-height:100%; width:auto; height:auto; position: absolute; margin: auto; top: 0; left: 0; right: 0; bottom: 0;}</style>";
// public static final String HIDE_BORDER = "<style>* { -webkit-tap-highlight-color: rgba(0,0,0,0) }</style>";
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;
}

View file

@ -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;
}
}

View file

@ -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<InAppMessageResponse> {
public GeneralInAppMessageProvider() {
}
public InAppMessageResponse parseCountlyJSON(final InputStream inputStream, Map<String, List<String>> 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;
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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<String, Thread> mRequestThreads;
private InAppMessageListener mListener;
private HashMap<String, InAppMessageResponse> mResponses;
private String interstitialRequestURL;
private String mDeviceID;
private DeviceId.Type mIdMode;
private boolean requestedHorizontalAd;
private Gender userGender;
private int userAge;
private List<String> keywords;
private boolean doNotShow = false;
private HashMap<String, InAppMessageRequest> mRequests = null;
private static HashMap<Long, InAppMessageManager> 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<String> productsList = new ArrayList<String>();
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<String> 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<String, String> 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<Long, InAppMessageManager> 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<String> keywords) {
this.keywords = keywords;
}
public void recordInterstitialEvent(String key, InAppMessageResponse ad) {
recordInterstitialEvent(key, ad, null);
}
public void recordInterstitialEvent(String key, InAppMessageResponse ad, HashMap<String,String> customSegments) {
HashMap<String, String> 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;
}
}

View file

@ -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<T> {
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<String, List<String>> headers) throws RequestException;
}

View file

@ -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<String> 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<String> 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<String> getKeywords() {
return this.keywords;
}
}

View file

@ -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;
}
}

View file

@ -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<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@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("<html>")) {
text = "<html><head></head><body style='margin:0;padding:0;'>" + Const.HIDE_BORDER + text + "</body></html>";
}
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("<html>")) {
text = "<html><head></head><body style='margin:0;padding:0;'>" + Const.HIDE_BORDER + text + "</body></html>";
}
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;
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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<Integer, Drawable> sResources = new HashMap<>();
private HashMap<Integer, Drawable> mResources = new HashMap<>();
public ResourceManager() {
}
public void releaseInstance(){
Iterator<Entry<Integer, Drawable>> it = mResources.entrySet().iterator();
while(it.hasNext()) {
Entry<Integer, Drawable> pairsEntry = (Entry<Integer, Drawable>)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<Integer, Drawable> getsResources() {
return sResources;
}
}

View file

@ -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> 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;
}
}

View file

@ -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<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> 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<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@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;
}
}

View file

@ -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<ResolveInfo> mMatchingIntents; //List of available OpenUDID Intents
private Map<String, Integer> 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<String, Integer>();
}
@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<String,Integer> 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;
}
}
}
}

View file

@ -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
<service android:name="org.OpenUDID.OpenUDID_service">
<intent-filter>
<action android:name="org.OpenUDID.GETUDID" />
</intent-filter>
</service>
*/
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;
}
};
}
}

View file

@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.playseeds.android.sdk"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="12"
android:targetSdkVersion="22" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<application>
<!-- Include the AdActivity and InAppPurchaseActivity configChanges and themes. -->
<activity
android:name="com.google.android.gms.ads.AdActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
android:theme="@android:style/Theme.Translucent" />
<activity
android:name="com.google.android.gms.ads.purchase.InAppPurchaseActivity"
android:theme="@style/Theme.IAPTheme" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.gms.wallet.api.enabled"
android:value="true" />
<receiver
android:name="com.google.android.gms.wallet.EnableWalletOptimizationReceiver"
android:exported="false" >
<intent-filter>
<action android:name="com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION" />
</intent-filter>
</receiver>
<!-- MobFox activities declaration -->
<activity
android:name="com.playseeds.android.sdk.inappmessaging.RichMediaActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
android:hardwareAccelerated="false" />
<activity
android:name="com.playseeds.android.sdk.inappmessaging.InAppWebView"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" />
</application>
</manifest>

View file

@ -1,2 +1,2 @@
include ':shared'
include 'Habitica', ':Habitica', ':seeds-sdk'
include 'Habitica', ':Habitica'