diff --git a/CHANGELOG b/CHANGELOG index dbd5d4e9a..581fa69ef 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +Version 5.3.0 (2023-06-30) +-------------------------- +Add media controller with APIs to track media events (#606) +Add emitter configuration support to remote configuration (#607) +Use default configuration for properties that are not configured using remote configuration (#613) +Add custom HTTP headers configuration (#276) +Truncate language in platform context entity to max 8 characters (#621) +Truncate URL scheme for page_url and page_refr properties (#616) +Remember requestCallback, customRetryForStatusCodes and onSessionUpdate set for initialized trackers after configuration updates + Version 5.2.0 (2023-06-02) -------------------------- Track install referrer details entity along with the application install event if available (#249) diff --git a/VERSION b/VERSION index 91ff57278..03f488b07 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.2.0 +5.3.0 diff --git a/build.gradle b/build.gradle index 52de0308b..ab888bd28 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ plugins { subprojects { group = 'com.snowplowanalytics' - version = '5.2.0' + version = '5.3.0' repositories { google() maven { diff --git a/gradle.properties b/gradle.properties index d63651f6b..99c126554 100644 --- a/gradle.properties +++ b/gradle.properties @@ -31,7 +31,7 @@ systemProp.org.gradle.internal.http.socketTimeout=120000 SONATYPE_STAGING_PROFILE=comsnowplowanalytics GROUP=com.snowplowanalytics POM_ARTIFACT_ID=snowplow-android-tracker -VERSION_NAME=5.2.0 +VERSION_NAME=5.3.0 POM_NAME=snowplow-android-tracker POM_PACKAGING=aar diff --git a/snowplow-demo-java/src/main/java/com/snowplowanalytics/snowplowtrackerdemojava/Demo.java b/snowplow-demo-java/src/main/java/com/snowplowanalytics/snowplowtrackerdemojava/Demo.java index 71aa47e99..31919b177 100644 --- a/snowplow-demo-java/src/main/java/com/snowplowanalytics/snowplowtrackerdemojava/Demo.java +++ b/snowplow-demo-java/src/main/java/com/snowplowanalytics/snowplowtrackerdemojava/Demo.java @@ -56,7 +56,6 @@ import com.snowplowanalytics.snowplow.emitter.BufferOption; import com.snowplowanalytics.snowplow.globalcontexts.GlobalContext; import com.snowplowanalytics.snowplow.tracker.DevicePlatform; -import com.snowplowanalytics.snowplow.tracker.InspectableEvent; import com.snowplowanalytics.snowplow.tracker.LoggerDelegate; import com.snowplowanalytics.snowplow.network.HttpMethod; import com.snowplowanalytics.snowplow.network.RequestCallback; diff --git a/snowplow-demo-kotlin/src/main/AndroidManifest.xml b/snowplow-demo-kotlin/src/main/AndroidManifest.xml index d8744ade7..63d5ec095 100644 --- a/snowplow-demo-kotlin/src/main/AndroidManifest.xml +++ b/snowplow-demo-kotlin/src/main/AndroidManifest.xml @@ -1,37 +1,38 @@ + package="com.snowplowanalytics.snowplowdemokotlin"> - - - + + android:usesCleartextTraffic="true"> + + android:exported="true" + android:screenOrientation="fullSensor"> + - + android:screenOrientation="fullSensor"> diff --git a/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/Demo.kt b/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/Demo.kt index 875e809b8..4cc7c86ca 100644 --- a/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/Demo.kt +++ b/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/Demo.kt @@ -15,6 +15,7 @@ package com.snowplowanalytics.snowplowdemokotlin import android.Manifest import android.app.Activity import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle @@ -29,6 +30,7 @@ import androidx.core.content.ContextCompat import androidx.core.util.Consumer import androidx.core.util.Pair import androidx.preference.PreferenceManager +import com.snowplowanalytics.core.tracker.Logger import com.snowplowanalytics.core.utils.Util import com.snowplowanalytics.snowplow.Snowplow.createTracker import com.snowplowanalytics.snowplow.Snowplow.defaultTracker @@ -56,6 +58,7 @@ class Demo : Activity(), LoggerDelegate { private var _startButton: Button? = null private var _tabButton: Button? = null private var _loadWebViewButton: Button? = null + private var _videoBtn: Button? = null private var _uriField: EditText? = null private var _webViewUriField: EditText? = null private var _type: RadioGroup? = null @@ -97,6 +100,13 @@ class Demo : Activity(), LoggerDelegate { _webViewUriField = findViewById(R.id.web_view_uri_field) as EditText _webView = findViewById(R.id.web_view) as WebView _loadWebViewButton = findViewById(R.id.btn_load_webview) as Button + _videoBtn = findViewById(R.id.btn_lite_video) as Button + _videoBtn?.setOnClickListener { + Logger.updateLogLevel(LogLevel.VERBOSE) + val intent = Intent(this@Demo, MediaActivity::class.java) + startActivity(intent) + } + _logOutput?.movementMethod = ScrollingMovementMethod() _logOutput?.text = "" @@ -293,7 +303,7 @@ class Demo : Activity(), LoggerDelegate { plugin.afterTrack { event: InspectableEvent -> println("Tracked event with ${event.entities.size} entities") } - + createTracker( applicationContext, namespace, diff --git a/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/MediaActivity.kt b/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/MediaActivity.kt new file mode 100644 index 000000000..7193dcd89 --- /dev/null +++ b/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/MediaActivity.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015-2023 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplowdemokotlin + +import android.app.Activity +import android.net.Uri +import android.os.Bundle +import com.snowplowanalytics.snowplowdemokotlin.media.VideoViewController + +class MediaActivity : Activity() { + private var videoViewController: VideoViewController? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_media) + + val uri = Uri.parse("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4") + videoViewController = VideoViewController(activity = this, uri = uri) + } + + override fun onDestroy() { + videoViewController?.destroy() + videoViewController = null + super.onDestroy() + } +} diff --git a/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/media/VideoView.kt b/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/media/VideoView.kt new file mode 100644 index 000000000..cf46490d3 --- /dev/null +++ b/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/media/VideoView.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015-2023 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplowdemokotlin.media + +import android.content.Context +import android.util.AttributeSet + +class VideoView : android.widget.VideoView { + private var viewController: VideoViewController? = null + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super( + context, + attrs, + defStyle + ) + + fun setVideoPlayer(player: VideoViewController?) { + viewController = player + } + + override fun start() { + super.start() + viewController?.onPlay() + } + + override fun pause() { + super.pause() + viewController?.onPause() + } + + override fun seekTo(msec: Int) { + super.seekTo(msec) + viewController?.onSeekStart() + } +} + diff --git a/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/media/VideoViewController.kt b/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/media/VideoViewController.kt new file mode 100644 index 000000000..843645f8c --- /dev/null +++ b/snowplow-demo-kotlin/src/main/java/com/snowplowanalytics/snowplowdemokotlin/media/VideoViewController.kt @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2015-2023 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplowdemokotlin.media + +import android.app.Activity +import android.content.Context +import android.media.AudioManager +import android.media.MediaPlayer +import android.media.MediaPlayer.* +import android.net.Uri +import android.os.Handler +import android.os.HandlerThread +import android.util.Log +import android.view.View +import android.widget.MediaController +import com.snowplowanalytics.snowplow.Snowplow +import com.snowplowanalytics.snowplow.event.Event +import com.snowplowanalytics.snowplow.media.controller.MediaTracking +import com.snowplowanalytics.snowplow.media.entity.MediaPlayerEntity +import com.snowplowanalytics.snowplow.media.event.* +import com.snowplowanalytics.snowplowdemokotlin.R +import java.util.* + +class VideoViewController(activity: Activity, uri: Uri) { + private val videoView: VideoView = activity.findViewById(R.id.videoView) as VideoView + private val mediaController: MediaController = MediaController(activity) + private var loaded = false + private var seeking = false + private val audio: AudioManager = activity.getSystemService(Context.AUDIO_SERVICE) as AudioManager + private var updateThread: UpdateThread? = null + private var mediaTracking: MediaTracking? = null + + /** + * Converts the volume to percentage. + */ + private val volume: Int + get() { + val volumeLevel = audio.getStreamVolume(AudioManager.STREAM_MUSIC) + val maxVolumeLevel = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + return (volumeLevel.toFloat() / maxVolumeLevel * 100).toInt() + } + + /** + * Constructs the player entity using information from the videoView. + */ + private val player: MediaPlayerEntity + get() = MediaPlayerEntity( + currentTime = videoView.currentPosition.toDouble() / 1000, + duration = videoView.duration.toDouble() / 1000, + paused = !videoView.isPlaying || seeking, + volume = volume + ) + + init { + // initialize video view + videoView.setVideoPlayer(this) + mediaController.setMediaPlayer(videoView) + videoView.setMediaController(mediaController) + videoView.requestFocus() + + // subscribe listeners + videoView.setOnPreparedListener { onPrepared(it) } + videoView.setOnInfoListener { _, what, _ -> onInfo(what); true } + videoView.setOnCompletionListener { onComplete(it) } + + videoView.setVideoURI(uri) + } + + fun onPlay() { + load() + track(MediaPlayEvent()) + } + + fun onPause() { + track(MediaPauseEvent()) + } + + fun onSeekStart() { + load() + seeking = true + track(MediaSeekStartEvent()) + } + + private fun onSeekEnd() { + seeking = false + track(MediaSeekEndEvent()) + } + + private fun onPrepared(mediaPlayer: MediaPlayer) { + mediaController.show(0) + mediaPlayer.setOnSeekCompleteListener { onSeekEnd() } + } + + private fun onInfo(what: Int) { + when (what) { + MEDIA_INFO_BUFFERING_START -> track(MediaBufferStartEvent()) + MEDIA_INFO_BUFFERING_END -> track(MediaBufferEndEvent()) + } + } + + private fun onComplete(player: MediaPlayer) { + mediaController.show(0) + + if (loaded) { + track(MediaEndEvent()) + mediaTracking?.id?.let { Snowplow.defaultTracker?.media?.endMediaTracking(it) } + updateThread?.invalidate() + reset() + } + } + + fun destroy() { + updateThread?.quit() + updateThread = null + } + + private fun load() { + if (!loaded) { + reset() + loaded = true + + mediaTracking = Snowplow.defaultTracker?.media?.startMediaTracking( + id = UUID.randomUUID().toString(), + player = player + ) + + updateThread = UpdateThread() + + track(MediaReadyEvent()) + } + } + + private fun reset() { + loaded = false + seeking = false + updateThread?.quit() + updateThread = null + mediaTracking = null + } + + private fun track(event: Event) { + Log.v(TAG, "Tracking media event: $event") + mediaTracking?.track(event, player = player) + } + + private inner class UpdateThread : HandlerThread("UpdatePlayerThread") { + private var shouldStop = false + + init { + start() + val handler = Handler(looper) + handler.post(object : Runnable { + override fun run() { + if (shouldStop) { return } + mediaTracking?.update(player = player) + handler.postDelayed(this, 1000L) + } + }) + } + + fun invalidate() { + shouldStop = true + } + } + + companion object { + private val TAG = VideoViewController::class.java.simpleName + } +} + diff --git a/snowplow-demo-kotlin/src/main/res/layout/activity_demo.xml b/snowplow-demo-kotlin/src/main/res/layout/activity_demo.xml index b2368dafd..f551651dc 100644 --- a/snowplow-demo-kotlin/src/main/res/layout/activity_demo.xml +++ b/snowplow-demo-kotlin/src/main/res/layout/activity_demo.xml @@ -383,6 +383,16 @@ android:text="@string/tab" android:textSize="16sp" android:textStyle="bold" /> + +