main

square/leakcanary

Last updated at: 29/12/2023 09:38

VisibilityTracker.kt

TLDR

The VisibilityTracker.kt file contains an internal class VisibilityTracker and an extension function registerVisibilityListener.

The VisibilityTracker class is responsible for tracking the visibility state of activities in an Android application. It implements the Application.ActivityLifecycleCallbacks interface and the BroadcastReceiver class. It keeps track of the number of started activities and determines if there are any visible activities. It also listens for screen on/off events and updates the visibility state accordingly. The registerVisibilityListener function registers the VisibilityTracker instance as an activity lifecycle callback and a broadcast receiver for screen on/off events.

Classes

VisibilityTracker

The VisibilityTracker class is an internal class that implements the Application.ActivityLifecycleCallbacks interface and the BroadcastReceiver class. Its main purpose is to track the visibility state of activities in an Android application. It keeps track of the number of started activities and determines if there are any visible activities. It also listens for screen on/off events and updates the visibility state accordingly.

END

package leakcanary.internal

import android.app.Activity
import android.app.Application
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_SCREEN_OFF
import android.content.Intent.ACTION_SCREEN_ON
import android.content.IntentFilter
import leakcanary.internal.friendly.noOpDelegate

internal class VisibilityTracker(
  private val listener: (Boolean) -> Unit
) : Application.ActivityLifecycleCallbacks by noOpDelegate(), BroadcastReceiver() {

  private var startedActivityCount = 0

  /**
   * Visible activities are any activity started but not stopped yet. An activity can be paused
   * yet visible: this will happen when another activity shows on top with a transparent background
   * and the activity behind won't get touch inputs but still need to render / animate.
   */
  private var hasVisibleActivities: Boolean = false

  /**
   * Assuming screen on by default.
   */
  private var screenOn: Boolean = true

  private var lastUpdate: Boolean = false

  override fun onActivityStarted(activity: Activity) {
    startedActivityCount++
    if (!hasVisibleActivities && startedActivityCount == 1) {
      hasVisibleActivities = true
      updateVisible()
    }
  }

  override fun onActivityStopped(activity: Activity) {
    // This could happen if the callbacks were registered after some activities were already
    // started. In that case we effectively considers those past activities as not visible.
    if (startedActivityCount > 0) {
      startedActivityCount--
    }
    if (hasVisibleActivities && startedActivityCount == 0 && !activity.isChangingConfigurations) {
      hasVisibleActivities = false
      updateVisible()
    }
  }

  override fun onReceive(
    context: Context,
    intent: Intent
  ) {
    screenOn = intent.action != ACTION_SCREEN_OFF
    updateVisible()
  }

  private fun updateVisible() {
    val visible = screenOn && hasVisibleActivities
    if (visible != lastUpdate) {
      lastUpdate = visible
      listener.invoke(visible)
    }
  }
}

internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) {
  val visibilityTracker = VisibilityTracker(listener)
  registerActivityLifecycleCallbacks(visibilityTracker)
  registerReceiver(visibilityTracker, IntentFilter().apply {
    addAction(ACTION_SCREEN_ON)
    addAction(ACTION_SCREEN_OFF)
  })
}