Add a feature flag to disable SMS megaphone.

As part of this work, we also make sure we fetch feature flags during
registration.
This commit is contained in:
Greyson Parrelli 2021-06-18 10:45:00 -04:00 committed by Cody Henthorne
parent 2d93d74b9f
commit 817f1ee938
5 changed files with 77 additions and 19 deletions

View file

@ -5,6 +5,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.util.LocaleFeatureFlags;
import org.thoughtcrime.securesms.util.Util;
import java.util.Collections;
@ -69,7 +70,7 @@ public final class OnboardingValues extends SignalStoreValues {
}
public boolean shouldShowSms(@NonNull Context context) {
return getBoolean(SHOW_SMS, false) && !Util.isDefaultSmsProvider(context) && PhoneNumberFormatter.getLocalCountryCode() != 91;
return getBoolean(SHOW_SMS, false) && !Util.isDefaultSmsProvider(context) && LocaleFeatureFlags.shouldSuggestSms();
}
public void setShowAppearance(boolean value) {

View file

@ -31,9 +31,12 @@ import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest;
import org.thoughtcrime.securesms.registration.service.RegistrationService;
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SupportEmailUtil;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -128,12 +131,23 @@ public final class EnterCodeFragment extends BaseRegistrationFragment
@Override
public void onSuccessfulRegistration() {
SimpleTask.run(() -> {
long startTime = System.currentTimeMillis();
try {
FeatureFlags.refreshSync();
Log.i(TAG, "Took " + (System.currentTimeMillis() - startTime) + " ms to get feature flags.");
} catch (IOException e) {
Log.w(TAG, "Failed to refresh flags after " + (System.currentTimeMillis() - startTime) + " ms.", e);
}
return null;
}, none -> {
keyboard.displaySuccess().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
handleSuccessfulRegistration();
}
});
});
}
@Override

View file

@ -23,6 +23,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData;
@ -30,10 +31,13 @@ import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
import org.thoughtcrime.securesms.registration.service.RegistrationService;
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.SupportEmailUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public final class RegistrationLockFragment extends BaseRegistrationFragment {
@ -310,18 +314,28 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
private void handleSuccessfulPinEntry() {
SignalStore.pinValues().setKeyboardType(getPinEntryKeyboardType());
long startTime = System.currentTimeMillis();
SimpleTask.run(() -> {
SignalStore.onboarding().clearAll();
return ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN);
}, result -> {
long elapsedTime = System.currentTimeMillis() - startTime;
if (result.isPresent()) {
Log.i(TAG, "Storage Service account restore completed: " + result.get().name() + ". (Took " + elapsedTime + " ms)");
} else {
Log.i(TAG, "Storage Service account restore failed to complete in the allotted time. (" + elapsedTime + " ms elapsed)");
Stopwatch stopwatch = new Stopwatch("RegistrationLockRestore");
ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN);
stopwatch.split("AccountRestore");
ApplicationDependencies.getJobManager().runSynchronously(new StorageSyncJob(), TimeUnit.SECONDS.toMillis(10));
stopwatch.split("ContactRestore");
try {
FeatureFlags.refreshSync();
} catch (IOException e) {
Log.w(TAG, "Failed to refresh flags.", e);
}
stopwatch.split("FeatureFlags");
stopwatch.stop(TAG);
return null;
}, none -> {
cancelSpinning(pinButton);
Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionSuccessfulRegistration());
});

View file

@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.util;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -78,6 +79,7 @@ public final class FeatureFlags {
private static final String RETRY_RECEIPT_LIFESPAN = "android.retryReceiptLifespan";
private static final String RETRY_RESPOND_MAX_AGE = "android.retryRespondMaxAge";
private static final String SENDER_KEY = "android.senderKey";
private static final String SUGGEST_SMS_BLACKLIST = "android.suggestSmsBlacklist";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@ -110,7 +112,8 @@ public final class FeatureFlags {
MEDIA_QUALITY_LEVELS,
RETRY_RECEIPT_LIFESPAN,
RETRY_RESPOND_MAX_AGE,
SENDER_KEY
SENDER_KEY,
SUGGEST_SMS_BLACKLIST
);
@VisibleForTesting
@ -156,7 +159,8 @@ public final class FeatureFlags {
MP4_GIF_SEND_SUPPORT,
MEDIA_QUALITY_LEVELS,
RETRY_RECEIPT_LIFESPAN,
RETRY_RESPOND_MAX_AGE
RETRY_RESPOND_MAX_AGE,
SUGGEST_SMS_BLACKLIST
);
/**
@ -199,7 +203,7 @@ public final class FeatureFlags {
Log.i(TAG, "init() " + REMOTE_VALUES.toString());
}
public static synchronized void refreshIfNecessary() {
public static void refreshIfNecessary() {
long timeSinceLastFetch = System.currentTimeMillis() - SignalStore.remoteConfigValues().getLastFetchTime();
if (timeSinceLastFetch < 0 || timeSinceLastFetch > FETCH_INTERVAL) {
@ -210,6 +214,12 @@ public final class FeatureFlags {
}
}
@WorkerThread
public static void refreshSync() throws IOException {
Map<String, Object> config = ApplicationDependencies.getSignalServiceAccountManager().getRemoteConfig();
FeatureFlags.update(config);
}
public static synchronized void update(@NonNull Map<String, Object> config) {
Map<String, Object> memory = REMOTE_VALUES;
Map<String, Object> disk = parseStoredConfig(SignalStore.remoteConfigValues().getPendingConfig());
@ -333,7 +343,7 @@ public final class FeatureFlags {
return getBoolean(MP4_GIF_SEND_SUPPORT, false);
}
public static @Nullable String getMediaQualityLevels() {
public static @NonNull String getMediaQualityLevels() {
return getString(MEDIA_QUALITY_LEVELS, "");
}
@ -352,6 +362,11 @@ public final class FeatureFlags {
return getBoolean(SENDER_KEY, false);
}
/** A comma-delimited list of country codes that should not be told about SMS during onboarding. */
public static @NonNull String suggestSmsBlacklist() {
return getString(SUGGEST_SMS_BLACKLIST, "");
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);

View file

@ -8,11 +8,15 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.mms.PushMediaConstraints;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* Provide access to locale specific values within feature flags following the locale CSV-Colon format.
@ -47,6 +51,16 @@ public final class LocaleFeatureFlags {
return Optional.ofNullable(PushMediaConstraints.MediaConfig.forLevel(level));
}
/**
* Whether or not you should suggest SMS during onboarding.
*/
public static boolean shouldSuggestSms() {
Set<String> blacklist = new HashSet<>(Arrays.asList(FeatureFlags.suggestSmsBlacklist().split(",")));
String countryCode = String.valueOf(PhoneNumberFormatter.getLocalCountryCode());
return !blacklist.contains(countryCode);
}
/**
* 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