Skip to content

Commit 84e4c81

Browse files
Merge branch 'refs/heads/tushar-khandelwal/rc-custom-targeting' into tushar-khandelwal/rename-configmetadataclient
# Conflicts: # firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java # firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClient.java # firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClientTest.java
2 parents 6d0e180 + 5a15867 commit 84e4c81

File tree

7 files changed

+126
-61
lines changed

7 files changed

+126
-61
lines changed

firebase-config/api.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ package com.google.firebase.remoteconfig {
1616
method public void remove();
1717
}
1818

19+
public class CustomSignals {
20+
}
21+
22+
public static class CustomSignals.Builder {
23+
ctor public CustomSignals.Builder();
24+
method @NonNull public com.google.firebase.remoteconfig.CustomSignals build();
25+
method @NonNull public com.google.firebase.remoteconfig.CustomSignals.Builder put(@NonNull String, @Nullable String);
26+
method @NonNull public com.google.firebase.remoteconfig.CustomSignals.Builder put(@NonNull String, long);
27+
method @NonNull public com.google.firebase.remoteconfig.CustomSignals.Builder put(@NonNull String, double);
28+
}
29+
1930
public class FirebaseRemoteConfig {
2031
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Boolean> activate();
2132
method @NonNull public com.google.firebase.remoteconfig.ConfigUpdateListenerRegistration addOnConfigUpdateListener(@NonNull com.google.firebase.remoteconfig.ConfigUpdateListener);
@@ -35,7 +46,7 @@ package com.google.firebase.remoteconfig {
3546
method @NonNull public com.google.firebase.remoteconfig.FirebaseRemoteConfigValue getValue(@NonNull String);
3647
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> reset();
3748
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setConfigSettingsAsync(@NonNull com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings);
38-
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setCustomSignals(@NonNull java.util.Map<java.lang.String,java.lang.Object>);
49+
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setCustomSignals(@NonNull com.google.firebase.remoteconfig.CustomSignals);
3950
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setDefaultsAsync(@NonNull java.util.Map<java.lang.String,java.lang.Object>);
4051
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> setDefaultsAsync(@XmlRes int);
4152
field public static final boolean DEFAULT_VALUE_FOR_BOOLEAN = false;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.remoteconfig;
16+
17+
import androidx.annotation.NonNull;
18+
import androidx.annotation.Nullable;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
/**
23+
* Helper class which handles the storage and conversion to strings of key/value pairs with
24+
* heterogeneous value types for custom signals.
25+
*/
26+
public class CustomSignals {
27+
28+
final Map<String, String> customSignals;
29+
30+
public static class Builder {
31+
// Holds the converted pairs of custom keys and values.
32+
private Map<String, String> customSignals = new HashMap<String, String>();
33+
34+
// Methods to accept keys and values and convert values to strings.
35+
@NonNull
36+
public Builder put(@NonNull String key, @Nullable String value) {
37+
customSignals.put(key, value);
38+
return this;
39+
}
40+
41+
@NonNull
42+
public Builder put(@NonNull String key, long value) {
43+
customSignals.put(key, Long.toString(value));
44+
return this;
45+
}
46+
47+
@NonNull
48+
public Builder put(@NonNull String key, double value) {
49+
customSignals.put(key, Double.toString(value));
50+
return this;
51+
}
52+
53+
@NonNull
54+
public CustomSignals build() {
55+
return new CustomSignals(this);
56+
}
57+
}
58+
59+
CustomSignals(@NonNull Builder builder) {
60+
this.customSignals = builder.customSignals;
61+
}
62+
}

firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -655,21 +655,18 @@ private Task<Void> setDefaultsWithStringsMapAsync(Map<String, String> defaultsSt
655655
/**
656656
* Asynchronously changes the custom signals for this {@link FirebaseRemoteConfig} instance.
657657
*
658-
* <p>The values in {@code customSignals} must be one of the following types:
658+
* <p>The {@code customSignals} parameter should be an instance of {@link CustomSignals}, which
659+
* enforces the allowed types for custom signal values (String, Long or Double).
659660
*
660-
* <ul>
661-
* <li><code>Long</code>
662-
* <li><code>String</code>
663-
* </ul>
664-
*
665-
* @param customSignals Map (key, value) of the custom signals to be set for the app instance
661+
* @param customSignalsMap A dictionary of keys and the values of the custom signals to be set for
662+
* the app instance
666663
*/
667664
@NonNull
668-
public Task<Void> setCustomSignals(@NonNull Map<String, Object> customSignals) {
665+
public Task<Void> setCustomSignals(@NonNull CustomSignals customSignalsMap) {
669666
return Tasks.call(
670667
executor,
671668
() -> {
672-
frcSharedPrefs.setCustomSignals(customSignals);
669+
frcSharedPrefs.setCustomSignals(customSignalsMap.customSignals);
673670
return null;
674671
});
675672
}

firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClient.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public class ConfigFetchHttpClient {
9494
private final String apiKey;
9595
private final String projectNumber;
9696
private final String namespace;
97-
Map<String, Object> customSignalMap;
97+
Map<String, String> customSignalsMap;
9898
private final long connectTimeoutInSeconds;
9999
private final long readTimeoutInSeconds;
100100

@@ -109,15 +109,15 @@ public ConfigFetchHttpClient(
109109
String namespace,
110110
long connectTimeoutInSeconds,
111111
long readTimeoutInSeconds,
112-
Map<String, Object> customSignalMap) {
112+
Map<String, String> customSignalsMap) {
113113
this.context = context;
114114
this.appId = appId;
115115
this.apiKey = apiKey;
116116
this.projectNumber = extractProjectNumberFromAppId(appId);
117117
this.namespace = namespace;
118118
this.connectTimeoutInSeconds = connectTimeoutInSeconds;
119119
this.readTimeoutInSeconds = readTimeoutInSeconds;
120-
this.customSignalMap = customSignalMap;
120+
this.customSignalsMap = customSignalsMap;
121121
}
122122

123123
/** Used to verify that the timeout is being set correctly. */
@@ -351,7 +351,9 @@ private JSONObject createFetchRequestBody(
351351

352352
requestBodyMap.put(ANALYTICS_USER_PROPERTIES, new JSONObject(analyticsUserProperties));
353353

354-
requestBodyMap.put(CUSTOM_SIGNALS, new JSONObject(customSignalMap));
354+
if (!customSignalsMap.isEmpty()) {
355+
requestBodyMap.put(CUSTOM_SIGNALS, new JSONObject(customSignalsMap));
356+
}
355357

356358
if (firstOpenTime != null) {
357359
requestBodyMap.put(FIRST_OPEN_TIME, convertToISOString(firstOpenTime));

firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClient.java

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET;
1919
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_SUCCESS;
2020
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.LAST_FETCH_STATUS_THROTTLED;
21+
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.TAG;
2122
import static com.google.firebase.remoteconfig.RemoteConfigComponent.CONNECTION_TIMEOUT_IN_SECONDS;
2223
import static com.google.firebase.remoteconfig.RemoteConfigConstants.RequestFieldKey.CUSTOM_SIGNALS;
2324
import static com.google.firebase.remoteconfig.internal.ConfigFetchHandler.DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS;
2425
import static java.lang.annotation.RetentionPolicy.SOURCE;
2526

2627
import android.content.SharedPreferences;
28+
import android.util.Log;
2729
import androidx.annotation.IntDef;
2830
import androidx.annotation.Nullable;
2931
import androidx.annotation.VisibleForTesting;
@@ -81,6 +83,13 @@ public class ConfigSharedPrefsClient {
8183
private static final String REALTIME_BACKOFF_END_TIME_IN_MILLIS_KEY =
8284
"realtime_backoff_end_time_in_millis";
8385

86+
/** Constants for custom signal limits.*/
87+
private static final int CUSTOM_SIGNALS_MAX_KEY_LENGTH = 250;
88+
89+
private static final int CUSTOM_SIGNALS_MAX_STRING_VALUE_LENGTH = 500;
90+
91+
private static final int CUSTOM_SIGNALS_MAX_COUNT = 100;
92+
8493
private final SharedPreferences frcSharedPrefs;
8594

8695
private final Object frcInfoLock;
@@ -259,22 +268,24 @@ void setBackoffMetadata(int numFailedFetches, Date backoffEndTime) {
259268
}
260269
}
261270

262-
public void setCustomSignals(Map<String, Object> newCustomSignals) {
271+
public void setCustomSignals(Map<String, String> newCustomSignals) {
263272
synchronized (customSignalsLock) {
264273
// Retrieve existing custom signals
265-
Map<String, Object> existingCustomSignals = getCustomSignals();
274+
Map<String, String> existingCustomSignals = getCustomSignals();
266275

267-
for (Map.Entry<String, Object> entry : newCustomSignals.entrySet()) {
276+
for (Map.Entry<String, String> entry : newCustomSignals.entrySet()) {
268277
String key = entry.getKey();
269-
Object value = entry.getValue();
270-
271-
// Validate value type, and key and value length
272-
if (value != null && !(value instanceof String || value instanceof Long)) {
273-
throw new IllegalArgumentException("Custom signal values must be of type String or Long");
274-
}
275-
if (key.length() > 250 || (value instanceof String && ((String) value).length() > 500)) {
276-
throw new IllegalArgumentException(
277-
"Custom signal keys must be 250 characters or less, and string values must be 500 characters or less.");
278+
String value = entry.getValue();
279+
280+
// Validate key and value length
281+
if (key.length() > CUSTOM_SIGNALS_MAX_KEY_LENGTH
282+
|| (value != null && value.length() > CUSTOM_SIGNALS_MAX_STRING_VALUE_LENGTH)) {
283+
Log.w(
284+
TAG,
285+
String.format(
286+
"Invalid custom signal: Custom signal keys must be %d characters or less, and values must be %d characters or less.",
287+
CUSTOM_SIGNALS_MAX_KEY_LENGTH, CUSTOM_SIGNALS_MAX_STRING_VALUE_LENGTH));
288+
return;
278289
}
279290

280291
// Merge new signals with existing ones, overwriting existing keys.
@@ -290,9 +301,13 @@ public void setCustomSignals(Map<String, Object> newCustomSignals) {
290301
if (existingCustomSignals.equals(getCustomSignals())) {
291302
return;
292303
}
293-
if (existingCustomSignals.size() > 100) {
294-
throw new IllegalArgumentException(
295-
"Too many custom signals provided. The maximum allowed is 100.");
304+
if (existingCustomSignals.size() > CUSTOM_SIGNALS_MAX_COUNT) {
305+
Log.w(
306+
TAG,
307+
String.format(
308+
"Invalid custom signal: Too many custom signals provided. The maximum allowed is %d.",
309+
CUSTOM_SIGNALS_MAX_COUNT));
310+
return;
296311
}
297312

298313
frcSharedPrefs
@@ -302,18 +317,15 @@ public void setCustomSignals(Map<String, Object> newCustomSignals) {
302317
}
303318
}
304319

305-
public Map<String, Object> getCustomSignals() {
320+
public Map<String, String> getCustomSignals() {
306321
String jsonString = frcSharedPrefs.getString(CUSTOM_SIGNALS, "{}");
307322
try {
308323
JSONObject existingCustomSignalsJson = new JSONObject(jsonString);
309-
Map<String, Object> custom_signals = new HashMap<>();
324+
Map<String, String> custom_signals = new HashMap<>();
310325
Iterator<String> keys = existingCustomSignalsJson.keys();
311326
while (keys.hasNext()) {
312327
String key = keys.next();
313-
Object value = existingCustomSignalsJson.get(key);
314-
if (value instanceof Integer) {
315-
value = existingCustomSignalsJson.getLong(key);
316-
}
328+
String value = existingCustomSignalsJson.optString(key);
317329
custom_signals.put(key, value);
318330
}
319331
return custom_signals;

firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHttpClientTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public class ConfigFetchHttpClientTest {
8686
"etag-" + PROJECT_NUMBER + "-" + DEFAULT_NAMESPACE + "-fetch-%d";
8787
private static final String FIRST_ETAG = String.format(ETAG_FORMAT, 1);
8888
private static final String SECOND_ETAG = String.format(ETAG_FORMAT, 2);
89-
private static final Map<String, Object> SAMPLE_CUSTOM_SIGNALS =
89+
private static final Map<String, String> SAMPLE_CUSTOM_SIGNALS =
9090
ImmutableMap.of(
9191
"subscription", "premium",
9292
"age", "20");

firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClientTest.java

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.LAST_FETCH_TIME_NO_FETCH_YET;
2626
import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.NO_BACKOFF_TIME;
2727
import static com.google.firebase.remoteconfig.internal.ConfigSharedPrefsClient.NO_FAILED_FETCHES;
28-
import static com.google.firebase.remoteconfig.testutil.Assert.assertThrows;
2928

3029
import android.content.Context;
3130
import android.content.SharedPreferences;
@@ -364,7 +363,7 @@ public void getCustomSignals_isNotSet_returnsEmptyMap() {
364363

365364
@Test
366365
public void getCustomSignals_isSet_returnsCustomSignals() {
367-
Map<String, Object> SAMPLE_CUSTOM_SIGNALS =
366+
Map<String, String> SAMPLE_CUSTOM_SIGNALS =
368367
ImmutableMap.of(
369368
"subscription", "premium",
370369
"age", "20");
@@ -374,40 +373,22 @@ public void getCustomSignals_isSet_returnsCustomSignals() {
374373

375374
@Test
376375
public void setCustomSignals_multipleTimes_addsNewSignals() {
377-
Map<String, Object> signals1 = ImmutableMap.of("subscription", "premium");
378-
Map<String, Object> signals2 = ImmutableMap.of("age", 20L);
376+
Map<String, String> signals1 = ImmutableMap.of("subscription", "premium");
377+
Map<String, String> signals2 = ImmutableMap.of("age", "20");
379378
sharedPrefsClient.setCustomSignals(signals1);
380379
sharedPrefsClient.setCustomSignals(signals2);
381-
Map<String, Object> expectedSignals = ImmutableMap.of("subscription", "premium", "age", 20L);
380+
Map<String, String> expectedSignals = ImmutableMap.of("subscription", "premium", "age", "20");
382381
assertThat(sharedPrefsClient.getCustomSignals()).isEqualTo(expectedSignals);
383382
}
384383

385384
@Test
386385
public void setCustomSignals_nullValue_removesSignal() {
387-
Map<String, Object> signals1 = ImmutableMap.of("subscription", "premium", "age", 20L);
386+
Map<String, String> signals1 = ImmutableMap.of("subscription", "premium", "age", "20");
388387
sharedPrefsClient.setCustomSignals(signals1);
389-
Map<String, Object> signals2 = new HashMap<>();
388+
Map<String, String> signals2 = new HashMap<>();
390389
signals2.put("age", null);
391390
sharedPrefsClient.setCustomSignals(signals2);
392391
Map<String, Object> expectedSignals = ImmutableMap.of("subscription", "premium");
393392
assertThat(sharedPrefsClient.getCustomSignals()).isEqualTo(expectedSignals);
394393
}
395-
396-
@Test
397-
public void setCustomSignals_invalidInput_throwsException() {
398-
Map<String, Object> invalidSignals1 = new HashMap<>();
399-
invalidSignals1.put("name", true);
400-
assertThrows(
401-
IllegalArgumentException.class, () -> sharedPrefsClient.setCustomSignals(invalidSignals1));
402-
403-
Map<String, Object> invalidSignals2 = new HashMap<>();
404-
invalidSignals2.put("a".repeat(251), "value");
405-
assertThrows(
406-
IllegalArgumentException.class, () -> sharedPrefsClient.setCustomSignals(invalidSignals2));
407-
408-
Map<String, Object> invalidSignals3 = new HashMap<>();
409-
invalidSignals3.put("key", "a".repeat(501));
410-
assertThrows(
411-
IllegalArgumentException.class, () -> sharedPrefsClient.setCustomSignals(invalidSignals3));
412-
}
413394
}

0 commit comments

Comments
 (0)