Skip to main content

Overview

Android TV and Google TV applications use the Permutive Android SDK. The same SDK that powers mobile Android applications fully supports Android TV, providing identical APIs for tracking, identity management, and ad targeting.
Same SDK, Same APIs: The Permutive Android SDK works seamlessly on Android TV. All features documented in the Android SDK are available on Android TV.

Requirements

RequirementVersion
Android API21+ (Android 5.0 Lollipop)
Compile SDK34+
Java8+ (JVM target 1.8)
Kotlin1.6+ (if using Kotlin)

Installation

Add the Permutive SDK to your build.gradle:
dependencies {
    // Core SDK (required)
    implementation("com.permutive.android:core:1.10.0")

    // Google Ads integration (optional)
    implementation("com.permutive.android:google-ads:2.2.0")
}

Initialization

Initialize the SDK in your Application class:
import android.app.Application
import com.permutive.android.Permutive

class MyTVApplication : Application() {

    lateinit var permutive: Permutive
        private set

    override fun onCreate() {
        super.onCreate()

        permutive = Permutive.Builder(
            applicationContext = this,
            apiKey = "<your api key>",
            workspaceId = "<your workspace id>"
        ).build()
    }
}
For detailed initialization options, see Android Initialization.

Video Tracking

Video tracking on Android TV uses the MediaTracker API. This is the primary use case for CTV applications.

Creating a MediaTracker

import com.permutive.android.MediaTracker

class VideoActivity : AppCompatActivity() {

    private val permutive by lazy {
        (application as MyTVApplication).permutive
    }

    private lateinit var videoTracker: MediaTracker

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        videoTracker = permutive.trackVideoView(
            durationMilliseconds = 3600000, // 1 hour
            videoProperties = MediaTracker.VideoProperties(
                title = "Show Title - S1E1",
                genre = listOf("Drama", "Thriller"),
                contentType = listOf("Series", "Episode"),
                seasonNumber = 1,
                episodeNumber = 1,
                runtime = 3600
            ),
            pageProperties = MediaTracker.PageProperties(
                title = "Now Playing",
                url = Uri.parse("https://example.com/shows/title/s1e1")
            )
        )
    }

    override fun onDestroy() {
        super.onDestroy()
        videoTracker.stop() // Important: Always stop to send VideoComplete
    }
}

Integrating with ExoPlayer

Android TV applications typically use ExoPlayer (now part of Media3) for video playback:
import com.permutive.android.MediaTracker
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.analytics.AnalyticsListener
import androidx.media3.common.Player

class VideoPlayerActivity : AppCompatActivity() {

    private val permutive by lazy {
        (application as MyTVApplication).permutive
    }

    private lateinit var videoTracker: MediaTracker
    private lateinit var exoPlayer: ExoPlayer

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_video_player)

        setupVideoTracker()
        setupExoPlayer()
    }

    private fun setupVideoTracker() {
        videoTracker = permutive.trackVideoView(
            durationMilliseconds = videoDurationMs,
            videoProperties = MediaTracker.VideoProperties(
                title = "Video Title",
                genre = listOf("Documentary"),
                contentType = listOf("Feature")
            )
        )
    }

    private fun setupExoPlayer() {
        exoPlayer = ExoPlayer.Builder(this).build()

        exoPlayer.addListener(object : Player.Listener {
            override fun onPlaybackStateChanged(state: Int) {
                when (state) {
                    Player.STATE_READY -> {
                        if (exoPlayer.isPlaying) {
                            videoTracker.play()
                        }
                    }
                    Player.STATE_BUFFERING -> {
                        videoTracker.pause()
                    }
                }
            }

            override fun onIsPlayingChanged(isPlaying: Boolean) {
                if (isPlaying) {
                    videoTracker.play()
                } else {
                    videoTracker.pause()
                }
            }
        })

        // Track seeking
        exoPlayer.addAnalyticsListener(object : AnalyticsListener {
            override fun onPositionDiscontinuity(
                eventTime: AnalyticsListener.EventTime,
                reason: Int
            ) {
                if (reason == Player.DISCONTINUITY_REASON_SEEK) {
                    videoTracker.play(exoPlayer.currentPosition)
                }
            }
        })
    }

    override fun onPause() {
        super.onPause()
        videoTracker.pause()
        exoPlayer.pause()
    }

    override fun onResume() {
        super.onResume()
        if (exoPlayer.isPlaying) {
            videoTracker.play()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        videoTracker.stop()
        exoPlayer.release()
    }
}

MediaTracker Lifecycle

MethodDescriptionEvents Tracked
play()Start/resume trackingVideoview (first call)
play(positionMs)Start at specific position (seeking)-
pause()Pause tracking (buffering, paused)-
stop()End tracking permanentlyVideoComplete
Always call stop(): Failing to call stop() when the video ends or the user exits will prevent VideoComplete events from being tracked.
For complete MediaTracker documentation, see Android Video Tracking.

