Skip to content

Instantly share code, notes, and snippets.

@j3lte
Created May 27, 2026 11:05
Show Gist options
  • Select an option

  • Save j3lte/0bdb1158ae7b98d3cdc2cc2ff64fc459 to your computer and use it in GitHub Desktop.

Select an option

Save j3lte/0bdb1158ae7b98d3cdc2cc2ff64fc459 to your computer and use it in GitHub Desktop.
Motorola Smart Feed — Affiliate Link Hijacking Investigation (DeviceNative SDK)

Motorola Smart Feed — Affiliate Link Hijacking Investigation

TL;DR

Motorola's pre-installed Smart Feed app (com.motorola.smartfeed) contains a third-party SDK called DeviceNative that silently injects affiliate-tracked app suggestions into the Motorola launcher. When users click what appears to be a normal app icon in their launcher's suggestion area, the click is intercepted and redirected through affiliate tracking URLs (america.devicenative.com / asia.devicenative.com) before landing on the destination. 144 apps are targeted across 16 markets, including Walmart, Target, Best Buy, Temu, AliExpress, Expedia, and more.

This investigation was performed through static analysis of APK version 1.03.0095 (build 361030095) using jadx and apktool, followed by live API queries to the DeviceNative server.


How It Works

The Setup

Smart Feed registers as a launcher plugin on Motorola devices:

  • Declares SmartFeedOverlayService with intent filter com.motorola.launcher3.ALL_APPS_OVERLAY_SERVICE
  • Exports a ContentAPIInterface ContentProvider at authority com.motorola.smartfeed.launcher_smart_plugins
  • Comes pre-installed as a system app — users cannot easily uninstall it

The Motorola launcher calls this ContentProvider to get app suggestions for the "Suggested Apps" section in the app tray.

The Injection

  1. Config + Ad sync: The DeviceNative SDK (moto-v1.5.15) periodically POSTs to https://api.devicenative.com/v2/sync with device fingerprint data (model, carrier, Android version, installed apps, usage stats). The server responds with:

    • A catalog of ad entries (apps to target), each with a clickUrl (tracking redirect) and destinationUrl (final affiliate destination)
    • 83KB of JavaScript code that runs in a QuickJS engine on-device to select and rank ads
    • Config flags controlling behavior
  2. Disguised suggestions: When the launcher asks for app suggestions, Smart Feed returns DeviceNative ad results formatted as type: "app_suggestion" with call_click_action: true. These use the real app's package name and icon, making them visually indistinguishable from legitimate suggestions.

  3. Click interception: When the user taps a suggestion, the launcher calls handle_click → Smart Feed's OnboardActivity → detects "DNA" source → routes through affiliate redirect system instead of launching the app normally.

The Redirect Chain

User taps "Amazon" suggestion in launcher
  → Launcher calls ContentAPIInterface.handle_click()
  → OnboardActivity detects DNA source
  → C4812h.m7677e() checks if app is installed
  → If NOT installed: C4622h.m7526D(clickUrl, destinationUrl, doRoute=true)
    → HTTP GET to america.devicenative.com/click?aId=...&dK=...&pN=...
    → Follow up to 5 HTTP 301/302 redirects
    → Final destination opens in browser (e.g., amazon.com with affiliate tag)
  → If installed: background tracking only, click does nothing visible

Server-Side Control

All behavior is remotely controlled. Key flags the server can toggle at any time:

