diff --git a/app/src/main/java/org/thoughtcrime/securesms/AppCapabilities.java b/app/src/main/java/org/thoughtcrime/securesms/AppCapabilities.java index 9a043f092a..cdbc4cc48b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/AppCapabilities.java +++ b/app/src/main/java/org/thoughtcrime/securesms/AppCapabilities.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms; +import org.thoughtcrime.securesms.stories.Stories; import org.thoughtcrime.securesms.util.FeatureFlags; import org.whispersystems.signalservice.api.account.AccountAttributes; @@ -20,6 +21,6 @@ public final class AppCapabilities { * asking if the user has set a Signal PIN or not. */ public static AccountAttributes.Capabilities getCapabilities(boolean storageCapable) { - return new AccountAttributes.Capabilities(UUID_CAPABLE, GV2_CAPABLE, storageCapable, GV1_MIGRATION, SENDER_KEY, ANNOUNCEMENT_GROUPS, CHANGE_NUMBER, FeatureFlags.stories(), FeatureFlags.giftBadgeReceiveSupport(), FeatureFlags.phoneNumberPrivacy()); + return new AccountAttributes.Capabilities(UUID_CAPABLE, GV2_CAPABLE, storageCapable, GV1_MIGRATION, SENDER_KEY, ANNOUNCEMENT_GROUPS, CHANGE_NUMBER, Stories.isFeatureFlagEnabled(), FeatureFlags.giftBadgeReceiveSupport(), FeatureFlags.phoneNumberPrivacy()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt index 24e6b67c4f..e015490936 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.kt @@ -89,6 +89,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.storage.StorageRecordUpdate import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.storage.StorageSyncModels +import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.GroupUtil @@ -1245,7 +1246,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) : * @return All storage IDs for synced records, excluding the ones that need to be deleted. */ fun getContactStorageSyncIdsMap(): Map { - val (inPart, args) = if (FeatureFlags.stories() && Recipient.self().storiesCapability == Recipient.Capability.SUPPORTED) { + val (inPart, args) = if (Stories.isFeatureAvailable()) { "(?, ?)" to SqlUtil.buildArgs(GroupType.NONE.id, Recipient.self().id, GroupType.SIGNAL_V1.id, GroupType.DISTRIBUTION_LIST.id) } else { "(?)" to SqlUtil.buildArgs(GroupType.NONE.id, Recipient.self().id, GroupType.SIGNAL_V1.id) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index b29caa59db..67adb16078 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -2518,7 +2518,7 @@ public final class MessageContentProcessor { @NonNull SignalServiceReceiptMessage message, @NonNull Recipient senderRecipient) { - boolean shouldOnlyProcessStories = FeatureFlags.stories() && !SignalStore.storyValues().isFeatureDisabled() && !TextSecurePreferences.isReadReceiptsEnabled(context); + boolean shouldOnlyProcessStories = Stories.isFeatureFlagEnabled() && !SignalStore.storyValues().isFeatureDisabled() && !TextSecurePreferences.isReadReceiptsEnabled(context); if (!TextSecurePreferences.isReadReceiptsEnabled(context) && !shouldOnlyProcessStories) { log("Ignoring viewed receipts for IDs: " + Util.join(message.getTimestamps(), ", ")); diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt index 5ed4c70f56..1e53151ea2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.BottomSheetUtil import org.thoughtcrime.securesms.util.FeatureFlags +import org.thoughtcrime.securesms.util.LocaleFeatureFlags import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.hasLinkPreview import java.util.Optional @@ -45,6 +46,9 @@ import kotlin.math.min import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds +/** + * Collection of helper methods and constants for dealing with the stories feature. + */ object Stories { private val TAG = Log.tag(Stories::class.java) @@ -55,11 +59,34 @@ object Stories { @JvmField val MAX_VIDEO_DURATION_MILLIS: Long = (31.seconds - 1.milliseconds).inWholeMilliseconds + /** + * Whether the feature is enabled at the flag level. + * + * `stories` will override `isInStoriesCountry` so as to not disable stories for those with + * that flag already enabled. + * + * Note: In general, you should prefer `isFeatureAvailable`. + */ @JvmStatic - fun isFeatureAvailable(): Boolean { - return SignalStore.account().isRegistered && FeatureFlags.stories() && Recipient.self().storiesCapability == Recipient.Capability.SUPPORTED + fun isFeatureFlagEnabled(): Boolean { + return FeatureFlags.stories() || LocaleFeatureFlags.isInStoriesCountry() } + /** + * Whether or not the user has access to stories. This checks: + * + * - Registration status + * - Flag status + * - Capabilities + */ + @JvmStatic + fun isFeatureAvailable(): Boolean { + return SignalStore.account().isRegistered && isFeatureFlagEnabled() && Recipient.self().storiesCapability == Recipient.Capability.SUPPORTED + } + + /** + * Whether or not the user has the Stories feature enabled. + */ @JvmStatic fun isFeatureEnabled(): Boolean { return isFeatureAvailable() && !SignalStore.storyValues().isFeatureDisabled diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 1977b6767b..28104510d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -105,6 +105,7 @@ public final class FeatureFlags { private static final String CDS_V2_LOAD_TEST = "android.cdsV2LoadTest"; private static final String SMS_EXPORTER = "android.sms.exporter"; private static final String CDS_V2_COMPAT = "android.cdsV2Compat.3"; + public static final String STORIES_LOCALE = "android.stories.locale"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -161,7 +162,8 @@ public final class FeatureFlags { RECIPIENT_MERGE_V2, CDS_V2_LOAD_TEST, SMS_EXPORTER, - CDS_V2_COMPAT + CDS_V2_COMPAT, + STORIES_LOCALE ); @VisibleForTesting @@ -481,6 +483,13 @@ public final class FeatureFlags { return getBoolean(STORIES_TEXT_FUNCTIONS, false); } + /** + * List of locales in which stories have been enabled. Overridden by the stories flag. + */ + public static @NonNull String storiesLocale() { + return getString(STORIES_LOCALE, ""); + } + /** * Whether or not donor badges should be displayed throughout the app. */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/LocaleFeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/LocaleFeatureFlags.java index d1a933cabb..38519fce7a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/LocaleFeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/LocaleFeatureFlags.java @@ -44,6 +44,13 @@ public final class LocaleFeatureFlags { return Optional.ofNullable(PushMediaConstraints.MediaConfig.forLevel(level)); } + /** + * In story group for given country code + */ + public static boolean isInStoriesCountry() { + return isEnabled(FeatureFlags.STORIES_LOCALE, FeatureFlags.storiesLocale()); + } + /** * Whether or not you should suggest SMS during onboarding. */