Video Properties

The SDK provides standard video properties for consistent tracking:
MediaTracker.VideoProperties(
    title = "Video Title",
    genre = listOf("Action", "Thriller"),
    contentType = listOf("Movie", "Feature"),
    ageRating = "PG-13",
    runtime = 7200, // Runtime in seconds
    country = "US",
    originalLanguage = "en",
    audioLanguage = "en",
    areSubtitlesEnabled = true,
    subtitlesLanguage = "en",
    seasonNumber = 1,
    episodeNumber = 5,
    consecutiveEpisodes = 3,
    iabCategories = listOf("IAB1", "IAB2")
)

Video Ad Tracking

Track video advertisements within your content:
// When ad starts
val adTracker = videoTracker.trackAdView(
    durationMs = 30000, // 30 second ad
    adProperties = AdTracker.AdProperties(
        title = "Product Ad",
        durationInSeconds = 30,
        campaignId = "campaign_123",
        creativeId = "creative_456"
    )
)

adTracker.play()

// When ad completes
adTracker.completion()
See Android Video Ad Tracking for details.

Identity Management

Set user identities for cross-device tracking:
// userId: Your application's user identifier
// hashedEmail: SHA256 hash of the user's email address
permutive.setIdentity(
    listOf(
        Alias.create("user_id", userId, priority = 0),
        Alias.create("email_sha256", hashedEmail, priority = 1)
    )
)
See Android Identity Management for details.

Cohorts and Targeting

Accessing Cohorts

// Get all cohorts
val cohorts = permutive.cohorts

// Get activations for a specific ad platform
val gamActivations = permutive.activations("gam")
import com.permutive.android.googleads.addPermutiveTargeting

val adRequest = AdManagerAdRequest.Builder()
    .addPermutiveTargeting(permutive)
    .build()

adView.loadAd(adRequest)
See Android Google Ad Manager for complete integration details.

Android TV-Specific Considerations

Android TV uses the Leanback library for its 10-foot UI. Ensure your tracking implementation doesn’t interfere with Leanback components:
class VideoFragment : VideoSupportFragment() {

    private lateinit var videoTracker: MediaTracker

    override fun onStart() {
        super.onStart()
        // VideoSupportFragment handles player lifecycle
        // Just sync tracking with player state
    }
}
Android TV uses D-pad navigation. Tracking works independently of navigation, but ensure you handle the back button appropriately:
override fun onBackPressed() {
    // Stop tracking before navigating away
    videoTracker.stop()
    super.onBackPressed()
}
If your app supports PiP mode, continue tracking during PiP:
override fun onPictureInPictureModeChanged(
    isInPictureInPictureMode: Boolean,
    newConfig: Configuration
) {
    super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
    // Continue tracking - no changes needed
    // MediaTracker tracks independently of UI mode
}
Android TV supports AAID. Use the AAID provider for automatic advertising ID tracking:
dependencies {
    implementation("com.permutive.android:aaid-provider:1.10.0")
}
val permutive = Permutive.Builder(this, apiKey, workspaceId)
    .enableAaidProvider()
    .build()
See AAID Provider for details.
Android TV apps may continue video playback when the home button is pressed. Handle this appropriately:
override fun onStop() {
    super.onStop()
    if (!isChangingConfigurations) {
        // User left the app
        if (exoPlayer.isPlaying) {
            // Playback continues in background
            // Keep tracking active
        } else {
            videoTracker.pause()
        }
    }
}

Best Practices

  • Initialize SDK early in Application.onCreate()
  • Create MediaTracker when video is ready to play
  • Call stop() when video ends or user exits
  • Sync play()/pause() with actual player state
  • Handle buffering states with pause()
  • Include video metadata for richer cohorts
  • Use Leanback-compatible patterns

Troubleshooting

Problem: Permutive instance is null or initialization fails.Solutions:
  • Verify API key and workspace ID are correct
  • Ensure initialization happens in Application.onCreate()
  • Check for initialization errors in Logcat
  • Enable debug mode: permutive.setDeveloperMode(true)
Problem: Video events don’t show in Permutive dashboard.Solutions:
  • Verify MediaTracker was created successfully
  • Ensure play() is called when video starts
  • Ensure stop() is called when video ends
  • Wait 5-10 minutes for events to process
  • Check Logcat for “Accepted: 1 / 1” messages
Problem: Engagement metrics don’t match expected values.Solutions:
  • Sync play()/pause() calls with actual player state
  • Call pause() during buffering
  • Provide accurate duration when creating MediaTracker
Problem: VideoComplete events not appearing.Solutions:
  • Ensure stop() is called in onDestroy()
  • Handle back button to call stop() before navigation
  • Verify app isn’t killed before event sends