Add ability to configure locale specific media quality settings.

Part 1 of improve media quality controls. User selection coming soon.
This commit is contained in:
Cody Henthorne
2021-05-03 11:44:20 -04:00
parent 85e0e74bc6
commit 2aad00df85
9 changed files with 195 additions and 106 deletions

View File

@@ -4,6 +4,7 @@ import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.annimon.stream.Stream;
@@ -77,6 +78,7 @@ public final class FeatureFlags {
private static final String MESSAGE_PROCESSOR_DELAY = "android.messageProcessor.foregroundDelayMs";
private static final String NOTIFICATION_REWRITE = "android.notificationRewrite";
private static final String MP4_GIF_SEND_SUPPORT = "android.mp4GifSendSupport";
private static final String MEDIA_QUALITY_LEVELS = "android.mediaQuality.levels";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@@ -109,7 +111,8 @@ public final class FeatureFlags {
MESSAGE_PROCESSOR_ALARM_INTERVAL,
MESSAGE_PROCESSOR_DELAY,
NOTIFICATION_REWRITE,
MP4_GIF_SEND_SUPPORT
MP4_GIF_SEND_SUPPORT,
MEDIA_QUALITY_LEVELS
);
@VisibleForTesting
@@ -154,7 +157,8 @@ public final class FeatureFlags {
MESSAGE_PROCESSOR_DELAY,
GV1_FORCED_MIGRATE,
NOTIFICATION_REWRITE,
MP4_GIF_SEND_SUPPORT
MP4_GIF_SEND_SUPPORT,
MEDIA_QUALITY_LEVELS
);
/**
@@ -350,6 +354,10 @@ public final class FeatureFlags {
return getBoolean(MP4_GIF_SEND_SUPPORT, false);
}
public static @Nullable String getMediaQualityLevels() {
return getString(MEDIA_QUALITY_LEVELS, "");
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);

View File

@@ -0,0 +1,99 @@
package org.thoughtcrime.securesms.util;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.mms.PushMediaConstraints;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* Provide access to locale specific values within feature flags following the locale CSV-Colon format.
*
* Example: countryCode:integerValue,countryCode:integerValue,*:integerValue
*/
public final class LocaleFeatureFlags {
private static final String TAG = Log.tag(LocaleFeatureFlags.class);
private static final String COUNTRY_WILDCARD = "*";
private static final int NOT_FOUND = -1;
/**
* In research megaphone group for given country code
*/
public static boolean isInResearchMegaphone() {
return false;
}
/**
* In donate megaphone group for given country code
*/
public static boolean isInDonateMegaphone() {
return isEnabled(FeatureFlags.DONATE_MEGAPHONE, FeatureFlags.donateMegaphone());
}
public static @NonNull Optional<PushMediaConstraints.MediaConfig> getMediaQualityLevel() {
Map<String, Integer> countryValues = parseCountryValues(FeatureFlags.getMediaQualityLevels(), NOT_FOUND);
int level = getCountryValue(countryValues, Recipient.self().getE164().or(""), NOT_FOUND);
return Optional.ofNullable(PushMediaConstraints.MediaConfig.forLevel(level));
}
/**
* Parses a comma-separated list of country codes colon-separated from how many buckets out of 1 million
* should be enabled to see this megaphone in that country code. At the end of the list, an optional
* element saying how many buckets out of a million should be enabled for all countries not listed previously
* in the list. For example, "1:20000,*:40000" would mean 2% of the NANPA phone numbers and 4% of the rest of
* the world should see the megaphone.
*/
private static boolean isEnabled(@NonNull String flag, @NonNull String serialized) {
Map<String, Integer> countryCodeValues = parseCountryValues(serialized, 0);
Recipient self = Recipient.self();
if (countryCodeValues.isEmpty() || !self.getE164().isPresent() || !self.getUuid().isPresent()) {
return false;
}
long countEnabled = getCountryValue(countryCodeValues, self.getE164().or(""), 0);
long currentUserBucket = BucketingUtil.bucket(flag, self.requireUuid(), 1_000_000);
return countEnabled > currentUserBucket;
}
@VisibleForTesting
static @NonNull Map<String, Integer> parseCountryValues(@NonNull String buckets, int defaultValue) {
Map<String, Integer> countryCountEnabled = new HashMap<>();
for (String bucket : buckets.split(",")) {
String[] parts = bucket.split(":");
if (parts.length == 2 && !parts[0].isEmpty()) {
countryCountEnabled.put(parts[0], Util.parseInt(parts[1], defaultValue));
}
}
return countryCountEnabled;
}
@VisibleForTesting
static int getCountryValue(@NonNull Map<String, Integer> countryCodeValues, @NonNull String e164, int defaultValue) {
Integer countEnabled = countryCodeValues.get(COUNTRY_WILDCARD);
try {
String countryCode = String.valueOf(PhoneNumberUtil.getInstance().parse(e164, "").getCountryCode());
if (countryCodeValues.containsKey(countryCode)) {
countEnabled = countryCodeValues.get(countryCode);
}
} catch (NumberParseException e) {
Log.d(TAG, "Unable to determine country code for bucketing.");
return defaultValue;
}
return countEnabled != null ? countEnabled : defaultValue;
}
}

View File

@@ -1,84 +0,0 @@
package org.thoughtcrime.securesms.util;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.HashMap;
import java.util.Map;
/**
* Parses a comma-separated list of country codes colon-separated from how many buckets out of 1 million
* should be enabled to see this megaphone in that country code. At the end of the list, an optional
* element saying how many buckets out of a million should be enabled for all countries not listed previously
* in the list. For example, "1:20000,*:40000" would mean 2% of the NANPA phone numbers and 4% of the rest of
* the world should see the megaphone.
*/
public final class PopulationFeatureFlags {
private static final String TAG = Log.tag(PopulationFeatureFlags.class);
private static final String COUNTRY_WILDCARD = "*";
/**
* In research megaphone group for given country code
*/
public static boolean isInResearchMegaphone() {
return false;
}
/**
* In donate megaphone group for given country code
*/
public static boolean isInDonateMegaphone() {
return isEnabled(FeatureFlags.DONATE_MEGAPHONE, FeatureFlags.donateMegaphone());
}
private static boolean isEnabled(@NonNull String flag, @NonNull String serialized) {
Map<String, Integer> countryCountEnabled = parseCountryCounts(serialized);
Recipient self = Recipient.self();
if (countryCountEnabled.isEmpty() || !self.getE164().isPresent() || !self.getUuid().isPresent()) {
return false;
}
long countEnabled = determineCountEnabled(countryCountEnabled, self.getE164().or(""));
long currentUserBucket = BucketingUtil.bucket(flag, self.requireUuid(), 1_000_000);
return countEnabled > currentUserBucket;
}
@VisibleForTesting
static @NonNull Map<String, Integer> parseCountryCounts(@NonNull String buckets) {
Map<String, Integer> countryCountEnabled = new HashMap<>();
for (String bucket : buckets.split(",")) {
String[] parts = bucket.split(":");
if (parts.length == 2 && !parts[0].isEmpty()) {
countryCountEnabled.put(parts[0], Util.parseInt(parts[1], 0));
}
}
return countryCountEnabled;
}
@VisibleForTesting
static long determineCountEnabled(@NonNull Map<String, Integer> countryCountEnabled, @NonNull String e164) {
Integer countEnabled = countryCountEnabled.get(COUNTRY_WILDCARD);
try {
String countryCode = String.valueOf(PhoneNumberUtil.getInstance().parse(e164, "").getCountryCode());
if (countryCountEnabled.containsKey(countryCode)) {
countEnabled = countryCountEnabled.get(countryCode);
}
} catch (NumberParseException e) {
Log.d(TAG, "Unable to determine country code for bucketing.");
return 0;
}
return countEnabled != null ? countEnabled : 0;
}
}