@@ -13,7 +13,6 @@ import android.os.Handler
1313import android.os.Looper
1414import android.os.Process
1515import android.os.SystemClock
16- import android.util.Log
1716import androidx.annotation.VisibleForTesting
1817import com.datadog.android.internal.system.BuildSdkVersionProvider
1918import com.datadog.android.rum.startup.RumFirstDrawTimeReporter
@@ -46,6 +45,7 @@ import kotlin.time.Duration.Companion.seconds
4645 * [java.lang.ref.WeakReference] to an [android.app.Activity]. It is single-use per process: there
4746 * is no automatic reset for warm re-launches, as each process has exactly one cold-start lifetime.
4847 */
48+ @Suppress(" PreferTimeProvider" , " UnsafeThirdPartyFunctionCall" )
4949object AppLaunchPreInitCollector {
5050
5151 /* *
@@ -142,7 +142,7 @@ object AppLaunchPreInitCollector {
142142 // endregion
143143
144144 private val firstFrameCallbacks = CopyOnWriteArrayList < (Long ) -> Unit > ()
145- private var _application : Application ? = null
145+ private var registeredApplication : Application ? = null
146146
147147 /* * Private flag tracking whether any Activity has been destroyed (process is warm). */
148148 private var _isFirstActivityForProcess : Boolean = true
@@ -201,7 +201,6 @@ object AppLaunchPreInitCollector {
201201 private fun onBeforeActivityCreated (activity : Activity , savedInstanceState : Bundle ? ) {
202202 // CAS: only the first caller transitions IDLE -> CAPTURING; concurrent claim() loses
203203 if (! _state .compareAndSet(State .IDLE , State .CAPTURING )) {
204- Log .d(TAG , " onBeforeActivityCreated: CAS failed — state is ${_state .get()} , not IDLE; skipping" )
205204 return
206205 }
207206
@@ -211,49 +210,33 @@ object AppLaunchPreInitCollector {
211210 isFirstActivityForProcess = _isFirstActivityForProcess
212211 processStartNs = computeProcessStartNs()
213212
214- Log .d(
215- TAG ,
216- " IDLE→CAPTURING: activity=${activity.javaClass.simpleName} " +
217- " hasSavedInstanceState=$hasSavedInstanceState " +
218- " isFirstActivityForProcess=$isFirstActivityForProcess " +
219- " processStartNs=$processStartNs " +
220- " activityOnCreateNs=$activityOnCreateNs " +
221- " gapMs=${(activityOnCreateNs - processStartNs) / 1_000_000 } "
222- )
223-
224213 // Unregister lifecycle callbacks — we've captured what we need from the first Activity
225- _application ?.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
214+ registeredApplication ?.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
226215
227216 // Subscribe to first frame drawn — transitions CAPTURING -> COMPLETE
228217 val handler = handlerFactory()
229218 val reporter = firstDrawTimeReporterFactory(handler)
230- reporter.subscribeToFirstFrameDrawn(activity, object : RumFirstDrawTimeReporter .Callback {
231- override fun onFirstFrameDrawn (timestampNs : Long ) {
232- firstFrameNs = timestampNs
233- _state .compareAndSet(State .CAPTURING , State .COMPLETE )
234-
235- Log .d(
236- TAG ,
237- " CAPTURING->COMPLETE: first frame drawn" +
238- " firstFrameNs=$firstFrameNs " +
239- " ttidMs=${(firstFrameNs - activityOnCreateNs) / 1_000_000 } " +
240- " totalMs=${(firstFrameNs - processStartNs) / 1_000_000 } " +
241- " pendingCallbacks=${firstFrameCallbacks.size} "
242- )
243-
244- // Drain all enqueued callbacks
245- val callbacks = firstFrameCallbacks.toList()
246- firstFrameCallbacks.clear()
247- callbacks.forEach { cb -> cb(firstFrameNs) }
219+ reporter.subscribeToFirstFrameDrawn(
220+ activity,
221+ object : RumFirstDrawTimeReporter .Callback {
222+ override fun onFirstFrameDrawn (timestampNs : Long ) {
223+ firstFrameNs = timestampNs
224+ _state .compareAndSet(State .CAPTURING , State .COMPLETE )
225+
226+ // Drain all enqueued callbacks
227+ val callbacks = firstFrameCallbacks.toList()
228+ firstFrameCallbacks.clear()
229+ callbacks.forEach { cb -> cb(firstFrameNs) }
230+ }
248231 }
249- } )
232+ )
250233 }
251234
252235 /* *
253236 * Compute the process start time in nanoseconds.
254237 *
255238 * On API 24+, uses Process.getStartElapsedRealtime() to back-compute from the current
256- * elapsed realtime clock. Applies a two-direction OEM sanity check:
239+ * elapsed realtime clock. Applies a two-direction OEM coherence check:
257240 * - If computed time is after DdRumContentProvider.createTimeNs (impossible), fall back.
258241 * - If computed time is more than 10s before createTimeNs (unreasonable), fall back.
259242 *
@@ -294,12 +277,10 @@ object AppLaunchPreInitCollector {
294277 */
295278 fun install (application : Application ) {
296279 if (! _state .compareAndSet(State .NOT_INSTALLED , State .IDLE )) {
297- Log .d(TAG , " install() called but already in state ${_state .get()} , skipping" )
298280 return
299281 }
300- _application = application
282+ registeredApplication = application
301283 application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
302- Log .d(TAG , " Installed — state: IDLE, ActivityLifecycleCallbacks registered" )
303284 }
304285
305286 /* *
@@ -318,10 +299,7 @@ object AppLaunchPreInitCollector {
318299 fun claim (): Boolean {
319300 val success = _state .compareAndSet(State .IDLE , State .CLAIMED )
320301 if (success) {
321- _application ?.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
322- Log .d(TAG , " Claimed — SDK initialized before first Activity (IDLE→CLAIMED); callbacks unregistered" )
323- } else {
324- Log .d(TAG , " claim() CAS failed — state is ${_state .get()} , not IDLE; collector already in use" )
302+ registeredApplication?.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
325303 }
326304 return success
327305 }
@@ -342,30 +320,24 @@ object AppLaunchPreInitCollector {
342320 */
343321 fun addFirstFrameCallback (cb : (Long ) -> Unit ) {
344322 if (_state .get() == State .COMPLETE ) {
345- Log .d(TAG , " addFirstFrameCallback: already COMPLETE, invoking callback synchronously" )
346323 cb(firstFrameNs)
347324 return
348325 }
349326 firstFrameCallbacks.add(cb)
350327 // Double-check: state may have transitioned to COMPLETE between the first check and the add
351328 if (_state .get() == State .COMPLETE ) {
352329 if (firstFrameCallbacks.remove(cb)) {
353- Log .d(TAG , " addFirstFrameCallback: TOCTOU race — COMPLETE during enqueue, invoking synchronously" )
354330 cb(firstFrameNs)
355331 }
356- } else {
357- Log .d(TAG , " addFirstFrameCallback: state=${_state .get()} , callback enqueued (${firstFrameCallbacks.size} total)" )
358332 }
359333 }
360334
361335 // endregion
362336
363337 // region Constants
364338
365- internal const val TAG = " DD/AppLaunch"
366-
367339 /* *
368- * Threshold for the two-direction OEM clock sanity check.
340+ * Threshold for the two-direction OEM clock coherence check.
369341 * If computed process start time is more than 10s before DdRumContentProvider.createTimeNs,
370342 * it is considered an OEM bug and createTimeNs is used as fallback.
371343 */
@@ -392,7 +364,7 @@ object AppLaunchPreInitCollector {
392364 _isFirstActivityForProcess = true
393365 activity = null
394366 firstFrameCallbacks.clear()
395- _application = null
367+ registeredApplication = null
396368 buildSdkVersionProvider = BuildSdkVersionProvider .DEFAULT
397369 handlerFactory = { Handler (Looper .getMainLooper()) }
398370 firstDrawTimeReporterFactory = { handler ->
0 commit comments