ReadonlyansweredOne entry per answered (trigger context, surface): the app's
OnDemandSurfaceTriggerCache holds one cache per (trigger, surface,
trigger context), so a surface answered for one profile is not answered for
another. The key is quickPromotionStashKey; the value is when the
answer came back (the process clock, milliseconds).
The app's quick-promotion stash: the moment each surface's last answered batch fetch came back. A slot whose surfaces all have a fresh answer resolves from the stash and sends no
QuickPromotionSurfaceQueryV3; a surface without one — never asked, asked too long ago, or whose fetch failed — makes the slot fetch.451.0.0.53.80, decompiled: the QP SDK keeps one cache per (trigger, surface, trigger context) —
QPSdkOnDemandSurfaceControllerbuilds acom.facebook.quickpromotion.sdk.fetcher.ondemand.OnDemandSurfaceTriggerCacheper entry and asks the cache whether the fetch may be skipped (OnDemandSurfaceTriggerCache.A02, :563-588): a cache that has never been answered (A00 == 0), or a forced refresh, fetches; otherwise it is fresh whilenow <= answeredAtMs + ttl. The answer's time is stored whatever the answer carried (A01= the answer's promotions,:532-534— an answer with no promotion stores the empty list), and a failed fetch keeps the cache's previous time (jNow = onDemandSurfaceTriggerCache.A00,:332/:446), so a failure does not make the slot skip its next fetch. The app's own slot fetcher repeats the same window on its side (IGSlotFetcher'sAnonymousClass0181:A02= 2 h for a normal refresh,A01= 10 s for a forced one).The time-to-live.
OnDemandSurfaceTriggerCache.A02picks it by the surface (:571-582):LOGIN_INTERSTITIALtakesC01Z9.A01=TimeUnit.MINUTES.toMillis(30),BARCELONA_MEGAPHONEreads the MobileConfig parameter36610524569411332(seconds; its build default isC01Z9.A00), and every other surface takesC01Z9.A00=TimeUnit.HOURS.toMillis(2)(C01Z9.java:9-10). The surfaces the library's slots ask about — the comment sheet's tooltip, megaphone, interstitial, promotional-dialog and floating-banner surfaces (media.comments.quick-promotion.ts), the Explore tab's six, the profile's four — are none of the two special ones, so their window is the two-hour constant; the MobileConfig parameter is not declared for them, because the app does not read a parameter for them.The app keys the cache by the fired trigger's surface with the trigger context, and the library's keys are the same pair (
quickPromotionStashKey). A batch that names no context — the Explore tab's, the comment sheet's — has one key per surface, while the profile's names the profile it visits and the way it was reached, so two profiles are two answers (android-451/follow-from-search026/040/054/071 fetch four of them in one process,android-451/media-like108 fetches the one whose user had no fresh entry).The answer outlives the process, so a checkpoint carries it. The app persists the answer's time: the write path serialises the answer through
OnDemandStashHelper.serialize(the promotions into the stash file, the time into the metadata store underlastFetchTime2/<stash key>), andOnDemandSurfaceTriggerCache.A01(initializeFromStash) puts the stored time back intoA00when the next process builds the cache. So a restarted app opens the sheet without a batch while the answer is fresh, and the library's stash is carried by the checkpoint (the partandroid.quick-promotion-stash,encodeAndroidQuickPromotionStash/RestoreAndroidQuickPromotionStash): a restored process is as indistinguishable on the wire from a continued one as the app's own restart is. It is never written to the session snapshot: the snapshot is the account's identity, not one process's caches.