Flag Purpose
rGP Replace ALL Google Play Store URLs with affiliate tracking URLs
gpU The affiliate URL template (e.g., https://tracking.example.com/click?app={packageName})
orgB Boost paid/sponsored results to appear as organic suggestions
vta Fire affiliate tracking URLs just from ads being displayed (no click needed)
org Enable organic-looking ads

Additional Monetization

Beyond launcher suggestions, the SDK also:

  • Injects ads into the Smart Feed content feed
  • Replaces Play Store links with affiliate URLs when rGP=true
  • Downloads and executes arbitrary JavaScript from the server
  • Collects installed app lists and usage statistics via QUERY_ALL_PACKAGES and PACKAGE_USAGE_STATS permissions

Files in This Gist

File Description
0_README.md This file — overview and technical explanation
1_ADS_RESULTS.md Full ad catalog across 32 markets (144 targeted apps)
2_INVESTIGATE_RESULTS.md Detailed reverse engineering findings with code references
3_sweep_markets.ts Deno script to query the DeviceNative API across all markets
4_dna_ad_selection_code.js The actual JavaScript code the server sends to control ad selection (83KB)

Reproduction

Query the DeviceNative API yourself:

# Single market
deno run --allow-net --allow-write --allow-read fetch_dna_ads.ts

# All markets
deno run --allow-net --allow-write --allow-read sweep_markets.ts

The API key 2d6e21f3-1213-4aac-9cd9-06e8ec91fa4e is hardcoded in the APK as the default fallback (found at C4317e.m7209a(), line 28).

Key Evidence (Code References)

All references to the decompiled APK (jadx --show-bad-code --deobf):

  • DNA suggestions disguised as app icons: C4354w0.m7260e() — sets type: "app_suggestion", call_click_action: true
  • DNA source detection + redirect: OnboardActivity.m6822e()if ("DNA".equalsIgnoreCase(content.m6839i())) → routes to affiliate system
  • Click handler with redirect chain: C4812h.m7677e()C4622h.m7526D() — follows HTTP redirects up to 5 hops
  • Play Store URL replacement: C4622h.m7520J() — replaces market:// and play.google.com URLs with affiliate template
  • URL template system: C4622h.m7523w() — fills {adId}, {packageName}, {clickId}, {productSource} in tracking URLs
  • Remote JS execution: C4906j — QuickJS engine executes server-downloaded JavaScript with access to installed apps and usage data
  • Server config sync: RunnableC0249e — downloads flags, JS code, and ad bundles from api.devicenative.com/v2/sync

Infrastructure

  • Click tracking: america.devicenative.com, asia.devicenative.com
  • Impression tracking: imptrk.siteplug.com (SitePlug — a known affiliate marketing company)
  • API server: api.devicenative.com/v2/
  • Config WebView: Runs in separate process (:dna_config_builder)

Affected Devices

Any Motorola device with Smart Feed pre-installed. The SDK identifies itself as moto-v1.5.15. Confirmed active in: US, Brazil, Mexico, Argentina, Colombia, Germany, UK, France, Italy, Spain, Netherlands, Poland, India.

DeviceNative DNA — Full Ad Catalog Across Markets

Date: 2026-05-27 API Endpoint: POST https://api.devicenative.com/v2/sync API Key: 2d6e21f3-1213-4aac-9cd9-06e8ec91fa4e (hardcoded default from APK) SDK Version: moto-v1.5.15 Markets queried: 32 Unique apps targeted: 144


Server Config Flags by Market

Market rGP vta org orgB aDL sad rad cpi Ads
United States (US) false true true false true true true false 41
United States (Verizon) (US) false true true false true true true false 41
United States (AT&T) (US) false true true false true true true false 41
Canada (CA) false true true false true true true false 0
Brazil (BR) false true true false true true true false 25
Mexico (MX) false true true false true true true false 17
Argentina (AR) false true true false true true true false 7
Colombia (CO) false true true false true true true false 7
Germany (DE) false true true false true true true false 22
United Kingdom (GB) false true true false true true true false 14
France (FR) false true true false true true true false 29
Italy (IT) false true true false true true true false 23
Spain (ES) false true true false true true true false 23
Netherlands (NL) false true true false true true true false 11
Poland (PL) false true true false true true true false 13
Austria (AT) false true true false true true true false 0
Sweden (SE) false true true false true true true false 0
Belgium (BE) false true true false true true true false 0
Portugal (PT) false true true false true true true false 0
Romania (RO) false true true false true true true false 0
India (IN) false true true false true true true false 34
Japan (JP) false true true false true true true false 0
Australia (AU) false true true false true true true false 0
South Korea (KR) false true true false true true true false 0
Indonesia (ID) false true true false true true true false 0
Thailand (TH) false true true false true true true false 0
Malaysia (MY) false true true false true true true false 0
Saudi Arabia (SA) false true true false true true true false 0
UAE (AE) false true true false true true true false 0
South Africa (ZA) false true true false true true true false 0
Nigeria (NG) false true true false true true true false 0
Default (no country) (DEFAULT) false true true false true true true false 22

Complete Ad Catalog

App Package Destination Markets
AliExpress com.alibaba.aliexpresshd aliexpress.com US, US, US, BR, MX, AR, CO, DE, FR, IT, ES, NL, PL, DEFAULT
Temu com.einnovation.temu temu.com US, US, US, MX, AR, CO, DE, GB, FR, IT, ES, NL, PL, DEFAULT
Tripadvisor com.tripadvisor.tripadvisor tripadvisor.com US, US, US, BR, MX, AR, CO, FR, IT, ES, NL, PL, IN
adidas com.adidas.app adidas.com US, US, US, BR, MX, DE, GB, FR, IT, ES, PL, DEFAULT
Expedia com.expedia.bookings expedia.com US, US, US, BR, MX, AR, DE, FR, IT, ES, DEFAULT
Hotels.com com.hcom.android hotels.com US, US, US, BR, MX, DE, GB, FR, IT, ES, DEFAULT
Alibaba.com com.alibaba.intl.android.apps.poseidon alibaba.com US, US, US, BR, MX, AR, CO, FR, IT, ES, NL
G2A com.g2a.marketplace g2a.com US, US, US, DE, FR, IT, ES, PL, DEFAULT
SHEIN com.zzkko de.shein.com US, US, US, BR, MX, AR, CO, GB
Trip.com ctrip.english trip.com US, US, US, GB, FR, IT, ES
Sephora com.sephora sephora.com US, US, US, DE, FR, PL, DEFAULT
DHgate com.dhgate.buyermob dhgate.com US, US, US, BR, MX, FR, ES
Skyscanner Flights Hotels Cars net.skyscanner.android.main skyscanner.com GB, FR, IT, ES, NL, PL, IN
trivago com.trivago trivago.com US, US, US, BR, FR, PL
Booking.com com.booking booking.com AR, CO, FR, IT, ES, NL
TikTok com.zhiliaoapp.musically seller.tiktok.com DE, GB, FR, IT, ES, DEFAULT
DAZN com.dazn dazn.com US, US, US, IT, ES
PUMA com.puma.ecom.app eu.puma.com FR, IT, ES, NL, IN
Macy's com.macys.android macys.com US, US, US, MX
bonprix de.bonprix bonprix.at DE, FR, IT, DEFAULT
Weather & Radar Forecast de.wetteronline.wetterapp weatherandradar.co US, US, US
My Total Wireless com.tracfone.total.myaccount totalwireless.com US, US, US
JCPenney com.jcp jcpenney.com US, US, US
Belk com.belk.android.belk belk.com US, US, US
Vrbo Vacation Rentals com.vrbo.android vrbo.com US, US, US
Instacart com.instacart.client instacart.com US, US, US
Grunt Style co.tapcart.app.id_D0F10MVfEw gruntstyle.com US, US, US
Zulily com.zulily.android zulily.com US, US, US
StockX com.stockx.stockx stockx.com US, US, US
Target com.target.ui target.com US, US, US
Walmart Business com.walmart.android.b2b business.walmart.com US, US, US
BetterHelp com.betterhelp betterhelp.com US, US, US
Best Buy com.bestbuy.android bestbuy.com US, US, US
iHerb com.iherb iherb.com US, US, US
IHG One Rewards com.ihg.apps.android ihg.com US, US, US
Walmart com.walmart.android walmart.com US, US, US
HelloFresh com.hellofresh.androidapp hellofresh.com US, US, US
Western Union Send Money Now com.westernunion.android.mtapp westernunion.com US, US, US
Groupon – Deals & Coupons com.groupon groupon.com US, US, US
Wish com.contextlogic.wish wish.com US, US, US
Lowe's com.lowes.android lowes.ca US, US, US
Bed Bath & Beyond com.overstock bedbathandbeyond.com US, US, US
StubHub com.stubhub stubhub.com US, US, US
Jomashop com.jomashop.app jomashop.com US, US, US
KAYAK com.kayak.android kayak.com US, US, US
Reverb com.reverb.app reverb.com US, US, US
MyHeritage: Family Tree & DNA air.com.myheritage.mobile myheritage.com FR, IT, ES
Amazon Shopping com.amazon.mShop.android.shopping amazon.com BR, MX
Nike com.nike.omega nike.com MX, FR
OTTO de.cellular.ottohybrid otto.com DE, DEFAULT
Shop Apotheke shop.shop_apotheke.com.shopapotheke shop-apotheke.com DE, DEFAULT
idealo de.idealo.android idealo.de DE, DEFAULT
Ernsting's family de.ernstings ernstings-family.de DE, DEFAULT
heine de.heine.mobile heine.de DE, DEFAULT
Joybuy-Don't Just Buy! com.joybuy.jdi joybuy.co DE, DEFAULT
REWE de.rewe.app.mobile rewe.de DE, DEFAULT
MediaMarkt Belgique be.com.media.markt mediamarkt.be DE, DEFAULT
Sieh an! eu.wittgruppe.siehan sieh-an.de DE, DEFAULT
H&M com.hm.goe hm.com DE, DEFAULT
RTL+ de.rtli.tvnow plus.rtl.de DE, DEFAULT
Witt eu.wittgruppe.witt witt-weiden.de DE, DEFAULT
SteuerSparErklärung de.steuertipps.stapp steuertipps.de DE, DEFAULT
Alamo com.alamo.mobile alamo.com FR, ES
Enterprise Car Rental com.ehi.enterprise.android enterprise.com FR, ES
Europcar Mobility App com.europcar.europcarmobility europcar.com FR, IT
Fiverr com.fiverr.fiverr fiverr.com NL, IN
Samsung Shop com.samsung.ecomm.global.in samsung.co IN, IN
Rigi TV com.litvibes rigitv.club IN, IN
ManyJobs com.manyjobs manyjobs.com IN, IN
Junglee Ludo com.junglee.ludo.onlinegame junglee-ludo.com IN, IN
Anytime Rupee com.anytimerupee anytimerupee.com IN, IN
toing in.swiggy.android.toing toingit.com IN, IN
Vivara tellerina.com.br.vivara vivara.com BR
Posthaus Moda pra gente br.com.supero.posthaus.site posthaus.com
Cobasi com.root.cobasi.Activities cobasi.com BR
Shopee com.shopee.br shopee.com BR
Decolar com.gm.decolar decolar.com BR
Lojas Renner br.com.lojasrenner lojasrenner.com BR
Natura net.natura.semprepresente natura.com BR
Casas Bahia com.novapontocom.casasbahia casasbahia.com BR
Centauro br.com.sbf.centauro centauro.com BR
O Boticário com.boticario.mobile boticario.com BR
Beleza na Web br.com.belezanaweb.android belezanaweb.com BR
Pontofrio com.sonyericsson.pontofrio pontofrio.com BR
Dafiti br.com.dafiti dafiti.com BR
Nike App br.com.gruposbf.nike nike.com BR
Netshoes br.com.netshoes.app netshoes.com BR
Priceline com.priceline.android.negotiator priceline.com MX
Shopee Indonesia com.shopee.id shopee.co MX
Walmart com.walmart.mg walmart.com MX
Elektra mx.elektra.appelektra elektra.com MX
Sam’s Club México air.SamsClubMexico sams.com MX
Jumbo com.cencosud.co.clientes jumbocolombia.com CO
Burton Menswear London com.arcadia.burton burton.co GB
Pizza Hut India com.phdv.universal pizzahut.co GB
Debenhams com.debenhams.app debenhams.com GB
New Look Fashion com.tigerspike.newlook newlook.com GB
Zavvi com.thehutgroup.ecommerce.zavvi zavvi.com GB
Dorothy Perkins com.arcadia.dp dorothyperkins.com GB
Wayfair com.wayfair.wayfair wayfair.co GB
Voghion com.voghion.app de.voghion.com FR
Lastminute Hotels & Flights com.lastminute.android lastminute.com FR
Loaded com.cdkeys cdkeys.com FR
Back Market com.backmarket backmarket.co FR
PrettyLittleThing com.poqstudio.app.platform.plt prettylittlething.fr FR
Etsy com.etsy.android etsy.com FR
JD Sports com.jd.jdsportsusa jdsports.fr FR
Trainline com.thetrainline thetrainline.com IT
HRS com.hrs.b2c.android hrs.com IT
TicketOne it.ticketone.mobile.app.Android ticketone.it IT
Privé by Zalando de.zalando.prive zalando-prive.es IT
Italo: Italian Highspeed Train it.italotreno italotreno.com IT
SEPHORA com.sephora.digital sephora.it IT
bonprix es.bonprix.bonprix bonprix.at ES
Habitissimo para profesionales com.habitissimo.app.pro habitissimo.es ES
La Redoute com.ocito.laredoute laredoute.com ES
Fubo tv.fubo.mobile fubo.tv ES
Sephora fr.sephora.sephorafrance sephora.com ES
Go City com.gocitypass gocity.com NL
ANWB Onderweg & Wegenwacht com.themobilecompany.Onderweg anwb.nl NL
Coolblue eu.coolblue.shop coolblue.be NL
ERLI pl.erli.app erli.pl PL
eobuwie pl.eobuwie.eobuwieapp eobuwie.com.pl PL
bonprix pl.bonprix bonprix.at PL
Ceneo pl.ceneo ceneo.pl PL
Allegro pl.allegro allegro.pl PL
Uni cards.uni.app.credit uni.cards IN
ICICI Direct com.icicidirect.idirectsuper icicidirect.com IN
Angel One com.msf.angelmobile angelbroking.com IN
WAVES com.prasarbharati.bharat.ott wavespb.com IN
QuickTV in.mohalla.quicktv quicktv.co IN
WAVES com.prasarbharati.android wavespb.com IN
Axis Max Life Insurance com.maxlifeinsurance.www.twa axismaxlife.com IN
Amazon India in.amazon.mShop.android.shopping amazon.in IN
Shopsy Shopping App com.flipkart.shopsy shopsy.in IN
EaseMyTrip com.easemytrip.android easemytrip.com IN
MX Player com.mxtech.videoplayer.ad mxplayer.in IN
Nykaa Fashion com.fsn.nds nykaafashion.com IN
Flipkart Online Shopping App com.flipkart.android flipkart.com IN
Myntra com.myntra.android myntra.com IN
Bewakoof com.bewakoof.bewakoof bewakoof.com IN
MakeMyTrip com.makemytrip makemytrip.com IN
Nykaa com.fsn.nykaa nykaa.com IN
AJIO com.ril.ajio ajio.com IN

Per-Market Breakdown

United States (US)

  • Device: motorola razr 50 ultra
  • Carrier: T-Mobile
  • Ads: 41
  • JS code: 83416 chars
App Package Click URL Destination
Weather & Radar Forecast de.wetteronline.wetterapp america.devicenative.com weatherandradar.co
My Total Wireless com.tracfone.total.myaccount america.devicenative.com totalwireless.com
G2A com.g2a.marketplace america.devicenative.com g2a.com
JCPenney com.jcp america.devicenative.com jcpenney.com
Belk com.belk.android.belk america.devicenative.com belk.com
Vrbo Vacation Rentals com.vrbo.android america.devicenative.com vrbo.com
Instacart com.instacart.client america.devicenative.com instacart.com
DAZN com.dazn america.devicenative.com dazn.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
Grunt Style co.tapcart.app.id_D0F10MVfEw america.devicenative.com gruntstyle.com
Zulily com.zulily.android america.devicenative.com zulily.com
StockX com.stockx.stockx america.devicenative.com stockx.com
Temu com.einnovation.temu america.devicenative.com temu.com
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
Expedia com.expedia.bookings america.devicenative.com expedia.com
Trip.com ctrip.english america.devicenative.com trip.com
Target com.target.ui america.devicenative.com target.com
Hotels.com com.hcom.android america.devicenative.com hotels.com
Walmart Business com.walmart.android.b2b america.devicenative.com business.walmart.com
trivago com.trivago america.devicenative.com trivago.com
BetterHelp com.betterhelp america.devicenative.com betterhelp.com
Best Buy com.bestbuy.android america.devicenative.com bestbuy.com
iHerb com.iherb america.devicenative.com iherb.com
IHG One Rewards com.ihg.apps.android america.devicenative.com ihg.com
Walmart com.walmart.android america.devicenative.com walmart.com
Macy's com.macys.android america.devicenative.com macys.com
HelloFresh com.hellofresh.androidapp america.devicenative.com hellofresh.com
adidas com.adidas.app america.devicenative.com adidas.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
SHEIN com.zzkko america.devicenative.com de.shein.com
Western Union Send Money Now com.westernunion.android.mtapp america.devicenative.com westernunion.com
Groupon – Deals & Coupons com.groupon america.devicenative.com groupon.com
Wish com.contextlogic.wish america.devicenative.com wish.com
Lowe's com.lowes.android america.devicenative.com lowes.ca
Sephora com.sephora america.devicenative.com sephora.com
DHgate com.dhgate.buyermob america.devicenative.com dhgate.com
Bed Bath & Beyond com.overstock america.devicenative.com bedbathandbeyond.com
StubHub com.stubhub america.devicenative.com stubhub.com
Jomashop com.jomashop.app america.devicenative.com jomashop.com
KAYAK com.kayak.android america.devicenative.com kayak.com
Reverb com.reverb.app america.devicenative.com reverb.com

United States (Verizon) (US)

  • Device: motorola edge 50 ultra
  • Carrier: Verizon
  • Ads: 41
  • JS code: 83416 chars
App Package Click URL Destination
Weather & Radar Forecast de.wetteronline.wetterapp america.devicenative.com weatherandradar.co
My Total Wireless com.tracfone.total.myaccount america.devicenative.com totalwireless.com
G2A com.g2a.marketplace america.devicenative.com g2a.com
JCPenney com.jcp america.devicenative.com jcpenney.com
Belk com.belk.android.belk america.devicenative.com belk.com
Vrbo Vacation Rentals com.vrbo.android america.devicenative.com vrbo.com
Instacart com.instacart.client america.devicenative.com instacart.com
DAZN com.dazn america.devicenative.com dazn.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
Grunt Style co.tapcart.app.id_D0F10MVfEw america.devicenative.com gruntstyle.com
Zulily com.zulily.android america.devicenative.com zulily.com
StockX com.stockx.stockx america.devicenative.com stockx.com
Temu com.einnovation.temu america.devicenative.com temu.com
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
Expedia com.expedia.bookings america.devicenative.com expedia.com
Trip.com ctrip.english america.devicenative.com trip.com
Target com.target.ui america.devicenative.com target.com
Hotels.com com.hcom.android america.devicenative.com hotels.com
Walmart Business com.walmart.android.b2b america.devicenative.com business.walmart.com
trivago com.trivago america.devicenative.com trivago.com
BetterHelp com.betterhelp america.devicenative.com betterhelp.com
Best Buy com.bestbuy.android america.devicenative.com bestbuy.com
iHerb com.iherb america.devicenative.com iherb.com
IHG One Rewards com.ihg.apps.android america.devicenative.com ihg.com
Walmart com.walmart.android america.devicenative.com walmart.com
Macy's com.macys.android america.devicenative.com macys.com
HelloFresh com.hellofresh.androidapp america.devicenative.com hellofresh.com
adidas com.adidas.app america.devicenative.com adidas.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
SHEIN com.zzkko america.devicenative.com de.shein.com
Western Union Send Money Now com.westernunion.android.mtapp america.devicenative.com westernunion.com
Groupon – Deals & Coupons com.groupon america.devicenative.com groupon.com
Wish com.contextlogic.wish america.devicenative.com wish.com
Lowe's com.lowes.android america.devicenative.com lowes.ca
Sephora com.sephora america.devicenative.com sephora.com
DHgate com.dhgate.buyermob america.devicenative.com dhgate.com
Bed Bath & Beyond com.overstock america.devicenative.com bedbathandbeyond.com
StubHub com.stubhub america.devicenative.com stubhub.com
Jomashop com.jomashop.app america.devicenative.com jomashop.com
KAYAK com.kayak.android america.devicenative.com kayak.com
Reverb com.reverb.app america.devicenative.com reverb.com

United States (AT&T) (US)

  • Device: motorola razr 60 ultra
  • Carrier: AT&T
  • Ads: 41
  • JS code: 83416 chars
App Package Click URL Destination
Weather & Radar Forecast de.wetteronline.wetterapp america.devicenative.com weatherandradar.co
My Total Wireless com.tracfone.total.myaccount america.devicenative.com totalwireless.com
G2A com.g2a.marketplace america.devicenative.com g2a.com
JCPenney com.jcp america.devicenative.com jcpenney.com
Belk com.belk.android.belk america.devicenative.com belk.com
Vrbo Vacation Rentals com.vrbo.android america.devicenative.com vrbo.com
Instacart com.instacart.client america.devicenative.com instacart.com
DAZN com.dazn america.devicenative.com dazn.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
Grunt Style co.tapcart.app.id_D0F10MVfEw america.devicenative.com gruntstyle.com
Zulily com.zulily.android america.devicenative.com zulily.com
StockX com.stockx.stockx america.devicenative.com stockx.com
Temu com.einnovation.temu america.devicenative.com temu.com
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
Expedia com.expedia.bookings america.devicenative.com expedia.com
Trip.com ctrip.english america.devicenative.com trip.com
Target com.target.ui america.devicenative.com target.com
Hotels.com com.hcom.android america.devicenative.com hotels.com
Walmart Business com.walmart.android.b2b america.devicenative.com business.walmart.com
trivago com.trivago america.devicenative.com trivago.com
BetterHelp com.betterhelp america.devicenative.com betterhelp.com
Best Buy com.bestbuy.android america.devicenative.com bestbuy.com
iHerb com.iherb america.devicenative.com iherb.com
IHG One Rewards com.ihg.apps.android america.devicenative.com ihg.com
Walmart com.walmart.android america.devicenative.com walmart.com
Macy's com.macys.android america.devicenative.com macys.com
HelloFresh com.hellofresh.androidapp america.devicenative.com hellofresh.com
adidas com.adidas.app america.devicenative.com adidas.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
SHEIN com.zzkko america.devicenative.com de.shein.com
Western Union Send Money Now com.westernunion.android.mtapp america.devicenative.com westernunion.com
Groupon – Deals & Coupons com.groupon america.devicenative.com groupon.com
Wish com.contextlogic.wish america.devicenative.com wish.com
Lowe's com.lowes.android america.devicenative.com lowes.ca
Sephora com.sephora america.devicenative.com sephora.com
DHgate com.dhgate.buyermob america.devicenative.com dhgate.com
Bed Bath & Beyond com.overstock america.devicenative.com bedbathandbeyond.com
StubHub com.stubhub america.devicenative.com stubhub.com
Jomashop com.jomashop.app america.devicenative.com jomashop.com
KAYAK com.kayak.android america.devicenative.com kayak.com
Reverb com.reverb.app america.devicenative.com reverb.com

Brazil (BR)

  • Device: motorola moto g84
  • Carrier: Claro
  • Ads: 25
  • JS code: 83416 chars
App Package Click URL Destination
SHEIN com.zzkko america.devicenative.com br.shein.com
DHgate com.dhgate.buyermob america.devicenative.com dhgate.com
Vivara tellerina.com.br.vivara america.devicenative.com vivara.com
adidas com.adidas.app america.devicenative.com adidas.be
Posthaus Moda pra gente br.com.supero.posthaus.site america.devicenative.com
Hotels.com com.hcom.android america.devicenative.com hotels.com
Expedia com.expedia.bookings america.devicenative.com expedia.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
Cobasi com.root.cobasi.Activities america.devicenative.com cobasi.com
Shopee com.shopee.br america.devicenative.com shopee.com
Decolar com.gm.decolar america.devicenative.com decolar.com
Lojas Renner br.com.lojasrenner america.devicenative.com lojasrenner.com
Natura net.natura.semprepresente america.devicenative.com natura.com
Casas Bahia com.novapontocom.casasbahia america.devicenative.com casasbahia.com
Centauro br.com.sbf.centauro america.devicenative.com centauro.com
O Boticário com.boticario.mobile america.devicenative.com boticario.com
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
Amazon Shopping com.amazon.mShop.android.shopping america.devicenative.com amazon.com
Beleza na Web br.com.belezanaweb.android america.devicenative.com belezanaweb.com
Pontofrio com.sonyericsson.pontofrio america.devicenative.com pontofrio.com
trivago com.trivago america.devicenative.com trivago.com
Dafiti br.com.dafiti america.devicenative.com dafiti.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
Nike App br.com.gruposbf.nike america.devicenative.com nike.com
Netshoes br.com.netshoes.app america.devicenative.com netshoes.com

Mexico (MX)

  • Device: motorola moto g84
  • Carrier: Telcel
  • Ads: 17
  • JS code: 83416 chars
App Package Click URL Destination
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
DHgate com.dhgate.buyermob america.devicenative.com dhgate.com
SHEIN com.zzkko america.devicenative.com br.shein.com
Expedia com.expedia.bookings america.devicenative.com expedia.com
Macy's com.macys.android america.devicenative.com macys.com
Priceline com.priceline.android.negotiator america.devicenative.com priceline.com
Shopee Indonesia com.shopee.id america.devicenative.com shopee.co
Amazon Shopping com.amazon.mShop.android.shopping america.devicenative.com amazon.com
Walmart com.walmart.mg america.devicenative.com walmart.com
Elektra mx.elektra.appelektra america.devicenative.com elektra.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
adidas com.adidas.app america.devicenative.com adidas.mx
Nike com.nike.omega america.devicenative.com nike.com
Hotels.com com.hcom.android america.devicenative.com de.hotels.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
Sam’s Club México air.SamsClubMexico america.devicenative.com sams.com
Temu com.einnovation.temu america.devicenative.com temu.com

Argentina (AR)

  • Device: motorola moto g54
  • Carrier: Claro
  • Ads: 7
  • JS code: 83416 chars
App Package Click URL Destination
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
Expedia com.expedia.bookings america.devicenative.com expedia.co
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
Booking.com com.booking america.devicenative.com booking.com
SHEIN com.zzkko america.devicenative.com arg.shein.com
Temu com.einnovation.temu america.devicenative.com temu.com
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com

Colombia (CO)

  • Device: motorola moto g54
  • Carrier: Claro
  • Ads: 7
  • JS code: 83416 chars
App Package Click URL Destination
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
SHEIN com.zzkko america.devicenative.com au.shein.com
Booking.com com.booking america.devicenative.com booking.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
Jumbo com.cencosud.co.clientes america.devicenative.com jumbocolombia.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.us
Temu com.einnovation.temu america.devicenative.com temu.com

Germany (DE)

  • Device: motorola razr 50 ultra
  • Carrier: Deutsche Telekom
  • Ads: 22
  • JS code: 83416 chars
App Package Click URL Destination
OTTO de.cellular.ottohybrid america.devicenative.com otto.com
bonprix de.bonprix america.devicenative.com bonprix.at
Shop Apotheke shop.shop_apotheke.com.shopapotheke america.devicenative.com shop-apotheke.com
G2A com.g2a.marketplace america.devicenative.com g2a.com
idealo de.idealo.android america.devicenative.com idealo.de
Ernsting's family de.ernstings america.devicenative.com ernstings-family.de
Expedia com.expedia.bookings america.devicenative.com expedia.co
adidas com.adidas.app america.devicenative.com adidas.co
heine de.heine.mobile america.devicenative.com heine.de
Joybuy-Don't Just Buy! com.joybuy.jdi america.devicenative.com joybuy.co
Temu com.einnovation.temu america.devicenative.com temu.com
REWE de.rewe.app.mobile america.devicenative.com rewe.de
MediaMarkt Belgique be.com.media.markt america.devicenative.com mediamarkt.be
Sephora com.sephora america.devicenative.com sephora.com
Sieh an! eu.wittgruppe.siehan america.devicenative.com sieh-an.de
H&M com.hm.goe america.devicenative.com hm.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
RTL+ de.rtli.tvnow america.devicenative.com plus.rtl.de
Witt eu.wittgruppe.witt america.devicenative.com witt-weiden.de
SteuerSparErklärung de.steuertipps.stapp america.devicenative.com steuertipps.de
Hotels.com com.hcom.android america.devicenative.com de.hotels.com
TikTok com.zhiliaoapp.musically america.devicenative.com seller.tiktok.com

United Kingdom (GB)

  • Device: motorola edge 50 ultra
  • Carrier: EE
  • Ads: 14
  • JS code: 83416 chars
App Package Click URL Destination
Burton Menswear London com.arcadia.burton america.devicenative.com burton.co
Pizza Hut India com.phdv.universal america.devicenative.com pizzahut.co
Trip.com ctrip.english america.devicenative.com trip.com
SHEIN com.zzkko america.devicenative.com arg.shein.com
Hotels.com com.hcom.android america.devicenative.com de.hotels.com
Debenhams com.debenhams.app america.devicenative.com debenhams.com
TikTok Shop Seller Center com.zhiliaoapp.musically america.devicenative.com seller.tiktok.com
New Look Fashion com.tigerspike.newlook america.devicenative.com newlook.com
adidas com.adidas.app america.devicenative.com adidas.co
Skyscanner Flights Hotels Cars net.skyscanner.android.main america.devicenative.com skyscanner.com
Zavvi com.thehutgroup.ecommerce.zavvi america.devicenative.com zavvi.com
Temu com.einnovation.temu america.devicenative.com temu.com
Dorothy Perkins com.arcadia.dp america.devicenative.com dorothyperkins.com
Wayfair com.wayfair.wayfair america.devicenative.com wayfair.co

France (FR)

  • Device: motorola edge 50 ultra
  • Carrier: Orange
  • Ads: 29
  • JS code: 83416 chars
App Package Click URL Destination
Voghion com.voghion.app america.devicenative.com de.voghion.com
PUMA com.puma.ecom.app america.devicenative.com eu.puma.com
Nike com.nike.omega america.devicenative.com nike.com
Alamo com.alamo.mobile america.devicenative.com alamo.com
Temu com.einnovation.temu america.devicenative.com temu.com
Sephora com.sephora america.devicenative.com sephora.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
Hotels.com com.hcom.android america.devicenative.com hotels.com
Lastminute Hotels & Flights com.lastminute.android america.devicenative.com lastminute.com
DHgate com.dhgate.buyermob america.devicenative.com dhgate.com
Loaded com.cdkeys america.devicenative.com cdkeys.com
trivago com.trivago america.devicenative.com trivago.com
Back Market com.backmarket america.devicenative.com backmarket.co
Expedia com.expedia.bookings america.devicenative.com expedia.co
adidas com.adidas.app america.devicenative.com adidas.be
TikTok Shop Seller Center com.zhiliaoapp.musically america.devicenative.com seller.tiktok.com
MyHeritage: Family Tree & DNA air.com.myheritage.mobile america.devicenative.com myheritage.com
Skyscanner Flights Hotels Cars net.skyscanner.android.main america.devicenative.com skyscanner.com
Enterprise Car Rental com.ehi.enterprise.android america.devicenative.com enterprise.com
G2A com.g2a.marketplace america.devicenative.com g2a.com
PrettyLittleThing com.poqstudio.app.platform.plt america.devicenative.com prettylittlething.fr
Etsy com.etsy.android america.devicenative.com etsy.com
Europcar Mobility App com.europcar.europcarmobility america.devicenative.com europcar.com
JD Sports com.jd.jdsportsusa america.devicenative.com jdsports.fr
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
bonprix de.bonprix america.devicenative.com bonprix.at
Booking.com com.booking america.devicenative.com booking.com
Trip.com ctrip.english america.devicenative.com trip.com

Italy (IT)

  • Device: motorola edge 50 ultra
  • Carrier: TIM
  • Ads: 23
  • JS code: 83416 chars
App Package Click URL Destination
Trainline com.thetrainline america.devicenative.com thetrainline.com
Europcar Mobility App com.europcar.europcarmobility america.devicenative.com europcar.com
Trip.com ctrip.english america.devicenative.com trip.com
MyHeritage air.com.myheritage.mobile america.devicenative.com myheritage.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
HRS com.hrs.b2c.android america.devicenative.com hrs.com
TicketOne it.ticketone.mobile.app.Android america.devicenative.com ticketone.it
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
Privé by Zalando de.zalando.prive america.devicenative.com zalando-prive.es
TikTok com.zhiliaoapp.musically america.devicenative.com seller.tiktok.com
PUMA com.puma.ecom.app america.devicenative.com eu.puma.com
Skyscanner Flights Hotels Cars net.skyscanner.android.main america.devicenative.com skyscanner.com
Booking.com com.booking america.devicenative.com booking.com
DAZN com.dazn america.devicenative.com dazn.com
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
Temu com.einnovation.temu america.devicenative.com temu.com
bonprix de.bonprix america.devicenative.com bonprix.it
Hotels.com com.hcom.android america.devicenative.com hotels.com
adidas com.adidas.app america.devicenative.com adidas.be
G2A com.g2a.marketplace america.devicenative.com g2a.com
Italo: Italian Highspeed Train it.italotreno america.devicenative.com italotreno.com
Expedia com.expedia.bookings america.devicenative.com expedia.co
SEPHORA com.sephora.digital america.devicenative.com sephora.it

Spain (ES)

  • Device: motorola edge 50 ultra
  • Carrier: Movistar
  • Ads: 23
  • JS code: 83416 chars
App Package Click URL Destination
adidas com.adidas.app america.devicenative.com adidas.be
bonprix es.bonprix.bonprix america.devicenative.com bonprix.at
Habitissimo para profesionales com.habitissimo.app.pro america.devicenative.com habitissimo.es
Expedia com.expedia.bookings america.devicenative.com expedia.co
PUMA com.puma.ecom.app america.devicenative.com eu.puma.com
Temu com.einnovation.temu america.devicenative.com temu.com
La Redoute com.ocito.laredoute america.devicenative.com laredoute.com
Booking.com com.booking america.devicenative.com booking.com
Trip.com ctrip.english america.devicenative.com trip.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
TikTok Shop Seller Center com.zhiliaoapp.musically america.devicenative.com seller.tiktok.com
Enterprise Car Rental com.ehi.enterprise.android america.devicenative.com enterprise.com
Alamo com.alamo.mobile america.devicenative.com alamo.com
G2A com.g2a.marketplace america.devicenative.com g2a.com
Skyscanner Flights Hotels Cars net.skyscanner.android.main america.devicenative.com skyscanner.com
Fubo tv.fubo.mobile america.devicenative.com fubo.tv
DHgate com.dhgate.buyermob america.devicenative.com dhgate.com
MyHeritage: Family Tree & DNA air.com.myheritage.mobile america.devicenative.com myheritage.com
DAZN com.dazn america.devicenative.com dazn.com
Sephora fr.sephora.sephorafrance america.devicenative.com sephora.com
Hotels.com com.hcom.android america.devicenative.com hotels.com
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.us

Netherlands (NL)

  • Device: motorola edge 50 ultra
  • Carrier: KPN
  • Ads: 11
  • JS code: 83416 chars
App Package Click URL Destination
Skyscanner Flights Hotels Cars net.skyscanner.android.main america.devicenative.com skyscanner.com
Go City com.gocitypass america.devicenative.com gocity.com
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
Temu com.einnovation.temu america.devicenative.com temu.com
ANWB Onderweg & Wegenwacht com.themobilecompany.Onderweg america.devicenative.com anwb.nl
Fiverr com.fiverr.fiverr america.devicenative.com fiverr.com
Alibaba.com com.alibaba.intl.android.apps.poseidon america.devicenative.com alibaba.com
PUMA com.puma.ecom.app america.devicenative.com eu.puma.com
Coolblue eu.coolblue.shop america.devicenative.com coolblue.be
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
Booking.com com.booking america.devicenative.com booking.com

Poland (PL)

  • Device: motorola moto g84
  • Carrier: Play
  • Ads: 13
  • JS code: 83416 chars
App Package Click URL Destination
Temu com.einnovation.temu america.devicenative.com temu.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
adidas com.adidas.app america.devicenative.com adidas.com
G2A com.g2a.marketplace america.devicenative.com g2a.com
ERLI pl.erli.app america.devicenative.com erli.pl
eobuwie pl.eobuwie.eobuwieapp america.devicenative.com eobuwie.com.pl
Tripadvisor com.tripadvisor.tripadvisor america.devicenative.com tripadvisor.com
trivago com.trivago america.devicenative.com trivago.com
bonprix pl.bonprix america.devicenative.com bonprix.at
Ceneo pl.ceneo america.devicenative.com ceneo.pl
Skyscanner Flights Hotels Cars net.skyscanner.android.main america.devicenative.com skyscanner.com
Allegro pl.allegro america.devicenative.com allegro.pl
Sephora com.sephora america.devicenative.com sephora.com

India (IN)

  • Device: motorola edge 50 ultra
  • Carrier: Jio
  • Ads: 34
  • JS code: 83416 chars
App Package Click URL Destination
Samsung Shop com.samsung.ecomm.global.in asia.devicenative.com samsung.co
Samsung Shop com.samsung.ecomm.global.in asia.devicenative.com samsung.co
Rigi TV com.litvibes asia.devicenative.com rigitv.club
Rigi TV com.litvibes asia.devicenative.com rigitv.club
Uni cards.uni.app.credit asia.devicenative.com uni.cards
ICICI Direct com.icicidirect.idirectsuper asia.devicenative.com icicidirect.com
Angel One com.msf.angelmobile asia.devicenative.com angelbroking.com
WAVES com.prasarbharati.bharat.ott asia.devicenative.com wavespb.com
QuickTV in.mohalla.quicktv asia.devicenative.com quicktv.co
ManyJobs com.manyjobs asia.devicenative.com manyjobs.com
ManyJobs com.manyjobs asia.devicenative.com manyjobs.com
Junglee Ludo com.junglee.ludo.onlinegame asia.devicenative.com junglee-ludo.com
Junglee Ludo com.junglee.ludo.onlinegame asia.devicenative.com junglee-ludo.com
Anytime Rupee com.anytimerupee asia.devicenative.com anytimerupee.com
Anytime Rupee com.anytimerupee asia.devicenative.com anytimerupee.com
toing in.swiggy.android.toing asia.devicenative.com toingit.com
toing in.swiggy.android.toing asia.devicenative.com toingit.com
WAVES com.prasarbharati.android asia.devicenative.com wavespb.com
Axis Max Life Insurance com.maxlifeinsurance.www.twa asia.devicenative.com axismaxlife.com
Amazon India in.amazon.mShop.android.shopping asia.devicenative.com amazon.in
Shopsy Shopping App com.flipkart.shopsy asia.devicenative.com shopsy.in
PUMA com.puma.ecom.app asia.devicenative.com eu.puma.com
EaseMyTrip com.easemytrip.android asia.devicenative.com easemytrip.com
MX Player com.mxtech.videoplayer.ad asia.devicenative.com mxplayer.in
Nykaa Fashion com.fsn.nds asia.devicenative.com nykaafashion.com
Flipkart Online Shopping App com.flipkart.android asia.devicenative.com flipkart.com
Skyscanner Flights Hotels Cars net.skyscanner.android.main asia.devicenative.com skyscanner.com
Myntra com.myntra.android asia.devicenative.com myntra.com
Bewakoof com.bewakoof.bewakoof asia.devicenative.com bewakoof.com
MakeMyTrip com.makemytrip asia.devicenative.com makemytrip.com
Fiverr com.fiverr.fiverr asia.devicenative.com de.fiverr.com
Tripadvisor com.tripadvisor.tripadvisor asia.devicenative.com tripadvisor.com
Nykaa com.fsn.nykaa asia.devicenative.com nykaa.com
AJIO com.ril.ajio asia.devicenative.com ajio.com

Default (no country) (DEFAULT)

  • Device: motorola razr 60 ultra
  • Carrier: none
  • Ads: 22
  • JS code: 83416 chars
App Package Click URL Destination
OTTO de.cellular.ottohybrid america.devicenative.com otto.com
bonprix de.bonprix america.devicenative.com bonprix.at
Shop Apotheke shop.shop_apotheke.com.shopapotheke america.devicenative.com shop-apotheke.com
G2A com.g2a.marketplace america.devicenative.com g2a.com
idealo de.idealo.android america.devicenative.com idealo.de
Ernsting's family de.ernstings america.devicenative.com ernstings-family.de
Expedia com.expedia.bookings america.devicenative.com expedia.co
adidas com.adidas.app america.devicenative.com adidas.co
heine de.heine.mobile america.devicenative.com heine.de
Joybuy-Don't Just Buy! com.joybuy.jdi america.devicenative.com joybuy.co
Temu com.einnovation.temu america.devicenative.com temu.com
REWE de.rewe.app.mobile america.devicenative.com rewe.de
MediaMarkt Belgique be.com.media.markt america.devicenative.com mediamarkt.be
Sephora com.sephora america.devicenative.com sephora.com
Sieh an! eu.wittgruppe.siehan america.devicenative.com sieh-an.de
H&M com.hm.goe america.devicenative.com hm.com
AliExpress com.alibaba.aliexpresshd america.devicenative.com aliexpress.com
RTL+ de.rtli.tvnow america.devicenative.com plus.rtl.de
Witt eu.wittgruppe.witt america.devicenative.com witt-weiden.de
SteuerSparErklärung de.steuertipps.stapp america.devicenative.com steuertipps.de
Hotels.com com.hcom.android america.devicenative.com de.hotels.com
TikTok com.zhiliaoapp.musically america.devicenative.com seller.tiktok.com

Infrastructure

Click tracking domains:

  • america.devicenative.com
  • asia.devicenative.com

Impression tracking domains:

  • imptrk.siteplug.com

Investigation Results — Smart Feed (Motorola Mobility LLC)

APK: com.motorola.smartfeed v1.03.0095 (build 361030095)
Claim: App quietly inserts affiliate links into traffic / intercepts app launches from launcher
Date: 2025-05-27
Verdict: CONFIRMED — affiliate link injection AND launcher app-launch interception via embedded DeviceNative SDK


Executive Summary

The Smart Feed app embeds a third-party monetization SDK called DeviceNative (self-identified as "DeviceNativeAds" in logs, SDK version moto-v1.5.15). This SDK:

  1. Injects DNA ad results into the Motorola launcher's app suggestions — appearing as normal app icons in the app tray/search, but routing clicks through affiliate tracking
  2. Intercepts app launches from the launcher — when a user clicks a DNA-injected "suggestion" that looks like a regular app, the click is routed through Smart Feed's OnboardActivity which detects it as a DNA result and fires the affiliate redirect system instead of launching the app normally
  3. Opens the browser with affiliate tracking URLs instead of launching the target app, following redirect chains (up to 5 hops) before landing on the final destination (e.g., amazon.com with affiliate code)
  4. Replaces Google Play Store links with affiliate tracking URLs when the server enables the rGP flag
  5. Downloads and executes arbitrary JavaScript from DeviceNative servers to control ad selection logic
  6. Collects extensive device/behavioral data including installed apps, app usage patterns, and device fingerprints
  7. Fires View-Through Attribution (VTA) tracking just from ads being displayed — no click required
  8. All behavior is remotely controlled by the DeviceNative server (api.devicenative.com/v2/) — flags can be toggled server-side without app updates

Reddit Claim Validation

The Reddit poster's description is accurate:

"when I tried to open the Amazon app, it would instead open the browser and send me to some sketchy looking url, which then redirects to amazon.com with an affiliate code"

This happens because Smart Feed is a launcher plugin (ALL_APPS_OVERLAY_SERVICE) that provides app suggestions to the Motorola launcher. The DNA SDK injects ad results disguised as "app_suggestion" type with "call_click_action": true. When clicked, the launcher calls back into Smart Feed which detects the DNA source and routes through the affiliate redirect system instead of launching the app.

"it intercepts the action and redirects you to their affiliate link"

Confirmed. The OnboardActivity.m6822e() method explicitly checks if ("DNA".equalsIgnoreCase(content.m6839i())) and routes DNA results through C4345s.m7243e()C4812h.m7677e()C4622h.m7526D(), which follows the clickUrl redirect chain and opens the browser with the affiliate URL.


Detailed Technical Findings

0. Launcher App-Launch Interception (THE CORE ATTACK)

This is the mechanism that validates the Reddit poster's claim. Smart Feed is not just a feed app — it's a launcher plugin that provides app suggestions directly into the Motorola launcher's app tray.

The Attack Chain

Step 1: Smart Feed registers as a launcher overlay plugin

The manifest declares SmartFeedOverlayService with intent filter com.motorola.launcher3.ALL_APPS_OVERLAY_SERVICE and exports a ContentAPIInterface provider at authority com.motorola.smartfeed.launcher_smart_plugins. The Motorola launcher calls this provider to get app suggestions.

Step 2: DNA SDK injects ad results into launcher suggestions

When the launcher calls ContentAPIInterface.call("get_plugin_contents", section="app_picks_suggestions"), the provider calls C4354w0.m7262g() which returns DNA SDK ad results formatted as launcher-compatible JSON.

File: com/motorola/smartstreamsdk/utils/C4354w0.java, method m7260e, lines 144-167:

// DNA ad result is converted to look like a normal app suggestion
jSONObject.put("click_action", m7264i(c4806b));     // DNA click action payload
jSONObject.put("type", "app_suggestion");             // Disguised as app suggestion
jSONObject.put("call_click_action", true);            // Forces click through Smart Feed
jSONObject.put("package_name", str);                  // Uses real package name (e.g., com.amazon.mShop)
jSONObject.put("component_name", ...);                // Uses real component name

The DNA result carries the real app's package name and icon, making it visually indistinguishable from a legitimate app suggestion in the launcher.

Step 3: User clicks what looks like a normal app icon

The launcher sees "call_click_action": true and calls ContentAPIInterface.call("handle_click") with the click_action JSON, which launches OnboardActivity.

Step 4: OnboardActivity detects DNA source and hijacks the click

File: com/motorola/smartstreamsdk/activities/OnboardActivity.java, method m6822e, lines 192-200:

if ("DNA".equalsIgnoreCase(content.m6839i())) {
    C4345s.m7243e(context, content);                   // Route through DNA click handler
    C4361v.m7298w(content, "dna_suggestion", "suggestion");
    finish();                                           // Done — never launches the actual app normally
}

Step 5: DNA click handler opens browser with affiliate URL

File: com/motorola/smartstreamsdk/utils/C4345s.java, method m7243e, lines 698-703:

// Deserializes the full DNA ad result (with clickUrl, destinationUrl, etc.)
C4812h.m7674g(context).m7677e(
    (C4806b) new Gson().fromJson(content.m6831a().get("DNAResultItem"), C4806b.class),
    new C4337o()
);

File: p314h4/C4812h.java, method m7677e, lines 498-523:

// If app is NOT installed, route through affiliate redirect system
if (!c4806b.f16280f) {
    boolean z5 = !c4806b.f16270E;  // notPlayStore flag
    r15.f16797i.execute(new Runnable() {
        public final void run() throws Throwable {
            c4622h4.m7526D(context4,
                c4806b2.f16286w,    // clickUrl (affiliate tracking URL)
                c4806b2.f16287x,    // destinationUrl (e.g., amazon.com)
                str21,              // adId
                c4806b2.f16282s,    // appName
                c4806b2.f16276b,    // packageName
                str18, str19, string, str20,
                true,               // doRoute = true → OPENS BROWSER
                z5,                 // notPlayStore
                interfaceC4813i);
        }
    });
}

Step 6: m7526D follows redirect chain and opens browser

The method m7526D (documented in section 2c below) manually follows HTTP 301/302/303/307/308 redirects up to 5 times from the clickUrl, which is a DeviceNative tracking URL. The final URL (e.g., amazon.com?tag=AFFILIATE_CODE) is opened via ACTION_VIEW intent in the browser.

Result

The user clicks what appears to be the Amazon app icon in their launcher → browser opens → navigates through devicenative.com tracking redirects → lands on amazon.com with an affiliate code. Exactly as the Reddit poster described.

View-Through Attribution (VTA) — Tracking Without Clicks

File: p054F6/RunnableC0892t.java, lines 188-210:

Even when DNA ad results are just displayed (not clicked), if viewThrough > 0 and dna_vta_enabled is true, the SDK fires m7526D with the affiliate clickUrl in the background:

if (c4806b.f16269D > 0 && str != null && !str.isEmpty() 
    && c4911o.f16791c.m3892l("dna_vta_enabled", z5) 
    && !AbstractC5729e.m8936T(c4911o.f16789a, c4806b.f16275a)) {
    // Fire affiliate tracking URL just from the ad being SHOWN
    c4911o.f16794f.m7526D(c4911o.f16789a, c4806b.f16286w, ...);
}

1. DeviceNative SDK Integration

Manifest Components:

  • com.devicenative.dna.DNADataOrchestrator — background Service, app lifecycle monitor
  • com.devicenative.dna.utils.DNAConfigBuilder — runs in separate process (:dna_config_builder), creates a WebView for config loading

Core Packages (obfuscated):

Package Purpose
p314h4 (h4) Data models — C4806b (DNAResultItem)
p325i4 (i4) Core engine — C4911o (orchestrator), C4906j (JS engine), C4909m (sync)
p334j4 (j4) Broadcast receivers
p344k4 (k4) Data sources — app list, shortcuts, usage
p364m4 (m4) Database/persistence — ads table
p375n4 (n4) Stats sync — C5894c
p293f3 (f3) Click handling — C4622h
p015B4 (B4) Config sync — RunnableC0249e

2. Affiliate Link Injection Mechanism

2a. Play Store URL Replacement (THE SMOKING GUN)

File: p293f3/C4622h.java, method m7520J, lines 164-248

When a user clicks a link in the feed that points to the Google Play Store:

// Check if URL points to Play Store
boolean z4 = str4.startsWith("market://") || str4.contains("play.google.com");

// If server has enabled replacement AND provided a replacement URL template:
if (c3010uM3872n.m3892l("dna_replace_google_play", false) 
    && (strM3901v = c3010uM3872n.m3901v("dna_play_store_replacement_url", "")) != null 
    && strM3901v.contains("{packageName}") && z4) {
    
    // Extract package name from original URL
    String queryParameter = uri.getQueryParameter("id");
    
    // Replace with affiliate URL
    Uri.Builder builder = Uri.parse(strM3901v.replace("{packageName}", queryParameter)).buildUpon();
    
    // Copy original query params
    for (String param : uri.getQueryParameterNames()) {
        builder.appendQueryParameter(param, uri.getQueryParameter(param));
    }
    
    str4 = builder.build().toString(); // REPLACED URL
    AbstractC3664b.m5713L("Successfully remapped Google Play URL: " + str4);
}

The replacement URL template is downloaded from the DeviceNative server (JSON key "gpU") and stored in SharedPreferences as dna_play_store_replacement_url. The format is something like https://tracking.example.com/click?app={packageName} — any affiliate tracking URL with a {packageName} placeholder.

2b. Click Tracking URL System

File: p293f3/C4622h.java, method m7523w

Every ad click URL is processed through a template system:

str.replace("{adId}", advertisingId)
   .replace("{packageName}", appPackageName)
   .replace("{clickId}", uniqueClickId)
   .replace("{productSource}", source)

This inserts device-specific tracking parameters into the click URL before it's opened.

2c. Redirect Chain Following

File: p293f3/C4622h.java, method m7526D, lines 690-1040

When an ad is clicked with doRoute=true, the SDK:

  1. Opens an HTTP connection to the click tracking URL
  2. Manually follows 301/302/303/307/308 redirects (up to 5 hops)
  3. Tracks the final destination URL
  4. Opens the final URL in browser/Play Store
  5. Caches the result for future clicks

This manual redirect following ensures affiliate attribution is captured through the entire redirect chain.

3. Remote Code Execution

File: p015B4/RunnableC0249e.java, line 823 and p325i4/C4906j.java

The DeviceNative server sends JavaScript code (JSON key "c") that gets stored as dna_ad_selection_code and executed in a QuickJS JavaScript engine. This script controls:

  • Which ads to show
  • Ad ranking and filtering
  • Result format

The script has access to:

  • Full list of installed apps
  • App usage data
  • Device model, carrier, Android version
  • Advertising ID (if enabled)
  • Current search query (if in search context)

4. Server-Controlled Configuration

Server: https://api.devicenative.com/v2/

The server response controls all behavior via these flags:

Server Key Pref Key Purpose
rGP dna_replace_google_play Enable Play Store URL replacement with affiliate URLs
gpU dna_play_store_replacement_url Affiliate URL template ({packageName} placeholder)
aDL dna_auto_deep_link_enabled Deep-link directly to apps
aId dna_adid_usage_enabled Use advertising ID in tracking
cpi dna_install_ads_enabled Enable cost-per-install ads
web dna_web_ads_enabled Enable web ads
org dna_organic_ads_enabled Enable organic-looking ads
orgO dna_organic_ads_only Show only organic-looking ads
orgB dna_paid_organic_boost_enabled Boost paid results to look organic
sad dna_config_search_ads_disabled Disable search ads
rad dna_config_rec_ads_disabled Disable recommendation ads
vta dna_vta_enabled View-through attribution
uD dna_app_usage_disabled Disable usage tracking
c dna_ad_selection_code JavaScript code for ad selection

5. Data Collection

The SDK collects and reports:

Device Information:

  • Device key (unique identifier)
  • Device model, Android version
  • Carrier info
  • CPU count, max memory
  • Advertising ID (when enabled)

Behavioral Data:

  • Full list of installed apps (via QUERY_ALL_PACKAGES)
  • App usage statistics (via PACKAGE_USAGE_STATS)
  • App install/uninstall events (via LauncherApps callbacks)
  • Shortcuts data

Click/Engagement:

  • Click URLs with session IDs
  • Redirect chain results
  • Impression tracking
  • View-through attribution

6. Ad Data Model

Database table ads:

CREATE TABLE IF NOT EXISTS ads (
    _id TEXT PRIMARY KEY,
    appName TEXT,
    packageName TEXT,
    type TEXT,
    creativeSource TEXT,
    viewThrough INTEGER DEFAULT 0,
    notPlayStore INTEGER DEFAULT 0,     -- Flag: destination is NOT Play Store
    title TEXT,
    description TEXT,
    iconUrl TEXT,
    clickUrl TEXT,                       -- Tracking/redirect URL
    destinationUrl TEXT,                 -- Final destination (may be affiliate)
    impressionUrl TEXT,                  -- Impression tracking pixel
    targetingMetadata TEXT DEFAULT '{}'
)

The notPlayStore flag explicitly marks when a result destination is outside the Play Store — indicating direct affiliate/web links.

7. Other SDKs Present

  • Taboola — content recommendations (api.taboola.com/2.0/json/). URLs from Taboola appear to be passed through without modification.
  • Firebase — messaging, remote config, analytics (analytics collection disabled by default in manifest)
  • Google Play Services — auth, measurement

Permissions of Concern

Permission Risk
QUERY_ALL_PACKAGES Enumerate all installed apps
PACKAGE_USAGE_STATS Track which apps are used and when
SYSTEM_ALERT_WINDOW Draw overlays over other apps
START_ACTIVITIES_FROM_BACKGROUND Launch activities without user interaction
ACCESS_FINE_LOCATION Precise location tracking
READ_CONTACTS Contact list access
READ_CALENDAR Calendar data access
AD_ID / ACCESS_ADSERVICES_AD_ID Advertising identifier

Flow Diagram

Launcher Interception Flow (Reddit Claim)

┌─────────────────────────────────────────────────────────────┐
│                   Motorola Launcher                          │
│                                                             │
│  Calls ContentAPIInterface.get_plugin_contents()            │
│  to get "app_picks_suggestions"                             │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│              Smart Feed ContentProvider                       │
│                                                             │
│  C4354w0.m7262g() returns DNA results as:                   │
│    type: "app_suggestion"                                   │
│    call_click_action: true                                  │
│    package_name: "com.amazon.mShop" (real app name!)        │
│    click_action: {DNA payload with affiliate URLs}          │
└────────────────────────┬────────────────────────────────────┘
                         │
                    Launcher displays DNA result
                    as normal app icon in app tray
                         │
                    User taps "Amazon" icon
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│  Launcher calls handle_click → OnboardActivity              │
│                                                             │
│  if ("DNA".equals(content.source)):                         │
│    C4345s.m7243e() → C4812h.m7677e()                       │
│    → C4622h.m7526D(clickUrl, destinationUrl, doRoute=true)  │
│    → finish()   // never launches the actual app!           │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│              Affiliate Redirect Chain                        │
│                                                             │
│  HTTP GET clickUrl (devicenative.com tracking URL)          │
│  → follow 301/302 redirects (up to 5 hops)                  │
│  → final URL: amazon.com?tag=AFFILIATE_CODE                 │
│  → ACTION_VIEW intent → browser opens                       │
│                                                             │
│  User sees: browser with affiliate-tagged Amazon URL        │
│  User expected: Amazon app to launch                        │
└─────────────────────────────────────────────────────────────┘

Full System Flow

┌─────────────────────────────────────────────────────────────┐
│                    DeviceNative Server                       │
│              api.devicenative.com/v2/                        │
│                                                             │
│  Sends: ad data, JS code, config flags, replacement URLs    │
│  Receives: installed apps, usage stats, click events        │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                  Smart Feed App                              │
│                                                             │
│  1. DNADataOrchestrator starts on app launch                │
│  2. Syncs config from server (gets flags, JS code, ads)     │
│  3. Collects installed apps + usage stats                   │
│  4. Reports device data + usage to server                   │
│  5. QuickJS engine runs server-provided JS to select ads    │
│  6. Provides DNA results to launcher as "app_suggestions"   │
│  7. Feed also displays mix of organic + sponsored results   │
└────────────────────────┬────────────────────────────────────┘
                         │
                    User clicks (in launcher OR feed)
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                   Click Handler                              │
│                                                             │
│  DNA results → affiliate redirect system                    │
│                                                             │
│  IF dna_replace_google_play == true:                        │
│    Play Store URLs → affiliate tracking URL                  │
│                                                             │
│  clickUrl template filled with:                             │
│    {adId}, {packageName}, {clickId}, {productSource}        │
│                                                             │
│  HTTP GET to clickUrl → follow up to 5 redirects            │
│  → open final destination in browser/Play Store             │
└─────────────────────────────────────────────────────────────┘

Conclusion

The Reddit poster's claim is fully confirmed. The Smart Feed app contains a DeviceNative monetization SDK that:

  1. Injects affiliate-tracked ad results into the Motorola launcher's app tray disguised as normal "app_suggestion" items with real package names and icons — visually indistinguishable from legitimate app suggestions
  2. Intercepts app launches from the launcher — when a user taps what looks like a normal app icon, the click is routed through Smart Feed's OnboardActivity which detects the DNA source and fires the affiliate redirect system instead of launching the app
  3. Opens the browser with affiliate tracking URLs following redirect chains through devicenative.com before landing on the destination (e.g., amazon.com with affiliate code) — exactly matching the Reddit poster's description
  4. Replaces Google Play Store links with affiliate tracking URLs (controlled by remote server flag rGP)
  5. Executes remotely-downloaded JavaScript to control ad selection — allowing the server to change which apps get targeted without app updates
  6. Fires View-Through Attribution (VTA) tracking in the background just from ads being displayed — no click required
  7. Collects extensive user data (installed apps, usage patterns, device fingerprints) and reports to DeviceNative servers

The most concerning aspects:

  • Pre-installed system app on Motorola devices with elevated permissions, difficult to uninstall
  • All behavior remotely controlled by DeviceNative servers — can be enabled/disabled/changed without app updates
  • dna_paid_organic_boost_enabled flag explicitly boosts paid results to appear organic — deliberately deceptive
  • Uses real app package names and icons to make affiliate suggestions indistinguishable from real app entries in the launcher
  • call_click_action: true forces clicks through Smart Feed instead of normal app launch, enabling the interception

FAQ — Scope and Behavior

Q: Does this only work with the Motorola Launcher?

Yes. The interception mechanism relies on Smart Feed being registered as a launcher plugin via:

  • SmartFeedOverlayService (intent filter: com.motorola.launcher3.ALL_APPS_OVERLAY_SERVICE)
  • ContentAPIInterface provider (authority: com.motorola.smartfeed.launcher_smart_plugins)

The Motorola launcher calls this provider to get app suggestions. On a third-party launcher (Nova, Lawnchair, etc.), this provider is never called and the DNA suggestions never appear.

Q: Where exactly do the DNA suggestions appear in the launcher?

They appear in two places:

  1. "Suggested Apps" / App Picks section in the app tray — plugin ID "2", section "app_picks_suggestions", UI type "transparent_card". These are displayed alongside real app icons in the launcher's recommendations area. DNA results have badge_res: "downloadicon" (a download icon badge) and optionally new_badge_res: "blue_dot". They are NOT replacing user's pinned/installed app icons — they appear in the dynamic suggestion area.

  2. Search results — when searching in the launcher, DNA results from "global search ads" placement are mixed into results alongside real local app matches.

Q: What happens if the user already has the Amazon app installed?

The DNA SDK checks installation status via launcherApps.isPackageEnabled() (method m7671b / field f16280f).

In C4812h.m7677e() (the click handler), line 504:

if (!c4806b.f16280f) {
    // App NOT installed → affiliate redirect via m7526D(doRoute=true)
    // → browser opens with affiliate tracking URL
}
// If app IS installed → this block is skipped
// → only background tracking fires (m7522u), no browser opens
// → but also NO app launch happens!

Three scenarios:

App installed? What happens on click
No Browser opens → devicenative.com redirect chain → lands on amazon.com with affiliate tag. This matches the Reddit poster's description.
Yes Background tracking fires silently. The actual app is not launched (the click handler doesn't have a "launch installed app" fallback for this path). OnboardActivity finishes and nothing visible happens.
Yes, but DNA result has notPlayStore=true The notPlayStore flag is passed to m7526D but doesn't change whether the redirect happens — only f16280f controls that. If the app IS detected as installed, the redirect is still skipped.

Bottom line: If the user has the Amazon Shopping app installed AND it's detected by the isPackageEnabled check, the affiliate redirect does NOT happen — but the suggestion still appears, and clicking it does nothing useful (no app launch, no browser). If the app is NOT installed (or the package name doesn't match), the browser redirect fires.

The Reddit poster likely did not have the specific Amazon app package installed (Amazon has multiple apps: com.amazon.mShop.android.shopping, com.amazon.avod, etc.), or the DNA result targeted a package that wasn't on their device.


Live API Results — Actual Ad Catalog

Using the hardcoded API key (2d6e21f3-1213-4aac-9cd9-06e8ec91fa4e from C4317e.m7209a()), we successfully queried the DeviceNative API and retrieved the live ad catalog.

API Endpoint: POST https://api.devicenative.com/v2/sync
Click tracking domain: america.devicenative.com
Impression tracking domain: imptrk.siteplug.com

Config Flags (live from server)

Flag Value Meaning
rGP false Play Store URL replacement currently DISABLED for this key
vta true View-through attribution ACTIVE
org true Organic-looking ads ENABLED
aDL true Auto deep link ENABLED
aId true Advertising ID usage ENABLED
sad true Search ads ENABLED
rad true Recommendation ads ENABLED
cpi false Cost-per-install ads DISABLED

Targeted Apps & Domains (22 entries as of 2025-05-27)

App Package Affiliate Destination
OTTO de.cellular.ottohybrid otto.com
bonprix de.bonprix bonprix.at
Shop Apotheke shop.shop_apotheke.com.shopapotheke shop-apotheke.com
G2A com.g2a.marketplace g2a.com
idealo de.idealo.android idealo.de
Ernsting's family de.ernstings ernstings-family.de
Expedia com.expedia.bookings expedia.co
adidas com.adidas.app adidas.co
heine de.heine.mobile heine.de
Joybuy com.joybuy.jdi joybuy.co
Temu com.einnovation.temu temu.com
REWE de.rewe.app.mobile rewe.de
MediaMarkt be.com.media.markt mediamarkt.be
Sephora com.sephora sephora.com
Sieh an! eu.wittgruppe.siehan sieh-an.de
H&M com.hm.goe hm.com
AliExpress com.alibaba.aliexpresshd aliexpress.com
RTL+ de.rtli.tvnow plus.rtl.de
Witt eu.wittgruppe.witt witt-weiden.de
SteuerSparErklärung de.steuertipps.stapp steuertipps.de
Hotels.com com.hcom.android de.hotels.com
TikTok com.zhiliaoapp.musically seller.tiktok.com

Note: This catalog is dynamic and varies by device key, region, and carrier. The default API key appears to return German/European market ads. Different Motorola device regions likely get different ad catalogs. Amazon was not in this specific response but may appear for US-market devices.

Ad Selection JavaScript (83,416 chars)

The server also returned a JavaScript file (dna_ad_selection_code.js) that runs in QuickJS on the device. Key behaviors:

  • Frequency caps: 12 ads/day, 60 ads/week
  • Click recency priority for "suggested apps" placement
  • Install recency priority window: 300 seconds
  • "Soft boost" algorithm that promotes paid results into organic positions
  • Important app scoring (Chrome: 0.4, Settings: 0.4, Play Store: 0.4)

Reproduction Script

fetch_dna_ads.ts — Deno script to query the API:

deno run --allow-net --allow-write --allow-read fetch_dna_ads.ts

Outputs: dna_raw_response.json, dna_ads_catalog.json, dna_ads_summary.csv, dna_ad_selection_code.js

/**
* DeviceNative DNA — Multi-Market Ad Catalog Sweep
*
* Queries the DeviceNative API with different country/carrier/device
* combinations to map the full affiliate ad catalog across markets.
*
* Usage: deno run --allow-net --allow-write --allow-read sweep_markets.ts
*/
const BASE_URL = "https://api.devicenative.com/v2/sync";
const API_KEY = "2d6e21f3-1213-4aac-9cd9-06e8ec91fa4e";
const SDK_VERSION = "moto-v1.5.15";
const PACKAGE_NAME = "com.motorola.smartfeed";
interface MarketConfig {
country: string;
label: string;
carrier: string;
deviceModel: string;
androidVersion: string;
}
const MARKETS: MarketConfig[] = [
// Americas
{ country: "US", label: "United States", carrier: "T-Mobile", deviceModel: "motorola razr 50 ultra", androidVersion: "15" },
{ country: "US", label: "United States (Verizon)", carrier: "Verizon", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "US", label: "United States (AT&T)", carrier: "AT&T", deviceModel: "motorola razr 60 ultra", androidVersion: "15" },
{ country: "CA", label: "Canada", carrier: "Rogers", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "BR", label: "Brazil", carrier: "Claro", deviceModel: "motorola moto g84", androidVersion: "14" },
{ country: "MX", label: "Mexico", carrier: "Telcel", deviceModel: "motorola moto g84", androidVersion: "14" },
{ country: "AR", label: "Argentina", carrier: "Claro", deviceModel: "motorola moto g54", androidVersion: "14" },
{ country: "CO", label: "Colombia", carrier: "Claro", deviceModel: "motorola moto g54", androidVersion: "13" },
// Europe
{ country: "DE", label: "Germany", carrier: "Deutsche Telekom", deviceModel: "motorola razr 50 ultra", androidVersion: "15" },
{ country: "GB", label: "United Kingdom", carrier: "EE", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "FR", label: "France", carrier: "Orange", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "IT", label: "Italy", carrier: "TIM", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "ES", label: "Spain", carrier: "Movistar", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "NL", label: "Netherlands", carrier: "KPN", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "PL", label: "Poland", carrier: "Play", deviceModel: "motorola moto g84", androidVersion: "14" },
{ country: "AT", label: "Austria", carrier: "A1", deviceModel: "motorola razr 50 ultra", androidVersion: "15" },
{ country: "SE", label: "Sweden", carrier: "Telia", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "BE", label: "Belgium", carrier: "Proximus", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "PT", label: "Portugal", carrier: "MEO", deviceModel: "motorola moto g84", androidVersion: "14" },
{ country: "RO", label: "Romania", carrier: "Orange", deviceModel: "motorola moto g54", androidVersion: "13" },
// Asia-Pacific
{ country: "IN", label: "India", carrier: "Jio", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "JP", label: "Japan", carrier: "SoftBank", deviceModel: "motorola razr 50 ultra", androidVersion: "14" },
{ country: "AU", label: "Australia", carrier: "Telstra", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "KR", label: "South Korea", carrier: "SK Telecom", deviceModel: "motorola razr 50 ultra", androidVersion: "14" },
{ country: "ID", label: "Indonesia", carrier: "Telkomsel", deviceModel: "motorola moto g84", androidVersion: "14" },
{ country: "TH", label: "Thailand", carrier: "AIS", deviceModel: "motorola moto g54", androidVersion: "13" },
{ country: "MY", label: "Malaysia", carrier: "Maxis", deviceModel: "motorola moto g54", androidVersion: "13" },
// Middle East / Africa
{ country: "SA", label: "Saudi Arabia", carrier: "STC", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "AE", label: "UAE", carrier: "du", deviceModel: "motorola edge 50 ultra", androidVersion: "14" },
{ country: "ZA", label: "South Africa", carrier: "Vodacom", deviceModel: "motorola moto g84", androidVersion: "14" },
{ country: "NG", label: "Nigeria", carrier: "MTN", deviceModel: "motorola moto g54", androidVersion: "13" },
// No country override (default)
{ country: "", label: "Default (no country)", carrier: "none", deviceModel: "motorola razr 60 ultra", androidVersion: "15" },
];
interface AdEntry {
id: string;
packageName: string;
appName: string;
clickUrl: string;
destinationUrl: string;
impressionUrl: string;
notPlayStore: boolean;
viewThrough: number;
category: string;
}
interface MarketResult {
market: MarketConfig;
status: number | string;
configFlags: Record<string, unknown>;
ads: AdEntry[];
jsCodeLength: number;
error?: string;
}
async function queryMarket(market: MarketConfig): Promise<MarketResult> {
const payload: Record<string, unknown> = {
dK: API_KEY,
aV: "1.03.0095",
pN: PACKAGE_NAME,
sV: SDK_VERSION,
oV: market.androidVersion,
dM: market.deviceModel,
cA: market.carrier,
lST: 0,
b: Date.now() - 86400000,
c: 8,
m: 8589934592,
sId: `${PACKAGE_NAME}_${Date.now()}_${market.country}`,
cVK: "",
aVK: "",
mVK: "",
bVK: "",
cGD: false,
cUD: false,
cAdD: false,
cSA: false,
cSI: false,
cRA: false,
cRI: false,
retry: 0,
};
if (market.country) {
payload.co = market.country;
}
try {
const resp = await fetch(BASE_URL, {
method: "POST",
headers: {
"Content-Type": "application/json; charset=utf-8",
Accept: "application/json",
"User-Agent": `Dalvik/2.1.0 (Linux; U; Android ${market.androidVersion}; ${market.deviceModel})`,
},
body: JSON.stringify(payload),
});
if (!resp.ok) {
const text = await resp.text();
return {
market,
status: resp.status,
configFlags: {},
ads: [],
jsCodeLength: 0,
error: text,
};
}
const data = await resp.json();
const cfg = (data.cfg ?? data.f ?? {}) as Record<string, unknown>;
const ads: AdEntry[] = [];
if (data.a && Array.isArray(data.a)) {
for (const adStr of data.a) {
try {
const ad = typeof adStr === "string" ? JSON.parse(adStr) : adStr;
ads.push({
id: ad.id ?? ad._id ?? "",
packageName: ad.packageName ?? "",
appName: ad.appName ?? "",
clickUrl: ad.clickUrl ?? "",
destinationUrl: ad.destinationUrl ?? "",
impressionUrl: ad.impressionUrl ?? "",
notPlayStore: ad.notPlayStore ?? false,
viewThrough: ad.viewThrough ?? 0,
category: ad.category ?? "",
});
} catch { /* skip */ }
}
}
return {
market,
status: resp.status,
configFlags: cfg,
ads,
jsCodeLength: data.c?.length ?? 0,
};
} catch (err) {
return {
market,
status: "error",
configFlags: {},
ads: [],
jsCodeLength: 0,
error: String(err),
};
}
}
function delay(ms: number): Promise<void> {
return new Promise((r) => setTimeout(r, ms));
}
async function main() {
console.log(`=== DeviceNative Multi-Market Sweep ===`);
console.log(`Querying ${MARKETS.length} markets...\n`);
const results: MarketResult[] = [];
for (const market of MARKETS) {
const tag = market.country || "DEFAULT";
process.stdout.write?.(`[${tag}] ${market.label}...`) ??
Deno.stdout.writeSync(new TextEncoder().encode(`[${tag}] ${market.label}...`));
const result = await queryMarket(market);
results.push(result);
if (result.error) {
console.log(` ERROR: ${result.error.slice(0, 80)}`);
} else {
console.log(` ${result.ads.length} ads, cfg.rGP=${result.configFlags.rGP ?? "n/a"}`);
}
await delay(500); // rate limit
}
// Deduplicate ads across all markets
const allAds = new Map<string, { ad: AdEntry; markets: string[] }>();
const marketAdCounts: Record<string, number> = {};
for (const result of results) {
const tag = result.market.country || "DEFAULT";
marketAdCounts[tag] = result.ads.length;
for (const ad of result.ads) {
const key = ad.packageName || ad.id;
if (allAds.has(key)) {
allAds.get(key)!.markets.push(tag);
} else {
allAds.set(key, { ad, markets: [tag] });
}
}
}
// Build markdown
const lines: string[] = [];
lines.push("# DeviceNative DNA — Full Ad Catalog Across Markets");
lines.push("");
lines.push(`**Date**: ${new Date().toISOString().split("T")[0]}`);
lines.push(`**API Endpoint**: \`POST https://api.devicenative.com/v2/sync\``);
lines.push(`**API Key**: \`${API_KEY}\` (hardcoded default from APK)`);
lines.push(`**SDK Version**: \`${SDK_VERSION}\``);
lines.push(`**Markets queried**: ${MARKETS.length}`);
lines.push(`**Unique apps targeted**: ${allAds.size}`);
lines.push("");
lines.push("---");
lines.push("");
// Config flags summary
lines.push("## Server Config Flags by Market");
lines.push("");
lines.push("| Market | rGP | vta | org | orgB | aDL | sad | rad | cpi | Ads |");
lines.push("|--------|-----|-----|-----|------|-----|-----|-----|-----|-----|");
for (const result of results) {
const tag = result.market.country || "DEFAULT";
const c = result.configFlags;
if (result.error) {
lines.push(`| ${result.market.label} (${tag}) | — | — | — | — | — | — | — | — | ERR |`);
} else {
lines.push(
`| ${result.market.label} (${tag}) | ${c.rGP ?? "—"} | ${c.vta ?? "—"} | ${c.org ?? "—"} | ${c.orgB ?? "—"} | ${c.aDL ?? "—"} | ${c.sad ?? "—"} | ${c.rad ?? "—"} | ${c.cpi ?? "—"} | ${result.ads.length} |`,
);
}
}
lines.push("");
// Play Store replacement URLs
const marketsWithRGP = results.filter(
(r) => r.configFlags.rGP === true || r.configFlags.rGP === "true",
);
if (marketsWithRGP.length > 0) {
lines.push("## !! Markets with Play Store URL Replacement (rGP=true)");
lines.push("");
for (const r of marketsWithRGP) {
lines.push(
`- **${r.market.label}** (${r.market.country}): gpU = \`${r.configFlags.gpU ?? "not set"}\``,
);
}
lines.push("");
}
// Full ad catalog
lines.push("## Complete Ad Catalog");
lines.push("");
lines.push("| App | Package | Destination | Markets |");
lines.push("|-----|---------|-------------|---------|");
const sorted = [...allAds.entries()].sort((a, b) =>
b[1].markets.length - a[1].markets.length
);
for (const [_key, { ad, markets }] of sorted) {
const dest = ad.destinationUrl || "—";
const mkts = markets.join(", ");
lines.push(
`| ${ad.appName || "?"} | \`${ad.packageName}\` | ${dest} | ${mkts} |`,
);
}
lines.push("");
// Detailed per-market breakdown
lines.push("## Per-Market Breakdown");
lines.push("");
for (const result of results) {
if (result.ads.length === 0) continue;
const tag = result.market.country || "DEFAULT";
lines.push(`### ${result.market.label} (${tag})`);
lines.push("");
lines.push(`- Device: ${result.market.deviceModel}`);
lines.push(`- Carrier: ${result.market.carrier}`);
lines.push(`- Ads: ${result.ads.length}`);
lines.push(`- JS code: ${result.jsCodeLength} chars`);
lines.push("");
lines.push("| App | Package | Click URL | Destination |");
lines.push("|-----|---------|-----------|-------------|");
for (const ad of result.ads) {
const clickDomain = ad.clickUrl
? (() => { try { return new URL(ad.clickUrl).hostname; } catch { return "?"; } })()
: "—";
lines.push(
`| ${ad.appName} | \`${ad.packageName}\` | ${clickDomain} | ${ad.destinationUrl || "—"} |`,
);
}
lines.push("");
}
// Infrastructure
lines.push("## Infrastructure");
lines.push("");
const allClickDomains = new Set<string>();
const allImpDomains = new Set<string>();
for (const { ad } of allAds.values()) {
try { allClickDomains.add(new URL(ad.clickUrl).hostname); } catch { /* */ }
try { allImpDomains.add(new URL(ad.impressionUrl).hostname); } catch { /* */ }
}
lines.push("**Click tracking domains:**");
for (const d of allClickDomains) lines.push(`- \`${d}\``);
lines.push("");
lines.push("**Impression tracking domains:**");
for (const d of allImpDomains) lines.push(`- \`${d}\``);
lines.push("");
// Write files
const md = lines.join("\n");
await Deno.writeTextFile("ADS_RESULTS.md", md);
console.log(`\n[+] Wrote ADS_RESULTS.md`);
await Deno.writeTextFile(
"all_markets_raw.json",
JSON.stringify(results.map((r) => ({
market: r.market,
status: r.status,
configFlags: r.configFlags,
adCount: r.ads.length,
ads: r.ads,
jsCodeLength: r.jsCodeLength,
error: r.error,
})), null, 2),
);
console.log("[+] Wrote all_markets_raw.json");
// Summary stats
console.log(`\n=== SUMMARY ===`);
console.log(`Markets queried: ${MARKETS.length}`);
console.log(`Markets with ads: ${results.filter((r) => r.ads.length > 0).length}`);
console.log(`Unique apps targeted: ${allAds.size}`);
console.log(`Markets with rGP=true: ${marketsWithRGP.length}`);
}
main();
(function() {
const effectiveOrganicRequest = organicRequest || source === 'suggested apps';
const searchReenAdCap = 1;
const displayReenAdCap = 4;
const searchInstAdCap = 4;
const d1FrequencyCap = 12;
const d7FrequencyCap = 60;
const clickRecencyPriorityWindowSeconds = 60;
const clickRecencyPriorityWindowMs = clickRecencyPriorityWindowSeconds * 1000;
const clickRecencyPriorityEnabled = source === 'suggested apps';
const installRecencyPriorityWindowSeconds = 300;
const installRecencyPriorityWindowMs = installRecencyPriorityWindowSeconds * 1000;
const installRecencyPriorityEnabled = source === 'suggested apps';
const softBoostEligibleRate = 0.25;
const softBoostTargetSlotIndex = 3;
const softBoostCandidateStartIndex = 4;
const softBoostCandidateEndIndex = 14;
const softBoostMinUsage7DSeconds = 30;
const softBoostDisplacementRecencyMs = 1000 * 60 * 60 * 2;
const d1delta = 1000 * 60 * 60 * 24;
const d3delta = 1000 * 60 * 60 * 24 * 3;
const d5delta = 1000 * 60 * 60 * 24 * 5;
const d7delta = 1000 * 60 * 60 * 24 * 7;
const d14delta = 1000 * 60 * 60 * 24 * 14;
const d30delta = 1000 * 60 * 60 * 24 * 30;
const y3delta = 1000 * 60 * 60 * 24 * 365 * 3;
let importantAppScores = {
"com.android.dialer": 0.3,
"com.samsung.android.dialer": 0.3,
"com.google.android.dialer": 0.3,
"com.android.messaging": 0.2,
"com.samsung.android.messaging": 0.2,
"com.google.android.messaging": 0.2,
"com.android.chrome": 0.4,
"com.google.android.youtube": 0.2,
"com.sec.android.app.camera": 0.3,
"com.android.settings": 0.4,
"com.android.vending": 0.4,
"com.google.android.apps.maps": 0.2
};
let penalizedPackages = ["com.motorola.mobiledesktop"]
let blockedDualAppPackages = ["com.google.android.googlequicksearchbox"];
let loggingObject = {
ad: ads.length,
ap: Object.keys(apps).length,
t: currentTime,
u: usageStatsEnabled,
s: Object.keys(shortcuts).length
};
let selectedAdIds = [];
let selectedPackages = [];
const hasBeenSelected = (ad) => {
return selectedPackages.includes(ad.packageName) || selectedAdIds.includes(ad.id);
};
const getAppVariantKey = (app) => {
if (!app || !app.packageName) {
return null;
}
return `${app.packageName}|${app.component || ''}|${app.userId || ''}`;
};
const getUserHandleNumber = (handle) => {
if (typeof handle !== 'string') {
return Number.NEGATIVE_INFINITY;
}
const match = handle.match(/\{(-?\d+)\}/);
if (!match) {
return Number.NEGATIVE_INFINITY;
}
const parsed = parseInt(match[1], 10);
return isNaN(parsed) ? Number.NEGATIVE_INFINITY : parsed;
};
const isInstalledAppVariant = (app) => {
if (!app) {
return false;
}
return !(app.uninstalledAt > app.installedAt);
};
const isCurrentUserAppVariant = (app) => {
return !!app && (!app.userId || app.userId === userId);
};
const pickPreferredAppVariant = (candidateApps) => {
if (!candidateApps || candidateApps.length === 0) {
return null;
}
let bestApp = null;
for (let i = 0; i < candidateApps.length; i += 1) {
const candidate = candidateApps[i];
if (!candidate) {
continue;
}
if (!bestApp) {
bestApp = candidate;
continue;
}
const candidateInstalled = isInstalledAppVariant(candidate);
const bestInstalled = isInstalledAppVariant(bestApp);
if (candidateInstalled !== bestInstalled) {
if (candidateInstalled) {
bestApp = candidate;
}
continue;
}
const candidateCurrent = isCurrentUserAppVariant(candidate);
const bestCurrent = isCurrentUserAppVariant(bestApp);
if (candidateCurrent !== bestCurrent) {
if (candidateCurrent) {
bestApp = candidate;
}
continue;
}
const candidateUpdatedAt = typeof candidate.updatedAt === 'number' ? candidate.updatedAt : 0;
const bestUpdatedAt = typeof bestApp.updatedAt === 'number' ? bestApp.updatedAt : 0;
if (candidateUpdatedAt !== bestUpdatedAt) {
if (candidateUpdatedAt > bestUpdatedAt) {
bestApp = candidate;
}
continue;
}
const candidateInstalledAt = typeof candidate.installedAt === 'number' ? candidate.installedAt : 0;
const bestInstalledAt = typeof bestApp.installedAt === 'number' ? bestApp.installedAt : 0;
if (candidateInstalledAt !== bestInstalledAt) {
if (candidateInstalledAt > bestInstalledAt) {
bestApp = candidate;
}
continue;
}
const candidateHandleNumber = getUserHandleNumber(candidate.userId);
const bestHandleNumber = getUserHandleNumber(bestApp.userId);
if (candidateHandleNumber !== bestHandleNumber) {
if (candidateHandleNumber > bestHandleNumber) {
bestApp = candidate;
}
}
}
return bestApp;
};
const parseStringList = (value) => {
if (typeof value !== 'string') {
return [];
}
const trimmed = value.trim();
if (!trimmed) {
return [];
}
if (trimmed[0] === '[') {
try {
const parsed = JSON.parse(trimmed);
if (Array.isArray(parsed)) {
return parsed.map((item) => String(item).trim()).filter(Boolean);
}
} catch (error) {
// fall through
}
}
return trimmed
.replace(/[\[\]]/g, '')
.split(',')
.map((item) => item.trim().replace(/^['"]|['"]$/g, ''))
.filter(Boolean);
};
const deStringifyTargetingMetadata = (ad) => {
if (!ad) {
return;
}
if (ad.targetingMetadata && typeof ad.targetingMetadata === 'string') {
try {
ad.targetingMetadata = JSON.parse(ad.targetingMetadata);
} catch (error) {
ad.targetingMetadata = {};
return;
}
}
if (!ad.targetingMetadata || typeof ad.targetingMetadata !== 'object' || Array.isArray(ad.targetingMetadata)) {
ad.targetingMetadata = {};
return;
}
if (ad.targetingMetadata.aliases && typeof ad.targetingMetadata.aliases === 'string') {
ad.targetingMetadata.aliases = parseStringList(ad.targetingMetadata.aliases);
}
if (ad.targetingMetadata.competitors && typeof ad.targetingMetadata.competitors === 'string') {
ad.targetingMetadata.competitors = parseStringList(ad.targetingMetadata.competitors);
}
};
const getReenBlackoutWindowMs = (ad) => {
if (!ad || ad.type !== 'reen') {
return 0;
}
deStringifyTargetingMetadata(ad);
let blackoutHours = ad.targetingMetadata ? ad.targetingMetadata.reenBlackoutHours : null;
if (blackoutHours === undefined || blackoutHours === null || blackoutHours === '') {
return 0;
}
if (typeof blackoutHours === 'string') {
blackoutHours = parseFloat(blackoutHours);
}
if (typeof blackoutHours !== 'number' || !isFinite(blackoutHours) || blackoutHours <= 0) {
return 0;
}
return blackoutHours * 60 * 60 * 1000;
};
const isFrequencyCapped = (ad) => {
if (ad.type === 'reen') {
return false;
}
const currEvents = events ? events[ad.packageName] : null;
if (currEvents) {
let d1Impressions = 0;
let d7Impressions = 0;
for (let i = 0; i < currEvents.length; i += 1) {
const event = currEvents[i];
if (event.event === 'view' && event.completedAt > (currentTime - d7delta)) {
if (event.placement && event.placement.indexOf('search') > -1) {
continue;
}
d7Impressions += 1;
if (event.completedAt > (currentTime - d1delta)) {
d1Impressions += 1;
}
if (d1Impressions > d1FrequencyCap || d7Impressions > d7FrequencyCap) {
return true;
}
}
}
}
return false;
};
const hasRecentVTA = (ad) => {
const currEvents = events ? events[ad.packageName] : null;
if (currEvents) {
for (let i = 0; i < currEvents.length; i += 1) {
if (currEvents[i].event === 'vta' && currEvents[i].completedAt > (currentTime - d7delta)) {
return true;
}
}
}
return false;
};
const isConvertedAd = (ad, app) => {
if (!ad) {
return false;
}
if (ad.type === 'reen') {
const blackoutWindowMs = getReenBlackoutWindowMs(ad);
if (!blackoutWindowMs) {
return false;
}
const currEvents = events ? events[ad.packageName] : null;
if (currEvents) {
for (let i = 0; i < currEvents.length; i += 1) {
if (currEvents[i].event === 'click' &&
currEvents[i].completedAt > (currentTime - blackoutWindowMs)) {
return true;
}
}
}
return false;
}
const currEvents = events ? events[ad.packageName] : null;
if (currEvents) {
for (let i = 0; i < currEvents.length; i += 1) {
if ((currEvents[i].event === 'click' || currEvents[i].event === 'vta') && currEvents[i].completedAt > (currentTime - d3delta)) {
if ((ad.type === 'inst' || ad.type === 'acti') &&
app && app.timeUsedInLast3Y > 0 &&
(app.lastTimeInForeground - currEvents[i].completedAt) > 0) {
return true;
// } else if ((ad.type === 'web' || ad.type === 'reen') &&
// (currentTime - currEvents[i].completedAt) < d3delta) {
// return true;
}
}
}
}
return false;
};
const isSoftBoostConvertedAd = (ad) => {
if (!ad || ad.type !== 'reen') {
return false;
}
const currEvents = events ? events[ad.packageName] : null;
if (currEvents) {
for (let i = 0; i < currEvents.length; i += 1) {
if (currEvents[i].event === 'click' &&
currEvents[i].completedAt > (currentTime - d1delta)) {
return true;
}
}
}
return false;
};
const hasAnyUsage = (app) => {
if (!app) {
return false;
}
return (app.timeUsedInLast1D || 0) > 0 ||
(app.timeUsedInLast3D || 0) > 0 ||
(app.timeUsedInLast7D || 0) > 0 ||
(app.timeUsedInLast3Y || 0) > 0;
};
const isInstEligibleApp = (app) => {
if (!app) {
return false;
}
return !hasAnyUsage(app) &&
(app.lastTimeInForeground || 0) <= 0 &&
(app.lastUsedAt || 0) <= 0;
};
const getInstalledAdPriority = (ad, app, options) => {
if (!ad || !app) {
return Number.POSITIVE_INFINITY;
}
const allowedTypes = options && options.allowedTypes ? options.allowedTypes : null;
if (allowedTypes && !allowedTypes.includes(ad.type)) {
return Number.POSITIVE_INFINITY;
}
const isConverted = options && options.useSoftBoostConversion ?
isSoftBoostConvertedAd(ad) :
isConvertedAd(ad, app);
if (isConverted) {
return Number.POSITIVE_INFINITY;
}
const appHasUsage = hasAnyUsage(app);
const hasNoUsage = !appHasUsage;
const recentInstallWithUsage = (currentTime - app.installedAt) < d1delta && appHasUsage;
if (ad.type === 'inst') {
return isInstEligibleApp(app) ? 0 : Number.POSITIVE_INFINITY;
}
if (ad.type === 'acti') {
const distantInstallWithUsage = (currentTime - app.installedAt) < d30delta && appHasUsage;
return hasNoUsage || recentInstallWithUsage || distantInstallWithUsage ? 1 : Number.POSITIVE_INFINITY;
}
if (ad.type === 'reen') {
return 2;
}
if (ad.type === 'web' && options && options.includeWeb && webAdsEnabled) {
return 3;
}
return Number.POSITIVE_INFINITY;
};
const getBestInstalledAdForApp = (app, adPool, options) => {
if (!app) {
return null;
}
const candidateAds = adPool || ads;
let bestAd = null;
let bestPriority = Number.POSITIVE_INFINITY;
for (let i = 0; i < candidateAds.length; i += 1) {
const candidateAd = candidateAds[i];
if (!candidateAd || candidateAd.packageName !== app.packageName) {
continue;
}
const priority = getInstalledAdPriority(candidateAd, app, options);
if (priority < bestPriority) {
bestAd = candidateAd;
bestPriority = priority;
if (priority === 0) {
break;
}
}
}
return bestAd;
};
const compareInstalledAdsWithTypePriority = (a, b, metricDiff) => {
if (metricDiff !== 0) {
return metricDiff;
}
if (a && b && a.packageName && a.packageName === b.packageName) {
const app = apps[a.packageName];
const aPriority = getInstalledAdPriority(a, app);
const bPriority = getInstalledAdPriority(b, app);
if (aPriority !== bPriority) {
return aPriority - bPriority;
}
}
return 0;
};
const hasSoftBoostRecentUsage = (app) => {
if (!app) {
return false;
}
return app.timeUsedInLast7D >= softBoostMinUsage7DSeconds &&
app.lastTimeInForeground > (currentTime - d7delta);
};
const isVeryRecentlyUsedApp = (app) => {
if (!app) {
return false;
}
return app.lastTimeInForeground > (currentTime - softBoostDisplacementRecencyMs);
};
const getSuggestedAppsSoftBoostState = (visibleApps, organicCount) => {
if (source !== 'suggested apps' ||
!organicAdsEnabled ||
organicAdsBoostEnabled ||
organicCount < 5 ||
visibleApps.length <= softBoostTargetSlotIndex) {
return null;
}
const displacedApp = visibleApps[softBoostTargetSlotIndex];
if (!displacedApp ||
importantAppScores[displacedApp.packageName] ||
isVeryRecentlyUsedApp(displacedApp)) {
return null;
}
let visiblePackages = [];
for (let i = 0; i < Math.min(organicCount, visibleApps.length); i += 1) {
visiblePackages.push(visibleApps[i].packageName);
}
for (let i = softBoostCandidateStartIndex; i <= softBoostCandidateEndIndex && i < visibleApps.length; i += 1) {
let candidateApp = visibleApps[i];
if (!candidateApp ||
visiblePackages.includes(candidateApp.packageName) ||
!hasSoftBoostRecentUsage(candidateApp)) {
continue;
}
let candidateAd = getBestInstalledAdForApp(candidateApp, ads, {
allowedTypes: ['reen'],
useSoftBoostConversion: true
});
if (!candidateAd ||
candidateAd.type !== 'reen' ||
isSoftBoostConvertedAd(candidateAd)) {
continue;
}
return {
candidateIndex: i,
candidatePackage: candidateApp.packageName,
displacedPackage: displacedApp.packageName,
scoreGap: (displacedApp.rankingScore || 0) - (candidateApp.rankingScore || 0)
};
}
return null;
};
const recentInstallNoUsage = (ad, app) => {
if ((currentTime - app.installedAt) < d7delta &&
app.timeUsedInLast7D === 0 &&
((ad.type === 'inst' && isInstEligibleApp(app)) || ad.type === 'acti' || ad.type === 'reen')) {
return true;
}
return false;
};
const recentInstallUsage = (ad, app) => {
if ((currentTime - app.installedAt) < d14delta &&
(ad.type === 'acti' || ad.type === 'reen')) {
return true;
}
return false;
};
const recentUsage = (ad, app) => {
if (((currentTime - app.installedAt) < d14delta || app.timeUsedInLast7D > 0) &&
ad.type === 'reen') {
return true;
}
return false;
};
const distantInstallNoUsage = (ad, app) => {
if ((currentTime - app.installedAt) < d30delta &&
app.timeUsedInLast3Y === 0 &&
((ad.type === 'inst' && isInstEligibleApp(app)) || ad.type === 'acti' || ad.type === 'reen')) {
return true;
}
return false;
};
const distantInstallUsage = (ad, app) => {
if ((currentTime - app.installedAt) < d30delta &&
(ad.type === 'acti' || ad.type === 'reen')) {
return true;
}
return false;
};
const competitorInstall = (ad) => {
if (ad.targetingMetadata &&
ad.targetingMetadata.competitors &&
ad.targetingMetadata.competitors.length > 0) {
for (let i = 0; i < ad.targetingMetadata.competitors.length; i += 1) {
const competitorPackage = ad.targetingMetadata.competitors[i];
const app = apps[competitorPackage];
if (app && (currentTime - app.installedAt) < d30delta) {
return true;
}
}
}
return false;
};
const uninstalledApp = (app) => {
if (app.uninstalledAt > app.installedAt) {
return true;
}
return false;
};
const parseMotoBoostRule = (ad) => {
if (!ad || ad.type !== 'reen') {
return null;
}
deStringifyTargetingMetadata(ad);
const rawRule = ad.targetingMetadata ? ad.targetingMetadata.mb : null;
if (typeof rawRule !== 'string' || !rawRule.trim()) {
return null;
}
const parts = rawRule.split('|').map((part) => parseInt(part.trim(), 10));
if (parts.length !== 3 || parts.some((part) => isNaN(part))) {
return null;
}
const slot = Math.max(1, parts[0]);
const interval = Math.max(1, parts[1]);
const weeklyCap = Math.max(0, parts[2]);
return { slot, interval, weeklyCap };
};
const getMotoBoostWeeklyViews = (ad) => {
const currEvents = events ? events[ad.packageName] : null;
if (!currEvents) {
return 0;
}
let views = 0;
for (let i = 0; i < currEvents.length; i += 1) {
const event = currEvents[i];
if (event.event === 'view' &&
event.completedAt > (currentTime - d7delta) &&
(!event.resId || event.resId === ad.id)) {
views += 1;
}
}
return views;
};
const passesMotoBoostInterval = (ad, rule) => {
return rule.interval <= 1 || Math.random() < (1 / rule.interval);
};
const isMotoBoostEligible = (ad, rule, allowAlreadySelected) => {
if (source !== 'suggested apps' || !effectiveOrganicRequest) {
return false;
}
if (!ad || !rule) {
return false;
}
if (!allowAlreadySelected && hasBeenSelected(ad)) {
return false;
}
const app = apps[ad.packageName];
if (!app || uninstalledApp(app)) {
return false;
}
if (blacklist && blacklist.includes(ad.packageName)) {
return false;
}
if (!passesMotoBoostInterval(ad, rule)) {
return false;
}
if (rule.weeklyCap > 0 && getMotoBoostWeeklyViews(ad) >= rule.weeklyCap) {
return false;
}
return !isConvertedAd(ad, app);
};
const tagMotoBoostAd = (ad, rule, mode) => {
if (ad.viewThrough && hasRecentVTA(ad)) {
ad.viewThrough = 0;
}
ad.log = {
sel: mode,
mb: '1',
mbS: rule.slot,
mbI: rule.interval,
mbW: rule.weeklyCap
};
};
const rememberSelectedAd = (ad) => {
if (!selectedAdIds.includes(ad.id)) {
selectedAdIds.push(ad.id);
}
if (!selectedPackages.includes(ad.packageName)) {
selectedPackages.push(ad.packageName);
}
};
const applyMotoCampaignBoost = (results, resultCount) => {
if (source !== 'suggested apps') {
return results;
}
for (let i = 0; i < ads.length; i += 1) {
const ad = ads[i];
const rule = parseMotoBoostRule(ad);
let matchingResult = null;
for (let j = 0; j < results.length; j += 1) {
const result = results[j];
if (!result || !result.app || result.app.packageName !== ad.packageName) {
continue;
}
matchingResult = result;
break;
}
const allowAlreadySelected = !!(matchingResult && matchingResult.ad && matchingResult.ad.id === ad.id);
if (!isMotoBoostEligible(ad, rule, allowAlreadySelected)) {
continue;
}
if (matchingResult) {
if (matchingResult.ad && matchingResult.ad.id !== ad.id) {
return results;
}
tagMotoBoostAd(ad, rule, 'moto-boost-organic');
matchingResult.ad = ad;
rememberSelectedAd(ad);
return results;
}
const slotIndex = rule.slot - 1;
if (slotIndex < 0 || slotIndex >= resultCount) {
return results;
}
if (results[slotIndex] && results[slotIndex].ad) {
return results;
}
tagMotoBoostAd(ad, rule, 'moto-boost-slot');
results.splice(slotIndex, 0, {
app: apps[ad.packageName],
ad
});
while (results.length > resultCount) {
results.pop();
}
rememberSelectedAd(ad);
return results;
}
return results;
};
const distantUsage = (ad, app) => {
if (app.timeUsedInLast3Y > 0 &&
ad.type === 'reen') {
return true;
}
return false;
};
const competitiveUsage = (ad) => {
if (ad.targetingMetadata &&
ad.targetingMetadata.competitors &&
ad.targetingMetadata.competitors.length > 0) {
for (let i = 0; i < ad.targetingMetadata.competitors.length; i += 1) {
let competitorPackage = ad.targetingMetadata.competitors[i];
let app = apps[competitorPackage];
if (app && app.timeUsedInLast3Y > 0) {
return true;
}
}
}
return false;
};
const appInstalled = (ad, app) => {
if (app.installedAt > app.uninstalledAt &&
ad.type === 'reen') {
return true;
}
return false;
};
const rankInstalledAds = (adsToRank) => {
let appList = [];
let seenPackages = [];
for (let i = 0; i < adsToRank.length; i += 1) {
let ad = adsToRank[i];
if (!ad || seenPackages.includes(ad.packageName)) {
continue;
}
let app = apps[ad.packageName];
if (!app) {
continue;
}
seenPackages.push(ad.packageName);
appList.push(app);
}
let rankedAds = [];
let rankedApps = rankInstalledApps(appList);
for (let i = 0; i < rankedApps.length; i += 1) {
let app = rankedApps[i];
let ad = getBestInstalledAdForApp(app, adsToRank);
if (ad) {
rankedAds.push(ad);
}
}
return rankedAds;
};
const rankInstalledApps = (appsToRank) => {
let rankedApps = [];
let max1DLog = Math.log(1 + d1delta);
let max3DLog = Math.log(1 + d3delta);
let max7DLog = Math.log(1 + d7delta);
let max3YLog = Math.log(1 + y3delta);
const timeOfDayLookbackMs = d7delta;
const timeOfDayWindowMinutes = 90;
const timeOfDayMinScore = 0.25;
const timeOfDayMaxScore = 3.0;
const timeOfDayWeight = 0.6;
const timeOfDayDate = new Date(currentTime);
const timeOfDayNowMinutes = (timeOfDayDate.getHours() * 60) + timeOfDayDate.getMinutes();
const timeOfDayInvWindow = 1 / timeOfDayWindowMinutes;
const timeOfDayInvLookback = 1 / timeOfDayLookbackMs;
const timeOfDayCutoffMs = currentTime - timeOfDayLookbackMs;
for (let i = 0; i < appsToRank.length; i += 1) {
let app = appsToRank[i];
if (app && !uninstalledApp(app)) {
let pkg = app.packageName;
if (pkg === currentPackage) {
continue;
}
let usedVeryRecentlyScore = 0;
if (app.lastTimeInForeground > 0) {
let timeSinceLastUseSeconds = (currentTime - app.lastTimeInForeground) / 1000;
if (timeSinceLastUseSeconds <= 1800) {
usedVeryRecentlyScore = 4.0 - (1.5 * timeSinceLastUseSeconds / 1800);
} else if (timeSinceLastUseSeconds <= 7200) {
usedVeryRecentlyScore = 2.5 - (1.5 * (timeSinceLastUseSeconds - 1800) / 5400);
} else if (timeSinceLastUseSeconds <= 21600) {
usedVeryRecentlyScore = 1.0 - (0.5 * (timeSinceLastUseSeconds - 7200) / 14400);
} else {
usedVeryRecentlyScore = Math.max(0.5 - (timeSinceLastUseSeconds - 21600) / 72000, 0);
}
}
let usageScore = 0.4 * (Math.log(1 + 1000 * app.timeUsedInLast1D) / max1DLog) +
0.3 * (Math.log(1 + 1000 * app.timeUsedInLast3D) / max3DLog) +
0.2 * (Math.log(1 + 1000 * app.timeUsedInLast7D) / max7DLog) +
0.1 * (Math.log(1 + 1000 * app.timeUsedInLast3Y) / max3YLog);
if (penalizedPackages.includes(pkg)) {
usageScore = usageScore * 0.05;
}
let engagementScore = 0;
let mostRecentClickAt = 0;
let timeOfDayScore = 0;
if (events && events[pkg]) {
const eventsForPackage = events[pkg];
const len = eventsForPackage.length;
if (len > 0) {
const first = eventsForPackage[0];
const last = eventsForPackage[len - 1];
const firstTs = first && typeof first.completedAt === 'number' ? first.completedAt : 0;
const lastTs = last && typeof last.completedAt === 'number' ? last.completedAt : 0;
const ascending = firstTs <= lastTs;
const appId = app.id;
let hasEngagement = false;
const start = ascending ? len - 1 : 0;
const end = ascending ? -1 : len;
const step = ascending ? -1 : 1;
for (let j = start; j !== end; j += step) {
const event = eventsForPackage[j];
if (!event || event.event !== 'click' || typeof event.completedAt !== 'number') {
continue;
}
if (event.resId === undefined || appId === undefined || event.resId !== appId) {
continue;
}
const ts = event.completedAt;
const ageMs = currentTime - ts;
if (ageMs < 0) {
continue;
}
if (!hasEngagement) {
const timeSinceEventSeconds = ageMs / 1000;
mostRecentClickAt = ts;
if (timeSinceEventSeconds <= 300) {
engagementScore = 10.0 - (2.0 * timeSinceEventSeconds / 300);
} else if (timeSinceEventSeconds <= 1800) {
engagementScore = 8.0 - (3.0 * (timeSinceEventSeconds - 300) / 1500);
} else if (timeSinceEventSeconds <= 3600) {
engagementScore = 5.0 - (2.0 * (timeSinceEventSeconds - 1800) / 1800);
} else if (timeSinceEventSeconds <= 21600) {
engagementScore = 3.0 - (1.0 * (timeSinceEventSeconds - 3600) / 18000);
} else if (timeSinceEventSeconds <= 86400) {
engagementScore = 2.0 - (1.0 * (timeSinceEventSeconds - 21600) / 64800);
} else {
const timeSinceEventDays = timeSinceEventSeconds / 86400;
engagementScore = Math.exp(-1.5 * timeSinceEventDays);
}
hasEngagement = true;
}
if (ts < timeOfDayCutoffMs) {
continue;
}
timeOfDayDate.setTime(ts);
const eventMinutes = (timeOfDayDate.getHours() * 60) + timeOfDayDate.getMinutes();
let diff = eventMinutes - timeOfDayNowMinutes;
if (diff < 0) {
diff = -diff;
}
if (diff > 720) {
diff = 1440 - diff;
}
if (diff > timeOfDayWindowMinutes) {
continue;
}
const timeWeight = 1 - (diff * timeOfDayInvWindow);
const recencyWeight = 1 - (ageMs * timeOfDayInvLookback);
timeOfDayScore += timeWeight * recencyWeight;
}
}
}
let timeSinceInstallSeconds = (currentTime - app.installedAt) / 1000;
let timeSinceInstallDays = timeSinceInstallSeconds / 86400;
let usageEngagementFactor = 1.0;
if (timeSinceInstallSeconds > 21600) {
let totalRecentUsage = app.timeUsedInLast1D + app.timeUsedInLast3D + app.timeUsedInLast7D;
let expectedUsageThreshold = Math.max(300, timeSinceInstallDays * 600);
if (totalRecentUsage === 0) {
usageEngagementFactor = Math.max(0.1, Math.exp(-0.5 * timeSinceInstallDays));
} else if (totalRecentUsage < expectedUsageThreshold * 0.3) {
usageEngagementFactor = Math.max(0.3, Math.exp(-0.2 * timeSinceInstallDays));
} else if (totalRecentUsage < expectedUsageThreshold) {
usageEngagementFactor = Math.max(0.6, Math.exp(-0.1 * timeSinceInstallDays));
}
}
let baseInstallScore = 0;
if (timeSinceInstallSeconds <= 3600) {
baseInstallScore = 12.0 - (2.0 * timeSinceInstallSeconds / 3600);
} else if (timeSinceInstallSeconds <= 21600) {
baseInstallScore = 2.0 - (1.0 * (timeSinceInstallSeconds - 3600) / 18000);
} else if (timeSinceInstallSeconds <= 86400) {
baseInstallScore = 1.0 - (0.5 * (timeSinceInstallSeconds - 21600) / 64800);
} else if (timeSinceInstallDays <= 3) {
baseInstallScore = 0.5 - (0.2 * (timeSinceInstallDays - 1) / 2);
} else if (timeSinceInstallDays <= 7) {
baseInstallScore = 0.3 - (0.1 * (timeSinceInstallDays - 3) / 4);
} else if (timeSinceInstallDays <= 14) {
baseInstallScore = 0.2 - (0.1 * (timeSinceInstallDays - 7) / 7);
} else {
baseInstallScore = 0.1 * Math.exp(-0.05 * (timeSinceInstallDays - 14));
}
let installScore = baseInstallScore * usageEngagementFactor;
let importantPackageScore = 0;
if (importantAppScores[pkg]) {
importantPackageScore = importantAppScores[pkg];
}
let engagementMultiplier = 1.0;
if (engagementScore >= 8.0) {
engagementMultiplier = 1.5;
} else if (engagementScore >= 5.0) {
engagementMultiplier = 1.2;
}
let recencyComponent = 0;
if (timeSinceInstallSeconds <= 86400) {
recencyComponent = 0.1 * Math.exp(-0.00003 * timeSinceInstallSeconds);
}
let timeOfDayBoost = 0;
if (timeOfDayScore >= timeOfDayMinScore) {
timeOfDayBoost = Math.min(timeOfDayMaxScore, timeOfDayScore) * timeOfDayWeight;
}
let clickRecencyBonus = 0;
if (clickRecencyPriorityEnabled && mostRecentClickAt > 0) {
const clickAgeMs = currentTime - mostRecentClickAt;
if (clickAgeMs >= 0 && clickAgeMs <= clickRecencyPriorityWindowMs) {
clickRecencyBonus = (clickRecencyPriorityWindowMs - clickAgeMs) / 1000;
}
}
let installRecencyBonus = 0;
if (installRecencyPriorityEnabled && app.installedAt > 0) {
const installAgeMs = currentTime - app.installedAt;
if (installAgeMs >= 0 && installAgeMs <= installRecencyPriorityWindowMs) {
installRecencyBonus = (installRecencyPriorityWindowMs - installAgeMs) / 1000;
}
}
app.rankingScore = usedVeryRecentlyScore
+ usageScore
+ (engagementScore * engagementMultiplier)
+ installScore
+ importantPackageScore
+ recencyComponent
+ timeOfDayBoost
+ clickRecencyBonus
+ installRecencyBonus;
rankedApps.push({ app, mostRecentClickAt, installedAt: app.installedAt || 0 });
}
}
rankedApps.sort((a, b) => {
if (installRecencyPriorityEnabled) {
const aInstalledAt = a.installedAt;
const bInstalledAt = b.installedAt;
const aInstallAge = aInstalledAt > 0 ? currentTime - aInstalledAt : Number.POSITIVE_INFINITY;
const bInstallAge = bInstalledAt > 0 ? currentTime - bInstalledAt : Number.POSITIVE_INFINITY;
const aRecentInstall = aInstalledAt > 0 && aInstallAge >= 0 && aInstallAge <= installRecencyPriorityWindowMs;
const bRecentInstall = bInstalledAt > 0 && bInstallAge >= 0 && bInstallAge <= installRecencyPriorityWindowMs;
if (aRecentInstall && bRecentInstall) {
if (aInstalledAt !== bInstalledAt) {
return bInstalledAt - aInstalledAt;
}
} else if (aRecentInstall !== bRecentInstall) {
return aRecentInstall ? -1 : 1;
}
}
if (clickRecencyPriorityEnabled) {
const aClickAt = a.mostRecentClickAt;
const bClickAt = b.mostRecentClickAt;
const aClickAge = aClickAt > 0 ? currentTime - aClickAt : Number.POSITIVE_INFINITY;
const bClickAge = bClickAt > 0 ? currentTime - bClickAt : Number.POSITIVE_INFINITY;
const aRecent = aClickAt > 0 && aClickAge >= 0 && aClickAge <= clickRecencyPriorityWindowMs;
const bRecent = bClickAt > 0 && bClickAge >= 0 && bClickAge <= clickRecencyPriorityWindowMs;
if (aRecent && bRecent) {
if (aClickAt !== bClickAt) {
return bClickAt - aClickAt;
}
} else if (aRecent !== bRecent) {
return aRecent ? -1 : 1;
}
}
return b.app.rankingScore - a.app.rankingScore;
});
return rankedApps.map(entry => entry.app);
};
const rankNotInstalledAds = (adsToRank) => {
for (let i = 0; i < adsToRank.length; i += 1) {
let ad = adsToRank[i];
deStringifyTargetingMetadata(ad);
}
adsToRank.sort((a, b) => {
if (a.targetingMetadata.downloads && b.targetingMetadata.downloads && a.targetingMetadata.downloads !== b.targetingMetadata.downloads) {
return b.targetingMetadata.downloads - a.targetingMetadata.downloads;
} else if (a.targetingMetadata.reviews && b.targetingMetadata.reviews) {
return b.targetingMetadata.reviews - a.targetingMetadata.reviews;
}
});
return adsToRank || [];
};
const getRecommendations = () => {
const resultCount = count ? count : 5;
let adCount = 0;
// if (resultCount > 6) {
// adCount = 2;
// } else if (resultCount < 4) {
// adCount = 0;
// }
if (pureOrganicAdsEnabled || ads.length === 0) {
adCount = 0;
}
let organicCount = resultCount - adCount;
let packagesWithVariants = new Set();
if (typeof multiActivityApps !== 'undefined' && multiActivityApps) {
for (let packageName of Object.keys(multiActivityApps)) {
packagesWithVariants.add(packageName);
}
}
if (typeof dualApps !== 'undefined' && dualApps) {
for (let packageName of Object.keys(dualApps)) {
packagesWithVariants.add(packageName);
}
}
let appClickStatus = {};
if (events) {
for (let packageName in events) {
if (packagesWithVariants.has(packageName)) {
let eventsForPackage = events[packageName];
for (let event of eventsForPackage) {
if (event.event === 'click') {
if (event.resId !== undefined) {
appClickStatus[`${packageName}|${event.resId}`] = true;
}
}
}
}
}
}
let packagesWithClickedApps = new Set();
let clickedApps = [];
let clickedKeys = new Set();
if (typeof multiActivityApps !== 'undefined' && multiActivityApps && Object.keys(multiActivityApps).length > 0) {
for (let multiActivityAppArray of Object.values(multiActivityApps)) {
for (let app of multiActivityAppArray) {
let appHasClicks = app.id !== undefined ?
appClickStatus[`${app.packageName}|${app.id}`] :
false;
if (appHasClicks) {
const key = `${app.packageName}|${app.component}|${app.userId}`;
if (!clickedKeys.has(key)) {
clickedKeys.add(key);
clickedApps.push(app);
packagesWithClickedApps.add(app.packageName);
}
}
}
}
}
if (typeof dualApps !== 'undefined' && dualApps && Object.keys(dualApps).length > 0) {
for (let dualAppArray of Object.values(dualApps)) {
for (let app of dualAppArray) {
let appHasClicks = app.id !== undefined ?
appClickStatus[`${app.packageName}|${app.id}`] :
false;
if (appHasClicks) {
const key = `${app.packageName}|${app.component}|${app.userId}`;
if (!clickedKeys.has(key)) {
clickedKeys.add(key);
clickedApps.push(app);
packagesWithClickedApps.add(app.packageName);
}
}
}
}
}
let allApps = Object.values(apps).filter(app => {
return !packagesWithClickedApps.has(app.packageName);
});
allApps.push(...clickedApps);
let rankedApps = rankInstalledApps(allApps);
let visibleApps = [];
let visibleAppLimit = Math.max(organicCount, softBoostCandidateEndIndex + 1);
for (let i = 0; i < rankedApps.length && visibleApps.length < visibleAppLimit; i += 1) {
let app = rankedApps[i];
if (blacklist && blacklist.includes(app.packageName)) {
continue;
}
visibleApps.push(app);
}
let softBoostState = getSuggestedAppsSoftBoostState(visibleApps, organicCount);
if (softBoostState && Math.random() < softBoostEligibleRate) {
visibleApps[softBoostTargetSlotIndex] = visibleApps[softBoostState.candidateIndex];
} else {
softBoostState = null;
}
let displayApps = visibleApps.slice(0, organicCount);
let results = [];
for (let i = 0; i < displayApps.length; i += 1) {
let app = displayApps[i];
let resultObj = {
app
};
if (organicAdsEnabled) {
let isSoftBoostedSlot = softBoostState &&
i === softBoostTargetSlotIndex &&
app.packageName === softBoostState.candidatePackage;
let ad = getBestInstalledAdForApp(app, ads, {
allowedTypes: isSoftBoostedSlot ? ['reen'] : null,
includeWeb: !isSoftBoostedSlot,
useSoftBoostConversion: !!isSoftBoostedSlot
});
if (ad) {
if (isSoftBoostedSlot) {
ad.log = {
sel: "organic-soft-boost",
boost_from_rank: softBoostState.candidateIndex + 1,
boost_into_slot: softBoostTargetSlotIndex + 1,
displaced_package: softBoostState.displacedPackage,
score_gap: softBoostState.scoreGap
};
} else {
ad.log = {
sel: "organic"
};
}
if (ad.viewThrough && hasRecentVTA(ad)) {
ad.viewThrough = 0;
}
resultObj.ad = ad;
selectedAdIds.push(ad.id);
selectedPackages.push(ad.packageName);
}
}
results.push(resultObj);
}
if (organicAdsBoostEnabled) {
let organicAdsResults = [], otherResults = [];
for (let i = 0; i < results.length; i += 1) {
let result = results[i];
if (result.ad) {
result.ad.log = {
sel: "organic-boost"
};
organicAdsResults.push(result);
} else {
otherResults.push(result);
}
}
results = [...organicAdsResults, ...otherResults];
}
results = applyMotoCampaignBoost(results, resultCount);
if (adCount === 1) {
let resultObj = {};
let ads = getAdsForDisplayWithUsage(adCount);
if (ads.length > 0) {
resultObj.ad = ads[0];
if (resultObj.ad.viewThrough && hasRecentVTA(resultObj.ad)) {
resultObj.ad.viewThrough = 0;
}
if (apps[ads[0].packageName]) {
resultObj.app = apps[ads[0].packageName];
}
results.push(resultObj);
} else if (rankedPackages.length >= resultCount) {
let newPackage = rankedPackages[resultCount - 1];
resultObj.app = apps[newPackage];
results.push(resultObj);
}
} else if (adCount === 2) {
let insertIndex = Math.floor(resultCount/2);
let resultObj = {};
let ads = getAdsForDisplayWithUsage(adCount);
if (ads.length > 0) {
resultObj.ad = ads[0];
if (resultObj.ad.viewThrough && hasRecentVTA(resultObj.ad)) {
resultObj.ad.viewThrough = 0;
}
if (apps[ads[0].packageName]) {
resultObj.app = apps[ads[0].packageName];
}
results.splice(insertIndex, 0, resultObj);
resultObj = {};
if (ads.length > 1) {
resultObj.ad = ads[1];
if (resultObj.ad.viewThrough && hasRecentVTA(resultObj.ad)) {
resultObj.ad.viewThrough = 0;
}
if (apps[ads[1].packageName]) {
resultObj.app = apps[ads[1].packageName];
}
results.push(resultObj);
} else if (rankedPackages.length >= resultCount) {
let newPackage = rankedPackages[resultCount - 1];
resultObj.app = apps[newPackage];
results.push(resultObj);
}
} else if (rankedPackages.length >= resultCount) {
let newPackage = rankedPackages[resultCount - 2];
resultObj.app = apps[newPackage];
results.push(resultObj);
resultObj = {};
let fallbackPackage = rankedPackages[resultCount - 1];
resultObj.app = apps[fallbackPackage];
results.push(resultObj);
}
}
return results;
};
const getAdsForDisplayWithUsage = (adCount) => {
let chosenAds = [];
let numSelectedReenAds = 0;
let recentInstallNoUsageConsideration = [];
let recentInstallUsageConsideration = [];
let recentUsageConsideration = [];
let timeOfDayUsageConsideration = [];
let distantInstallNoUsageConsideration = [];
let distantInstallUsageConsideration = [];
let competitorInstallConsideration = [];
let uninstalledAppConsideration = [];
let distantUsageConsideration = [];
let competitiveUsageConsideration = [];
let appInstalledConsideration = [];
let notFreqCappedAds = [];
let convertedAds = 0;
const timeOfDayLookbackMs = d7delta;
const timeOfDayWindowMinutes = 90;
const timeOfDayMinScore = 0.25;
const timeOfDayScoreByPkg = {};
const timeOfDayDate = new Date(currentTime);
const timeOfDayNowMinutes = (timeOfDayDate.getHours() * 60) + timeOfDayDate.getMinutes();
const timeOfDayInvWindow = 1 / timeOfDayWindowMinutes;
const timeOfDayInvLookback = 1 / timeOfDayLookbackMs;
const getTimeOfDayScore = (packageName) => {
if (!packageName) {
return 0;
}
const cached = timeOfDayScoreByPkg[packageName];
if (cached !== undefined) {
return cached;
}
const pkgEvents = events ? events[packageName] : null;
if (!pkgEvents || pkgEvents.length === 0) {
timeOfDayScoreByPkg[packageName] = 0;
return 0;
}
const cutoffMs = currentTime - timeOfDayLookbackMs;
const len = pkgEvents.length;
const first = pkgEvents[0];
const last = pkgEvents[len - 1];
const firstTs = first && typeof first.completedAt === 'number' ? first.completedAt : 0;
const lastTs = last && typeof last.completedAt === 'number' ? last.completedAt : 0;
const ascending = firstTs <= lastTs;
let score = 0;
let matches = 0;
const start = ascending ? len - 1 : 0;
const end = ascending ? -1 : len;
const step = ascending ? -1 : 1;
for (let i = start; i !== end; i += step) {
const event = pkgEvents[i];
if (!event || typeof event.completedAt !== 'number') {
continue;
}
const ts = event.completedAt;
if (ts < cutoffMs) {
break;
}
if (event.event !== 'click') {
continue;
}
const ageMs = currentTime - ts;
if (ageMs < 0) {
continue;
}
timeOfDayDate.setTime(ts);
const eventMinutes = (timeOfDayDate.getHours() * 60) + timeOfDayDate.getMinutes();
let diff = eventMinutes - timeOfDayNowMinutes;
if (diff < 0) {
diff = -diff;
}
if (diff > 720) {
diff = 1440 - diff;
}
if (diff > timeOfDayWindowMinutes) {
continue;
}
const timeWeight = 1 - (diff * timeOfDayInvWindow);
const recencyWeight = 1 - (ageMs * timeOfDayInvLookback);
score += timeWeight * recencyWeight;
matches += 1;
}
if (matches === 0 || score < timeOfDayMinScore) {
timeOfDayScoreByPkg[packageName] = 0;
return 0;
}
timeOfDayScoreByPkg[packageName] = score;
return score;
};
for (let i = 0; i < ads.length; i += 1) {
const ad = ads[i];
if (hasBeenSelected(ad)) {
continue;
}
const app = apps[ad.packageName];
if (blacklist && blacklist.includes(ad.packageName)) {
continue;
}
if (!installAdsEnabled && (!app || (app && uninstalledApp(app)))) {
continue;
}
if (app && ad.type === 'web') {
continue;
}
if (!webAdsEnabled && ad.type === 'web') {
continue;
}
if (app && isConvertedAd(ad, app)) {
convertedAds += 1;
continue;
}
if (ad.type === 'reen' && (!app || (app && uninstalledApp(app)))) {
continue;
}
if (app && recentInstallNoUsage(ad, app)) {
recentInstallNoUsageConsideration.push(ad);
}
if (app && recentInstallUsage(ad, app)) {
recentInstallUsageConsideration.push(ad);
}
if (app && recentUsage(ad, app)) {
recentUsageConsideration.push(ad);
}
if (app && ad.type === 'reen' && getTimeOfDayScore(ad.packageName) > 0) {
timeOfDayUsageConsideration.push(ad);
}
if (app && distantInstallNoUsage(ad, app)) {
distantInstallNoUsageConsideration.push(ad);
}
if (isFrequencyCapped(ad)) {
continue;
} else {
notFreqCappedAds.push(ad);
}
if (app && ad.type === 'inst' && !uninstalledApp(app)) {
continue;
}
if (app && distantInstallUsage(ad, app)) {
distantInstallUsageConsideration.push(ad);
}
deStringifyTargetingMetadata(ad);
if (competitorInstall(ad)) {
competitorInstallConsideration.push(ad);
}
if (app && uninstalledApp(app)) {
uninstalledAppConsideration.push(ad);
}
if (app && distantUsage(ad, app)) {
distantUsageConsideration.push(ad);
}
if (competitiveUsage(ad)) {
competitiveUsageConsideration.push(ad);
}
if (app && appInstalled(ad, app)) {
appInstalledConsideration.push(ad);
}
}
loggingObject.l1 = recentInstallNoUsageConsideration.length;
loggingObject.l2 = recentInstallUsageConsideration.length;
loggingObject.l3 = recentUsageConsideration.length;
loggingObject.ltod = timeOfDayUsageConsideration.length;
loggingObject.l4 = distantInstallNoUsageConsideration.length;
loggingObject.l5 = distantInstallUsageConsideration.length;
loggingObject.l6 = competitorInstallConsideration.length;
loggingObject.l7 = uninstalledAppConsideration.length;
loggingObject.l8 = distantUsageConsideration.length;
loggingObject.l9 = competitiveUsageConsideration.length;
loggingObject.l10 = appInstalledConsideration.length;
loggingObject.lfr = (ads.length-notFreqCappedAds.length);
loggingObject.lc = convertedAds;
if (recentInstallNoUsageConsideration.length > 0) {
recentInstallNoUsageConsideration.sort((a, b) => {
const aApp = apps[a.packageName];
const bApp = apps[b.packageName];
return compareInstalledAdsWithTypePriority(a, b, aApp.installedAt - bApp.installedAt);
});
for (let i = 0; i < recentInstallNoUsageConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = recentInstallNoUsageConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "recent-inst-no-usage", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
numSelectedReenAds += 1;
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (recentInstallUsageConsideration.length > 0) {
recentInstallUsageConsideration.sort((a, b) => {
const aApp = apps[a.packageName];
const bApp = apps[b.packageName];
return compareInstalledAdsWithTypePriority(a, b, aApp.installedAt - bApp.installedAt);
});
for (let i = 0; i < recentInstallUsageConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = recentInstallUsageConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "recent-inst-usage", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
numSelectedReenAds += 1;
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (recentUsageConsideration.length > 0) {
recentUsageConsideration.sort((a, b) => {
const aApp = apps[a.packageName];
const bApp = apps[b.packageName];
return aApp.timeUsedInLast7D - bApp.timeUsedInLast7D;
});
for (let i = 0; i < recentUsageConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = recentUsageConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "recent-usage", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
numSelectedReenAds += 1;
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (timeOfDayUsageConsideration.length > 0) {
timeOfDayUsageConsideration.sort((a, b) => {
const aScore = timeOfDayScoreByPkg[a.packageName] || 0;
const bScore = timeOfDayScoreByPkg[b.packageName] || 0;
if (aScore !== bScore) {
return bScore - aScore;
}
const aApp = apps[a.packageName];
const bApp = apps[b.packageName];
return (bApp ? bApp.lastTimeInForeground : 0) - (aApp ? aApp.lastTimeInForeground : 0);
});
for (let i = 0; i < timeOfDayUsageConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = timeOfDayUsageConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "time-of-day-usage", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
numSelectedReenAds += 1;
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (distantInstallNoUsageConsideration.length > 0) {
distantInstallNoUsageConsideration.sort((a, b) => {
const aApp = apps[a.packageName];
const bApp = apps[b.packageName];
return compareInstalledAdsWithTypePriority(a, b, aApp.installedAt - bApp.installedAt);
});
for (let i = 0; i < distantInstallNoUsageConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = distantInstallNoUsageConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "dist-inst-no-usage", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
numSelectedReenAds += 1;
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (distantInstallUsageConsideration.length > 0) {
distantInstallUsageConsideration.sort((a, b) => {
const aApp = apps[a.packageName];
const bApp = apps[b.packageName];
return compareInstalledAdsWithTypePriority(a, b, bApp.timeUsedInLast3Y - aApp.timeUsedInLast3Y);
});
for (let i = 0; i < distantInstallUsageConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = distantInstallUsageConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "dist-inst-usage", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
numSelectedReenAds += 1;
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (competitorInstallConsideration.length > 0) {
competitorInstallConsideration.sort((a, b) => {
let aCompetitiveInstall = 0;
let bCompetitiveInstall = 0;
for (let i = 0; i < a.targetingMetadata.competitors.length; i += 1) {
let competitorPackage = a.targetingMetadata.competitors[i];
let app = apps[competitorPackage];
if (app && app.installedAt > aCompetitiveInstall) {
aCompetitiveInstall = app.installedAt;
}
}
for (let i = 0; i < b.targetingMetadata.competitors.length; i += 1) {
const competitorPackage = b.targetingMetadata.competitors[i];
const app = apps[competitorPackage];
if (app && app.installedAt > bCompetitiveInstall) {
bCompetitiveInstall = app.installedAt;
}
}
return aCompetitiveInstall - bCompetitiveInstall;
});
for (let i = 0; i < competitorInstallConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = competitorInstallConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (apps[selectedAd.packageName] && numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "competitor-install", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
if (apps[selectedAd.packageName]) {
numSelectedReenAds += 1;
}
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (uninstalledAppConsideration.length > 0) {
uninstalledAppConsideration.sort((a, b) => {
const aApp = apps[a.packageName];
const bApp = apps[b.packageName];
return aApp.timeUsedInLast3Y - bApp.timeUsedInLast3Y;
});
for (let i = 0; i < uninstalledAppConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = uninstalledAppConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
selectedAd.log = { "sel": "uninstalled-app", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (distantUsageConsideration.length > 0) {
distantUsageConsideration.sort((a, b) => {
const aApp = apps[a.packageName];
const bApp = apps[b.packageName];
return aApp.timeUsedInLast3Y - bApp.timeUsedInLast3Y;
});
for (let i = 0; i < distantUsageConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = distantUsageConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "dist-usage", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
numSelectedReenAds += 1;
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (competitiveUsageConsideration.length > 0) {
competitiveUsageConsideration.sort((a, b) => {
let aCompetitiveUsage = 0;
let bCompetitiveUsage = 0;
for (let i = 0; i < a.targetingMetadata.competitors.length; i += 1) {
const competitorPackage = a.targetingMetadata.competitors[i];
const app = apps[competitorPackage];
if (app) {
aCompetitiveUsage += app.timeUsedInLast7D;
}
}
for (let i = 0; i < b.targetingMetadata.competitors.length; i += 1) {
const competitorPackage = b.targetingMetadata.competitors[i];
const app = apps[competitorPackage];
if (app) {
bCompetitiveUsage += app.timeUsedInLast7D;
}
}
return aCompetitiveUsage - bCompetitiveUsage;
});
for (let i = 0; i < competitiveUsageConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = competitiveUsageConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (apps[selectedAd.packageName] && numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "competitive-usage", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
if (apps[selectedAd.packageName]) {
numSelectedReenAds += 1;
}
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (appInstalledConsideration.length > 0) {
appInstalledConsideration.sort((a, b) => {
const aApp = apps[a.packageName];
const bApp = apps[b.packageName];
return aApp.lastTimeInForeground - bApp.lastTimeInForeground;
});
for (let i = 0; i < appInstalledConsideration.length && chosenAds.length < adCount; i += 1) {
let selectedAd = appInstalledConsideration[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "app-installed", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
numSelectedReenAds += 1;
}
if (chosenAds.length === adCount) {
return chosenAds;
}
}
if (notFreqCappedAds.length > 0) {
notFreqCappedAds = rankNotInstalledAds(notFreqCappedAds);
for (let i = 0; i < notFreqCappedAds.length && chosenAds.length < adCount; i += 1) {
let selectedAd = notFreqCappedAds[i];
if (hasBeenSelected(selectedAd)) {
continue;
}
if (apps[selectedAd.packageName] && numSelectedReenAds >= displayReenAdCap) {
continue;
}
selectedAd.log = { "sel": "by-downloads", ...loggingObject };
selectedAdIds.push(selectedAd.id);
selectedPackages.push(selectedAd.packageName);
chosenAds.push(selectedAd);
if (apps[selectedAd.packageName]) {
numSelectedReenAds += 1;
}
}
}
return chosenAds;
};
const processQueryIntoParts = () => {
// Return the full query as a single part instead of splitting by spaces/periods
let queryParts = [];
if (query && query.length > 0) {
queryParts.push(query.toLowerCase());
}
return queryParts;
};
const processAppNameIntoParts = (appName) => {
let appNameParts = [appName.toLowerCase()];
let parts = appName.split(' ');
for (let j = 0; j < parts.length; j += 1) {
let part = parts[j];
let lowerPart = part.toLowerCase();
if (appNameParts.includes(lowerPart)) {
continue;
}
if (part.length > 0) {
let capParts = part.split(/(?<=[a-z])(?=[A-Z])/);
if (capParts.length > 1) {
for (let k = 0; k < capParts.length; k += 1) {
let capPart = capParts[k];
if (capPart.length > 0) {
appNameParts.push(capPart.toLowerCase());
}
}
}
appNameParts.push(lowerPart);
}
}
return appNameParts;
};
const KEYBOARD_ADJACENCY_MATRIX = {
'a': ['q', 'w', 's', 'z'],
'b': ['v', 'g', 'h', 'n'],
'c': ['x', 'd', 'f', 'v'],
'd': ['s', 'e', 'r', 'f', 'x', 'c'],
'e': ['w', 's', 'd', 'r'],
'f': ['d', 'r', 't', 'g', 'v', 'c'],
'g': ['f', 't', 'y', 'h', 'b', 'v'],
'h': ['g', 'y', 'u', 'j', 'n', 'b'],
'i': ['u', 'j', 'k', 'o'],
'j': ['h', 'u', 'i', 'k', 'm', 'n'],
'k': ['j', 'i', 'o', 'l'],
'l': ['k', 'o', 'p'],
'm': ['n', 'j', 'k'],
'n': ['b', 'h', 'j', 'm'],
'o': ['i', 'k', 'l', 'p'],
'p': ['o', 'l'],
'q': ['a', 'w'],
'r': ['e', 'd', 'f', 't'],
's': ['a', 'w', 'e', 'd', 'x', 'z'],
't': ['r', 'f', 'g', 'y'],
'u': ['y', 'h', 'j', 'i'],
'v': ['c', 'f', 'g', 'b'],
'w': ['q', 'a', 's', 'e'],
'x': ['z', 's', 'd', 'c'],
'y': ['t', 'g', 'h', 'u'],
'z': ['a', 's', 'x', ',']
};
const startsWithAllowingTwoAdjacents = (queryPart, appNamePart) => {
if (queryPart.length > appNamePart.length) {
return false;
}
let maxAllowedAdjacents = 2;
const areAdjacentChars = (char1, char2) => {
return KEYBOARD_ADJACENCY_MATRIX[char1]?.includes(char2);
}
let doesMatch = true;
let adjacents = 0;
for (let i = 0; i < queryPart.length; i += 1) {
if (queryPart[i] === appNamePart[i]) {
continue;
}
if (areAdjacentChars(appNamePart[i], queryPart[i]) && adjacents < maxAllowedAdjacents) {
adjacents += 1;
} else {
doesMatch = false;
break;
}
}
return doesMatch;
};
const filterAppsByNameAndQuery = (apps, queryParts) => {
let startPrefixMatchedApps = [];
let laterPrefixMatchedApps = [];
let fuzzyPrefixedMatchedApps = [];
let startPrefixAliasMatchedApps = [];
let fuzzyLaterPrefixedMatchedApps = [];
let containsMatchedApps = [];
let shortcutMatchedApps = [];
// Since we now have only one query part (the full query), we can simplify the logic
if (queryParts.length === 0) {
return {
startPrefixMatchedApps,
laterPrefixMatchedApps,
fuzzyPrefixedMatchedApps,
startPrefixAliasMatchedApps,
fuzzyLaterPrefixedMatchedApps,
containsMatchedApps
};
}
const fullQuery = queryParts[0]; // The full query string
for (let i = 0; i < apps.length; i += 1) {
let app = apps[i];
let appName = app.appName;
if (app.normAppName && app.normAppName.length > 0) {
appName = app.normAppName;
}
let appNameParts = processAppNameIntoParts(appName);
let aliases = app.aliases ? app.aliases.split(',') : [];
if (aliases.length === 0 && app.targetingMetadata) {
deStringifyTargetingMetadata(app);
aliases = app.targetingMetadata.aliases || [];
}
let matched = false;
// Check for start prefix match (query matches start of first app name part)
if (!matched && appNameParts.length > 0 && appNameParts[0].startsWith(fullQuery)) {
if (!app.log) {
app.log = {};
}
app.log = { "sel": "start-prefix-match", ...loggingObject };
startPrefixMatchedApps.push(app);
matched = true;
}
// Check for later prefix match (query matches start of any non-first app name part)
if (!matched) {
for (let j = 1; j < appNameParts.length; j += 1) {
if (appNameParts[j].startsWith(fullQuery)) {
if (!app.log) {
app.log = {};
}
app.log = { "sel": "later-prefix-match", ...loggingObject };
laterPrefixMatchedApps.push(app);
matched = true;
break;
}
}
}
// Check for fuzzy prefix match (query matches with typos)
if (!matched) {
for (let j = 0; j < appNameParts.length; j += 1) {
if (startsWithAllowingTwoAdjacents(fullQuery, appNameParts[j])) {
if (!app.log) {
app.log = {};
}
if (j === 0) {
app.log = { "sel": "start-prefix-fuzz", ...loggingObject };
fuzzyPrefixedMatchedApps.push(app);
} else {
app.log = { "sel": "later-prefix-fuzz", ...loggingObject };
fuzzyLaterPrefixedMatchedApps.push(app);
}
matched = true;
break;
}
}
}
// Check for alias match (query matches start of any alias)
if (!matched && aliases.length > 0) {
for (let j = 0; j < aliases.length; j += 1) {
if (aliases[j].startsWith(fullQuery)) {
if (!app.log) {
app.log = {};
}
app.log = { "sel": "start-prefix-alias", ...loggingObject };
startPrefixAliasMatchedApps.push(app);
matched = true;
break;
}
}
}
// Check for contains match (query is contained within app name parts)
if (!matched && fullQuery.length > 2) {
for (let j = 0; j < appNameParts.length; j += 1) {
if (appNameParts[j].includes(fullQuery)) {
if (!app.log) {
app.log = {};
}
app.log = { "sel": "contains-matched", ...loggingObject };
containsMatchedApps.push(app);
matched = true;
break;
}
}
}
}
return {
startPrefixMatchedApps,
laterPrefixMatchedApps,
fuzzyPrefixedMatchedApps,
startPrefixAliasMatchedApps,
fuzzyLaterPrefixedMatchedApps,
containsMatchedApps
};
};
const collectSearchCandidateApps = () => {
let allApps = [];
const seenVariantKeys = new Set();
const addAppVariant = (app) => {
const variantKey = getAppVariantKey(app);
if (!variantKey || seenVariantKeys.has(variantKey)) {
return;
}
seenVariantKeys.add(variantKey);
allApps.push(app);
};
for (let app of Object.values(apps || {})) {
addAppVariant(app);
}
if (typeof multiActivityApps !== 'undefined' && multiActivityApps && Object.keys(multiActivityApps).length > 0) {
for (let multiActivityAppArray of Object.values(multiActivityApps)) {
for (let app of multiActivityAppArray) {
addAppVariant(app);
}
}
}
return allApps;
};
const dedupeSearchMatches = (matchedApps) => {
const groupedApps = new Map();
const groupedOrder = [];
for (let i = 0; i < matchedApps.length; i += 1) {
const app = matchedApps[i];
const appKey = getAppVariantKey(app);
if (!appKey) {
continue;
}
if (!groupedApps.has(appKey)) {
groupedApps.set(appKey, []);
groupedOrder.push(appKey);
}
groupedApps.get(appKey).push(app);
}
const dedupedApps = [];
for (let i = 0; i < groupedOrder.length; i += 1) {
const preferredApp = pickPreferredAppVariant(groupedApps.get(groupedOrder[i]));
if (preferredApp) {
dedupedApps.push(preferredApp);
}
}
return dedupedApps;
};
const collectDistinctDualSearchVariants = (packageName) => {
if (!packageName || !dualApps || !dualApps[packageName] || blockedDualAppPackages.includes(packageName)) {
return [];
}
const groupedApps = new Map();
const groupedOrder = [];
for (let i = 0; i < dualApps[packageName].length; i += 1) {
const dualApp = dualApps[packageName][i];
if (!dualApp || uninstalledApp(dualApp)) {
continue;
}
const dualKey = getAppVariantKey(dualApp);
if (!dualKey) {
continue;
}
if (!groupedApps.has(dualKey)) {
groupedApps.set(dualKey, []);
groupedOrder.push(dualKey);
}
groupedApps.get(dualKey).push(dualApp);
}
const distinctApps = [];
for (let i = 0; i < groupedOrder.length; i += 1) {
const preferredApp = pickPreferredAppVariant(groupedApps.get(groupedOrder[i]));
if (preferredApp) {
distinctApps.push(preferredApp);
}
}
return distinctApps;
};
const getSearchResults = () => {
let results = [];
let queryParts = processQueryIntoParts();
let allApps = collectSearchCandidateApps();
let organicMatchingObj = filterAppsByNameAndQuery(allApps, queryParts);
// TEMPORARILY DISABLED - Complex usage-based ranking
// organicMatchingObj.startPrefixMatchedApps = rankInstalledApps(organicMatchingObj.startPrefixMatchedApps);
// organicMatchingObj.laterPrefixMatchedApps = rankInstalledApps(organicMatchingObj.laterPrefixMatchedApps);
// organicMatchingObj.fuzzyPrefixedMatchedApps = rankInstalledApps(organicMatchingObj.fuzzyPrefixedMatchedApps);
// organicMatchingObj.fuzzyLaterPrefixedMatchedApps = rankInstalledApps(organicMatchingObj.fuzzyLaterPrefixedMatchedApps);
// organicMatchingObj.startPrefixAliasMatchedApps = rankInstalledApps(organicMatchingObj.startPrefixAliasMatchedApps);
// organicMatchingObj.containsMatchedApps = rankInstalledApps(organicMatchingObj.containsMatchedApps);
// console.log('organicMatchingObj: ', organicMatchingObj);
// TEMPORARY ALPHABETICAL SORTING - Combine all results and sort alphabetically
let tempResults = [
...organicMatchingObj.startPrefixMatchedApps.sort((a, b) => {
let aName = (a.appName || a.packageName || '').toLowerCase();
let bName = (b.appName || b.packageName || '').toLowerCase();
return aName.localeCompare(bName);
}),
...organicMatchingObj.laterPrefixMatchedApps.sort((a, b) => {
let aName = (a.appName || a.packageName || '').toLowerCase();
let bName = (b.appName || b.packageName || '').toLowerCase();
return aName.localeCompare(bName);
})];
// ...organicMatchingObj.fuzzyPrefixedMatchedApps,
// ...organicMatchingObj.startPrefixAliasMatchedApps,
// ...organicMatchingObj.fuzzyLaterPrefixedMatchedApps,
// ...organicMatchingObj.containsMatchedApps];
// Sort alphabetically first, but don't filter uninstalled apps yet (we need them for dual app logic)
tempResults = tempResults.filter(app => {
return app && app.packageName !== currentPackage;
});
tempResults = dedupeSearchMatches(tempResults);
for (let i = 0; i < tempResults.length; i += 1) {
let app = tempResults[i];
let resultObj = {
app
};
if (organicAdsEnabled) {
let ad = getBestInstalledAdForApp(app, ads, {
allowedTypes: ['inst', 'acti', 'reen']
});
if (ad) {
if (ad.viewThrough && hasRecentVTA(ad)) {
ad.viewThrough = 0;
}
resultObj.ad = ad;
resultObj.ad.log = app.log;
delete app.log;
selectedAdIds.push(ad.id);
selectedPackages.push(ad.packageName);
}
}
results.push(resultObj);
}
if (organicAdsBoostEnabled) {
let organicAdsResults = [], otherResults = [];
for (let i = 0; i < results.length; i += 1) {
let result = results[i];
if (result.ad) {
organicAdsResults.push(result);
} else {
otherResults.push(result);
}
}
results = [...organicAdsResults, ...otherResults];
}
if (!organicAdsEnabled) {
let reenAdsToInsert = [];
for (let i = 0; i < searchReenAdCap; i += 1) {
for (let j = 0; j < results.length; j += 1) {
let result = results[j];
let ad = getBestInstalledAdForApp(result.app, ads, {
allowedTypes: ['acti', 'reen']
});
if (ad) {
if (ad.viewThrough && hasRecentVTA(ad)) {
ad.viewThrough = 0;
}
let resultObj = {
app: result.app,
ad
}
result.ad.log = result.app.log;
delete result.app.log;
reenAdsToInsert.push(resultObj);
selectedAdIds.push(ad.id);
selectedPackages.push(ad.packageName);
break;
}
}
}
results = [...reenAdsToInsert, ...results];
}
if (typeof dualApps !== 'undefined' && dualApps && Object.keys(dualApps).length > 0) {
// Track which app variants are already present so we don't insert duplicates.
const seenAppKeys = new Set();
for (let k = 0; k < results.length; k += 1) {
const a = results[k] && results[k].app;
if (a) {
const appKey = getAppVariantKey(a);
if (appKey) {
seenAppKeys.add(appKey);
}
}
}
// Use a separate counter to track position as we modify the array
for (let i = 0; i < results.length; i += 1) {
let result = results[i];
if (result.app &&
result.app.packageName &&
dualApps[result.app.packageName] &&
!blockedDualAppPackages.includes(result.app.packageName)) {
const distinctDualApps = collectDistinctDualSearchVariants(result.app.packageName);
let dualAppsToInsert = [];
for (let j = 0; j < distinctDualApps.length; j += 1) {
let dualApp = distinctDualApps[j];
if (!dualApp || dualApp.userId === userId || dualApp.userId === null || uninstalledApp(dualApp)) {
continue;
}
const dualKey = getAppVariantKey(dualApp);
if (seenAppKeys.has(dualKey)) {
continue;
}
seenAppKeys.add(dualKey);
// Work on a copy so we don't mutate the original dualApp object
let dualAppCopy = { ...dualApp };
let dualResult = {
app: dualAppCopy
};
dualAppsToInsert.push(dualResult);
}
// Insert dual apps right after current position
if (dualAppsToInsert.length > 0) {
results.splice(i + 1, 0, ...dualAppsToInsert);
// Skip over the newly inserted items
i += dualAppsToInsert.length;
}
}
}
}
// Now filter out uninstalled apps (after dual apps have been processed)
results = results.filter(result => {
return result.app && !uninstalledApp(result.app);
});
if (installAdsEnabled && !pureOrganicAdsEnabled) {
let potentialInstallAds = [];
for (let i = 0; i < ads.length; i += 1) {
let ad = ads[i];
if (!apps[ad.packageName] && ad.type !== 'reen') {
if (ad.type === 'web' && !webAdsEnabled) {
continue;
}
potentialInstallAds.push(ad);
}
}
let installAdsMatchObject = filterAppsByNameAndQuery(potentialInstallAds, queryParts);
installAdsMatchObject.startPrefixMatchedApps = rankNotInstalledAds(installAdsMatchObject.startPrefixMatchedApps);
installAdsMatchObject.laterPrefixMatchedApps = rankNotInstalledAds(installAdsMatchObject.laterPrefixMatchedApps);
installAdsMatchObject.fuzzyPrefixedMatchedApps = rankNotInstalledAds(installAdsMatchObject.fuzzyPrefixedMatchedApps);
installAdsMatchObject.fuzzyLaterPrefixedMatchedApps = rankNotInstalledAds(installAdsMatchObject.fuzzyLaterPrefixedMatchedApps);
installAdsMatchObject.startPrefixAliasMatchedApps = rankNotInstalledAds(installAdsMatchObject.startPrefixAliasMatchedApps);
let tempInstallAds = [
...installAdsMatchObject.startPrefixMatchedApps,
...installAdsMatchObject.laterPrefixMatchedApps,
...installAdsMatchObject.fuzzyPrefixedMatchedApps,
...installAdsMatchObject.startPrefixAliasMatchedApps,
...installAdsMatchObject.fuzzyLaterPrefixedMatchedApps];
let localAppCount = results.length;
let selectedCount = 0;
for (let i = 0; i < tempInstallAds.length; i += 1) {
if (selectedCount >= searchInstAdCap) {
break;
}
let ad = tempInstallAds[i];
if (hasBeenSelected(ad)) {
continue;
}
if (hasRecentVTA(ad) ||
(i > 0 && localAppCount > 2) ||
query.length === 1 ||
(ad.log && ad.log.sel &&
(ad.log.sel.indexOf("fuzz") > -1 ||
ad.log.sel.indexOf("alias") > -1 ||
ad.log.sel.indexOf("contains") > -1) && query.length < 3)) {
ad.viewThrough = 0;
}
let resultObj = {
ad
};
results.push(resultObj);
selectedAdIds.push(ad.id);
selectedPackages.push(ad.packageName);
selectedCount += 1;
}
}
if (results.length > 0 && results[0].app) {
let app = results[0].app;
let sublinks = [];
if (shortcuts[app.packageName]) {
shortcuts[app.packageName].sort((a, b) => {
return a.rank - b.rank;
});
shortcuts[app.packageName].map((shortcut) => {
sublinks.push({
id: shortcut.id,
appName: app.appName,
title: shortcut.name,
description: shortcut.description,
packageName: shortcut.packageName,
intentUri: shortcut.intentUri,
type: 'shortcut'
});
});
}
results[0].sublinks = sublinks.slice(0, 3);
}
return results;
};
const getAdsForSearch = () => {
let adsToReturn = [];
let queryParts = processQueryIntoParts();
let matchedAds = filterAppsByNameAndQuery(ads, queryParts);
let installedAdFilterFunction = (ad) => {
return apps[ad.packageName] &&
(ad.type === 'reen' || ad.type === 'acti') &&
!isConvertedAd(ad, apps[ad.packageName]) &&
!uninstalledApp(apps[ad.packageName]);
};
let installedMatchedAds = {
startPrefixMatchedAds: matchedAds.startPrefixMatchedApps.filter(installedAdFilterFunction),
laterPrefixMatchedAds: matchedAds.laterPrefixMatchedApps.filter(installedAdFilterFunction),
fuzzyPrefixedMatchedAds: matchedAds.fuzzyPrefixedMatchedApps.filter(installedAdFilterFunction),
startPrefixAliasMatchedAds: matchedAds.startPrefixAliasMatchedApps.filter(installedAdFilterFunction),
fuzzyLaterPrefixedMatchedAds: matchedAds.fuzzyLaterPrefixedMatchedApps.filter(installedAdFilterFunction)
};
let uninstalledAdFilterFunction = (ad) => {
if (ad.type === 'web' && !webAdsEnabled) {
return false;
}
return (!apps[ad.packageName] || uninstalledApp(apps[ad.packageName])) &&
ad.type !== 'reen';
};
let uninstalledMatchedAds = {
startPrefixMatchedAds: matchedAds.startPrefixMatchedApps.filter(uninstalledAdFilterFunction),
laterPrefixMatchedAds: matchedAds.laterPrefixMatchedApps.filter(uninstalledAdFilterFunction),
fuzzyPrefixedMatchedAds: matchedAds.fuzzyPrefixedMatchedApps.filter(uninstalledAdFilterFunction),
startPrefixAliasMatchedAds: matchedAds.startPrefixAliasMatchedApps.filter(uninstalledAdFilterFunction),
fuzzyLaterPrefixedMatchedAds: matchedAds.fuzzyLaterPrefixedMatchedApps.filter(uninstalledAdFilterFunction)
};
installedMatchedAds.startPrefixMatchedAds = rankInstalledAds(installedMatchedAds.startPrefixMatchedAds);
installedMatchedAds.laterPrefixMatchedAds = rankInstalledAds(installedMatchedAds.laterPrefixMatchedAds);
installedMatchedAds.fuzzyPrefixedMatchedAds = rankInstalledAds(installedMatchedAds.fuzzyPrefixedMatchedAds);
installedMatchedAds.startPrefixAliasMatchedAds = rankInstalledAds(installedMatchedAds.startPrefixAliasMatchedAds);
installedMatchedAds.fuzzyLaterPrefixedMatchedAds = rankInstalledAds(installedMatchedAds.fuzzyLaterPrefixedMatchedAds);
let installedAds = [
...installedMatchedAds.startPrefixMatchedAds,
...installedMatchedAds.laterPrefixMatchedAds,
...installedMatchedAds.fuzzyPrefixedMatchedAds,
...installedMatchedAds.startPrefixAliasMatchedAds,
...installedMatchedAds.fuzzyLaterPrefixedMatchedAds
];
uninstalledMatchedAds.startPrefixMatchedAds = rankNotInstalledAds(uninstalledMatchedAds.startPrefixMatchedAds);
uninstalledMatchedAds.laterPrefixMatchedAds = rankNotInstalledAds(uninstalledMatchedAds.laterPrefixMatchedAds);
uninstalledMatchedAds.fuzzyPrefixedMatchedAds = rankNotInstalledAds(uninstalledMatchedAds.fuzzyPrefixedMatchedAds);
uninstalledMatchedAds.startPrefixAliasMatchedAds = rankNotInstalledAds(uninstalledMatchedAds.startPrefixAliasMatchedAds);
uninstalledMatchedAds.fuzzyLaterPrefixedMatchedAds = rankNotInstalledAds(uninstalledMatchedAds.fuzzyLaterPrefixedMatchedAds);
let uninstalledAds = [
...uninstalledMatchedAds.startPrefixMatchedAds,
...uninstalledMatchedAds.laterPrefixMatchedAds,
...uninstalledMatchedAds.fuzzyPrefixedMatchedAds,
...uninstalledMatchedAds.startPrefixAliasMatchedAds,
...uninstalledMatchedAds.fuzzyLaterPrefixedMatchedAds
];
for (let i = 0; i < Math.min(searchReenAdCap, installedAds.length); i += 1) {
let ad = installedAds[i];
if (ad.viewThrough && hasRecentVTA(ad)) {
ad.viewThrough = 0;
}
adsToReturn.push({
ad
});
}
for (let i = 0; i < Math.min(searchInstAdCap, uninstalledAds.length); i += 1) {
let ad = uninstalledAds[i];
if (ad.viewThrough && hasRecentVTA(ad)) {
ad.viewThrough = 0;
}
adsToReturn.push({
ad
});
}
return adsToReturn;
};
let results = [];
if (effectiveOrganicRequest) {
if (query === null || query === undefined || query === '') {
if (linksOnly) {
results = [];
} else {
results = getRecommendations();
}
} else {
results = getSearchResults();
}
} else {
if (query === null || query === undefined || query === '') {
let ads = getAdsForDisplayWithUsage(count);
for (let i = 0; i < ads.length; i += 1) {
if (ads[i].viewThrough && hasRecentVTA(ads[i])) {
ads[i].viewThrough = 0;
}
results.push({
ad: ads[i]
});
}
} else {
results = getAdsForSearch();
}
}
for (let i = 0; i < results.length; i += 1) {
let res = results[i];
if (res.ad) {
deStringifyTargetingMetadata(res.ad);
let tMeta = res.ad.targetingMetadata || {};
if (tMeta.downloads) {
res.ad.downloads = tMeta.downloads;
}
if (tMeta.reviews) {
res.ad.ratings = tMeta.reviews;
}
if (tMeta.rating) {
res.ad.rating = tMeta.rating;
}
if (tMeta.category) {
res.ad.categories = tMeta.category;
}
}
}
return JSON.stringify(results);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment