44import android .content .Context ;
55import android .content .pm .PackageManager ;
66import com .newrelic .videoagent .core .utils .NRLog ;
7+ import java .util .concurrent .atomic .AtomicBoolean ;
78
89import java .util .HashMap ;
910import java .util .Map ;
@@ -44,6 +45,11 @@ public final class NRVideoConfiguration {
4445 private final boolean isTV ;
4546 private final String collectorAddress ;
4647
48+ // Runtime configuration fields (mutable, thread-safe) - Using AtomicBoolean for better performance
49+ private final AtomicBoolean qoeAggregateEnabled = new AtomicBoolean (true );
50+ private final AtomicBoolean runtimeConfigInitialized = new AtomicBoolean (false );
51+
52+
4753 // Performance optimization constants
4854 private static final int DEFAULT_HARVEST_CYCLE_SECONDS = 5 * 60 ; // 5 minutes
4955 private static final int DEFAULT_LIVE_HARVEST_CYCLE_SECONDS = 30 ; // 30 seconds
@@ -80,6 +86,10 @@ private NRVideoConfiguration(Builder builder) {
8086 this .debugLoggingEnabled = builder .debugLoggingEnabled ;
8187 this .isTV = builder .isTV ;
8288 this .collectorAddress = builder .collectorAddress ;
89+
90+ // Initialize runtime configuration
91+ this .qoeAggregateEnabled .set (builder .qoeAggregateEnabled );
92+ this .runtimeConfigInitialized .set (true );
8393 }
8494
8595 // Immutable getters
@@ -95,6 +105,38 @@ private NRVideoConfiguration(Builder builder) {
95105 public boolean isTV () { return isTV ; }
96106 public String getCollectorAddress () { return collectorAddress ; }
97107
108+ // Runtime configuration getters and setters
109+ /**
110+ * Check if QOE_AGGREGATE events should be sent during harvest cycles
111+ * @return true if QOE_AGGREGATE should be sent, false otherwise
112+ */
113+ public boolean isQoeAggregateEnabled () {
114+ if (!runtimeConfigInitialized .get ()) {
115+ throw new IllegalStateException ("NRVideoConfiguration not initialized! Call build() first." );
116+ }
117+ return qoeAggregateEnabled .get ();
118+ }
119+
120+ /**
121+ * Set whether QOE_AGGREGATE events should be sent during harvest cycles
122+ * Lock-free, thread-safe runtime configuration using AtomicBoolean
123+ * @param enabled true to enable QOE_AGGREGATE, false to disable
124+ */
125+ public void setQoeAggregateEnabled (boolean enabled ) {
126+ this .qoeAggregateEnabled .set (enabled );
127+ }
128+
129+ /**
130+ * Initialize configuration with client settings
131+ * @param clientQoeAggregateEnabled QOE aggregate setting from client (null if not provided)
132+ */
133+ public void initializeFromClient (Boolean clientQoeAggregateEnabled ) {
134+ // If client provides a value, use it; otherwise keep current default
135+ if (clientQoeAggregateEnabled != null ) {
136+ this .qoeAggregateEnabled .set (clientQoeAggregateEnabled );
137+ }
138+ }
139+
98140 /**
99141 * Get dead letter retry interval in milliseconds
100142 * Optimized for different device types and network conditions
@@ -110,56 +152,41 @@ public long getDeadLetterRetryInterval() {
110152 }
111153
112154 /**
113- * Parse region code from application token prefix
114- * Matches NewRelic iOS Agent pattern: extracts region prefix before 'x'
115- * Examples: "EUxABCD..." -> "EU", "APxABCD..." -> "AP", "AA..." -> ""
116- */
117- private static String parseRegionFromToken (String applicationToken ) {
118- if (applicationToken == null || applicationToken .length () < 3 ) {
119- return "" ;
120- }
121-
122- // Find the first 'x' in the token
123- int xIndex = applicationToken .indexOf ('x' );
124- if (xIndex == -1 ) {
125- return "" ; // No region prefix found
126- }
127-
128- // Extract everything before the first 'x'
129- String regionCode = applicationToken .substring (0 , xIndex );
130-
131- // Remove any trailing 'x' characters
132- while (regionCode .length () > 0 && regionCode .charAt (regionCode .length () - 1 ) == 'x' ) {
133- regionCode = regionCode .substring (0 , regionCode .length () - 1 );
134- }
135-
136- return regionCode ;
137- }
138-
139- /**
140- * Identify region with proper token parsing and fallback logic
141- * Behavior similar to NewRelic iOS Agent's NRMAAgentConfiguration
155+ * Enterprise-grade region identification with multiple fallback strategies
156+ * Thread-safe and optimized for performance
142157 */
143158 private static String identifyRegion (String applicationToken ) {
144159 if (applicationToken == null || applicationToken .length () < 10 ) {
145160 return "US" ; // Safe default
146161 }
147162
148- // First, try to parse region from token prefix (e.g., "EUx", "APx")
149- String regionCode = parseRegionFromToken (applicationToken );
163+ String cleanToken = applicationToken .trim ().toLowerCase ();
150164
151- if (regionCode != null && regionCode .length () > 0 ) {
152- // Convert region code to uppercase and validate
153- String upperRegion = regionCode .toUpperCase ();
165+ // Strategy 1: Direct prefix matching (most reliable)
166+ for (Map .Entry <String , String > entry : REGION_MAPPINGS .entrySet ()) {
167+ String regionKey = entry .getKey ().toLowerCase ();
168+ if (cleanToken .startsWith (regionKey ) || cleanToken .contains ("-" + regionKey + "-" )) {
169+ return entry .getValue ();
170+ }
171+ }
154172
155- // Map region codes to standard regions
156- String mappedRegion = REGION_MAPPINGS .get (upperRegion );
157- if (mappedRegion != null ) {
158- return mappedRegion ;
173+ // Strategy 2: Token structure analysis
174+ if (cleanToken .length () >= 40 ) { // Standard NR token length
175+ // EU tokens often have specific patterns
176+ if (cleanToken .contains ("eu" ) || cleanToken .contains ("europe" )) {
177+ return "EU" ;
178+ }
179+ // AP tokens often have specific patterns
180+ if (cleanToken .contains ("ap" ) || cleanToken .contains ("asia" ) || cleanToken .contains ("pacific" )) {
181+ return "AP" ;
182+ }
183+ // Gov tokens have specific patterns
184+ if (cleanToken .contains ("gov" ) || cleanToken .contains ("fed" )) {
185+ return "GOV" ;
159186 }
160187 }
161188
162- // Default to US for standard tokens without region prefix
189+ // Strategy 3: Default to US for production stability
163190 return "US" ;
164191 }
165192
@@ -177,6 +204,7 @@ public static final class Builder {
177204 private boolean debugLoggingEnabled = false ;
178205 private boolean isTV = false ;
179206 private String collectorAddress = null ;
207+ private boolean qoeAggregateEnabled = true ; // Default enabled
180208
181209 public Builder (String applicationToken ) {
182210 this .applicationToken = applicationToken ;
@@ -264,6 +292,25 @@ public Builder withCollectorAddress(String collectorAddress) {
264292 return this ;
265293 }
266294
295+ /**
296+ * Enable QOE aggregate events (default: enabled)
297+ * @return Builder instance for method chaining
298+ */
299+ public Builder enableQoeAggregate () {
300+ this .qoeAggregateEnabled = true ;
301+ return this ;
302+ }
303+
304+ /**
305+ * Configure QOE aggregate events
306+ * @param enabled true to enable QOE_AGGREGATE events, false to disable
307+ * @return Builder instance for method chaining
308+ */
309+ public Builder enableQoeAggregate (boolean enabled ) {
310+ this .qoeAggregateEnabled = enabled ;
311+ return this ;
312+ }
313+
267314 private void applyTVOptimizations () {
268315 this .harvestCycleSeconds = TV_HARVEST_CYCLE_SECONDS ;
269316 this .liveHarvestCycleSeconds = TV_LIVE_HARVEST_CYCLE_SECONDS ;
@@ -280,7 +327,10 @@ private void applyMemoryOptimizations() {
280327 }
281328
282329 public NRVideoConfiguration build () {
283- return new NRVideoConfiguration (this );
330+ NRVideoConfiguration config = new NRVideoConfiguration (this );
331+ // Mark runtime configuration as initialized
332+ config .runtimeConfigInitialized .set (true );
333+ return config ;
284334 }
285335 }
286336
0 commit comments