diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java index 1ed081a39d..4abcd77179 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -271,7 +271,7 @@ public final class ContactSelectionListFragment extends LoggingFragment RecyclerViewConcatenateAdapterStickyHeader concatenateAdapter = new RecyclerViewConcatenateAdapterStickyHeader(); if (listCallback != null) { - if (FeatureFlags.groupsV2create() && FeatureFlags.groupsV2internalTest()) { + if (FeatureFlags.groupsV2create() && FeatureFlags.internalUser()) { headerAdapter = new FixedViewsAdapter(createNewGroupItem(listCallback), createNewGroupsV1GroupItem(listCallback)); } else { headerAdapter = new FixedViewsAdapter(createNewGroupItem(listCallback)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java index 7a9393f3bb..6c6c3750f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2.java @@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2 import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper; import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage; import org.thoughtcrime.securesms.profiles.AvatarHelper; @@ -117,6 +118,10 @@ final class GroupManagerV2 { GroupCandidate self = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId()); Set candidates = new HashSet<>(groupCandidateHelper.recipientIdsToCandidates(members)); + if (SignalStore.internalValues().forceGv2Invites()) { + candidates = GroupCandidate.withoutProfileKeyCredentials(candidates); + } + if (!self.hasProfileKeyCredential()) { Log.w(TAG, "Cannot create a V2 group as self does not have a versioned profile"); throw new MembershipNotSuitableForV2Exception("Cannot create a V2 group as self does not have a versioned profile"); @@ -186,6 +191,11 @@ final class GroupManagerV2 { } Set groupCandidates = groupCandidateHelper.recipientIdsToCandidates(new HashSet<>(newMembers)); + + if (SignalStore.internalValues().forceGv2Invites()) { + groupCandidates = GroupCandidate.withoutProfileKeyCredentials(groupCandidates); + } + return commitChangeWithConflictResolution(groupOperations.createModifyGroupMembershipChange(groupCandidates, selfUuid)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java new file mode 100644 index 0000000000..e768c8ec9b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java @@ -0,0 +1,20 @@ +package org.thoughtcrime.securesms.keyvalue; + +import org.thoughtcrime.securesms.util.FeatureFlags; + +public final class InternalValues extends SignalStoreValues { + + public static final String GV2_FORCE_INVITES = "internal.gv2.force_invites"; + + InternalValues(KeyValueStore store) { + super(store); + } + + @Override + void onFirstEverAppLaunch() { + } + + public synchronized boolean forceGv2Invites() { + return FeatureFlags.internalUser() && getBoolean(GV2_FORCE_INVITES, false); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java index a71002b688..8cc979255f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SignalStore.java @@ -23,6 +23,7 @@ public final class SignalStore { private final UiHints uiHints; private final TooltipValues tooltipValues; private final MiscellaneousValues misc; + private final InternalValues internalValues; private SignalStore() { this.store = ApplicationDependencies.getKeyValueStore(); @@ -34,6 +35,7 @@ public final class SignalStore { this.uiHints = new UiHints(store); this.tooltipValues = new TooltipValues(store); this.misc = new MiscellaneousValues(store); + this.internalValues = new InternalValues(store); } public static void onFirstEverAppLaunch() { @@ -45,6 +47,7 @@ public final class SignalStore { uiHints().onFirstEverAppLaunch(); tooltips().onFirstEverAppLaunch(); misc().onFirstEverAppLaunch(); + internalValues().onFirstEverAppLaunch(); } public static @NonNull KbsValues kbsValues() { @@ -79,6 +82,10 @@ public final class SignalStore { return INSTANCE.misc; } + public static @NonNull InternalValues internalValues() { + return INSTANCE.internalValues; + } + public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() { return new GroupsV2AuthorizationSignalStoreCache(getStore()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index 3780edb131..1093a45d22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -16,19 +16,19 @@ import androidx.appcompat.app.AlertDialog; import androidx.preference.CheckBoxPreference; import androidx.preference.Preference; -import org.thoughtcrime.securesms.BuildConfig; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.logging.Log; - import com.google.firebase.iid.FirebaseInstanceId; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; +import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactIdentityManager; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity; import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity; +import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.whispersystems.libsignal.util.guava.Optional; @@ -42,6 +42,7 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment { private static final String PUSH_MESSAGING_PREF = "pref_toggle_push_messaging"; private static final String SUBMIT_DEBUG_LOG_PREF = "pref_submit_debug_logs"; + private static final String INTERNAL_PREF = "pref_internal"; private static final int PICK_IDENTITY_CONTACT = 1; @@ -54,6 +55,22 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment { Preference submitDebugLog = this.findPreference(SUBMIT_DEBUG_LOG_PREF); submitDebugLog.setOnPreferenceClickListener(new SubmitDebugLogListener()); submitDebugLog.setSummary(getVersion(getActivity())); + + Preference internalPreference = this.findPreference(INTERNAL_PREF); + internalPreference.setVisible(FeatureFlags.internalUser()); + internalPreference.setOnPreferenceClickListener(preference -> { + if (FeatureFlags.internalUser()) { + requireActivity().getSupportFragmentManager() + .beginTransaction() + .setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end) + .replace(android.R.id.content, new InternalOptionsPreferenceFragment()) + .addToBackStack(null) + .commit(); + return true; + } else { + return false; + } + }); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/InternalOptionsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/InternalOptionsPreferenceFragment.java new file mode 100644 index 0000000000..b8625d5163 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/InternalOptionsPreferenceFragment.java @@ -0,0 +1,39 @@ +package org.thoughtcrime.securesms.preferences; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.preference.PreferenceDataStore; + +import org.thoughtcrime.securesms.ApplicationPreferencesActivity; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; +import org.thoughtcrime.securesms.keyvalue.InternalValues; +import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.logging.Log; + +public class InternalOptionsPreferenceFragment extends CorrectedPreferenceFragment { + private static final String TAG = Log.tag(InternalOptionsPreferenceFragment.class); + + @Override + public void onCreate(Bundle paramBundle) { + super.onCreate(paramBundle); + } + + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.preferences_internal); + + PreferenceDataStore preferenceDataStore = SignalStore.getPreferenceDataStore(); + + SwitchPreferenceCompat forceGv2Preference = (SwitchPreferenceCompat) this.findPreference(InternalValues.GV2_FORCE_INVITES); + forceGv2Preference.setPreferenceDataStore(preferenceDataStore); + forceGv2Preference.setChecked(SignalStore.internalValues().forceGv2Invites()); + } + + @Override + public void onResume() { + super.onResume(); + ((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__internal_preferences); + } +} 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 7cf6f84222..cb54343a6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -62,8 +62,8 @@ public final class FeatureFlags { private static final String GROUPS_V2 = "android.groupsv2"; private static final String GROUPS_V2_CREATE = "android.groupsv2.create"; private static final String GROUPS_V2_CAPACITY = "android.groupsv2.capacity"; - private static final String GROUPS_V2_INTERNAL_TEST = "android.groupsv2.internalTest"; private static final String CDS = "android.cds"; + private static final String INTERNAL_USER = "android.internalUser"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -82,7 +82,7 @@ public final class FeatureFlags { GROUPS_V2, GROUPS_V2_CREATE, GROUPS_V2_CAPACITY, - GROUPS_V2_INTERNAL_TEST + INTERNAL_USER ); /** @@ -252,9 +252,9 @@ public final class FeatureFlags { return getInteger(GROUPS_V2_CAPACITY, 100); } - /** Groups v2 UI for internal testing. */ - public static boolean groupsV2internalTest() { - return groupsV2() && getBoolean(GROUPS_V2_INTERNAL_TEST, false); + /** Internal testing extensions. */ + public static boolean internalUser() { + return getBoolean(INTERNAL_USER, false); } /** Whether or not to use the new contact discovery service endpoint, which supports UUIDs. */ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26dd6f80f4..b2dbabb7af 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1941,6 +1941,12 @@ Enable sealed sender for incoming messages from non-contacts and people with whom you have not shared your profile. Learn more + + Internal Preferences + Groups V2 + Force Invites + Members will not be added directly to a GV2 even if they could be. + diff --git a/app/src/main/res/xml/preferences_advanced.xml b/app/src/main/res/xml/preferences_advanced.xml index ddd5bb4462..b6d70eb33f 100644 --- a/app/src/main/res/xml/preferences_advanced.xml +++ b/app/src/main/res/xml/preferences_advanced.xml @@ -1,6 +1,7 @@ - + + + + diff --git a/app/src/main/res/xml/preferences_internal.xml b/app/src/main/res/xml/preferences_internal.xml new file mode 100644 index 0000000000..a36cfa866e --- /dev/null +++ b/app/src/main/res/xml/preferences_internal.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupCandidate.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupCandidate.java index f5d5a6f3cb..69d4d85398 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupCandidate.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/groupsv2/GroupCandidate.java @@ -5,7 +5,9 @@ import org.whispersystems.libsignal.util.guava.Optional; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; /** @@ -39,6 +41,21 @@ public final class GroupCandidate { return profileKeyCredential.isPresent(); } + public static Set withoutProfileKeyCredentials(Set groupCandidates) { + HashSet result = new HashSet<>(groupCandidates.size()); + + for (GroupCandidate candidate: groupCandidates) { + result.add(candidate.withoutProfileKeyCredential()); + } + + return result; + } + + public GroupCandidate withoutProfileKeyCredential() { + return hasProfileKeyCredential() ? new GroupCandidate(uuid, Optional.absent()) + : this; + } + public GroupCandidate withProfileKeyCredential(ProfileKeyCredential profileKeyCredential) { return new GroupCandidate(uuid, Optional.of(profileKeyCredential)); }