mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 08:08:33 +00:00
Add support for remote feature flags.
This commit is contained in:
parent
b8602ee004
commit
55e9f8722f
@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
||||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
@ -71,6 +72,7 @@ import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
|||||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||||
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
import org.thoughtcrime.securesms.stickers.BlessedPacks;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
@ -133,6 +135,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
initializeBlobProvider();
|
initializeBlobProvider();
|
||||||
initializeCleanup();
|
initializeCleanup();
|
||||||
initializeCameraX();
|
initializeCameraX();
|
||||||
|
FeatureFlags.init();
|
||||||
NotificationChannels.create(this);
|
NotificationChannels.create(this);
|
||||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||||
|
|
||||||
|
@ -42,7 +42,6 @@ import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
|||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, List<Recipient>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getRecipientName(Recipient recipient) {
|
private String getRecipientName(Recipient recipient) {
|
||||||
if (FeatureFlags.PROFILE_DISPLAY) return recipient.getDisplayName(context);
|
if (FeatureFlags.profileDisplay()) return recipient.getDisplayName(context);
|
||||||
|
|
||||||
String name = recipient.toShortString(context);
|
String name = recipient.toShortString(context);
|
||||||
|
|
||||||
|
@ -434,7 +434,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
|||||||
colorPreference.setColors(MaterialColors.CONVERSATION_PALETTE.asConversationColorArray(requireActivity()));
|
colorPreference.setColors(MaterialColors.CONVERSATION_PALETTE.asConversationColorArray(requireActivity()));
|
||||||
colorPreference.setColor(recipient.getColor().toActionBarColor(requireActivity()));
|
colorPreference.setColor(recipient.getColor().toActionBarColor(requireActivity()));
|
||||||
|
|
||||||
if (FeatureFlags.PROFILE_DISPLAY) {
|
if (FeatureFlags.profileDisplay()) {
|
||||||
aboutPreference.setTitle(recipient.getDisplayName(requireContext()));
|
aboutPreference.setTitle(recipient.getDisplayName(requireContext()));
|
||||||
aboutPreference.setSummary(recipient.resolve().getE164().or(""));
|
aboutPreference.setSummary(recipient.resolve().getE164().or(""));
|
||||||
} else {
|
} else {
|
||||||
|
@ -275,7 +275,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
|||||||
byte[] localId;
|
byte[] localId;
|
||||||
byte[] remoteId;
|
byte[] remoteId;
|
||||||
|
|
||||||
if (FeatureFlags.UUIDS && recipient.resolve().getUuid().isPresent()) {
|
if (FeatureFlags.uuids() && recipient.resolve().getUuid().isPresent()) {
|
||||||
Log.i(TAG, "Using UUID (version 2).");
|
Log.i(TAG, "Using UUID (version 2).");
|
||||||
version = 2;
|
version = 2;
|
||||||
localId = UuidUtil.toByteArray(TextSecurePreferences.getLocalUuid(requireContext()));
|
localId = UuidUtil.toByteArray(TextSecurePreferences.getLocalUuid(requireContext()));
|
||||||
|
@ -61,7 +61,7 @@ public class FromTextView extends EmojiTextView {
|
|||||||
|
|
||||||
if (recipient.isLocalNumber()) {
|
if (recipient.isLocalNumber()) {
|
||||||
builder.append(getContext().getString(R.string.note_to_self));
|
builder.append(getContext().getString(R.string.note_to_self));
|
||||||
} else if (!FeatureFlags.PROFILE_DISPLAY && recipient.getName(getContext()) == null && !recipient.getProfileName().isEmpty()) {
|
} else if (!FeatureFlags.profileDisplay() && recipient.getName(getContext()) == null && !recipient.getProfileName().isEmpty()) {
|
||||||
SpannableString profileName = new SpannableString(" (~" + recipient.getProfileName().toString() + ") ");
|
SpannableString profileName = new SpannableString(" (~" + recipient.getProfileName().toString() + ") ");
|
||||||
profileName.setSpan(new CenterAlignedRelativeSizeSpan(0.75f), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
profileName.setSpan(new CenterAlignedRelativeSizeSpan(0.75f), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
profileName.setSpan(new TypefaceSpan("sans-serif-light"), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
profileName.setSpan(new TypefaceSpan("sans-serif-light"), 0, profileName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
@ -370,7 +370,7 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObs
|
|||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
.into(this.photo);
|
.into(this.photo);
|
||||||
|
|
||||||
if (FeatureFlags.PROFILE_DISPLAY) {
|
if (FeatureFlags.profileDisplay()) {
|
||||||
this.name.setText(recipient.getDisplayName(getContext()));
|
this.name.setText(recipient.getDisplayName(getContext()));
|
||||||
|
|
||||||
if (recipient.getE164().isPresent()) {
|
if (recipient.getE164().isPresent()) {
|
||||||
|
@ -145,15 +145,15 @@ public class ContactsCursorLoader extends CursorLoader {
|
|||||||
cursorList.addAll(getContactsCursors());
|
cursorList.addAll(getContactsCursors());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FeatureFlags.USERNAMES && NumberUtil.isVisuallyValidNumberOrEmail(filter)) {
|
if (FeatureFlags.usernames() && NumberUtil.isVisuallyValidNumberOrEmail(filter)) {
|
||||||
cursorList.add(getPhoneNumberSearchHeaderCursor());
|
cursorList.add(getPhoneNumberSearchHeaderCursor());
|
||||||
cursorList.add(getNewNumberCursor());
|
cursorList.add(getNewNumberCursor());
|
||||||
} else if (!FeatureFlags.USERNAMES && NumberUtil.isValidSmsOrEmail(filter)){
|
} else if (!FeatureFlags.usernames() && NumberUtil.isValidSmsOrEmail(filter)){
|
||||||
cursorList.add(getContactsHeaderCursor());
|
cursorList.add(getContactsHeaderCursor());
|
||||||
cursorList.add(getNewNumberCursor());
|
cursorList.add(getNewNumberCursor());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FeatureFlags.USERNAMES && UsernameUtil.isValidUsernameForSearch(filter)) {
|
if (FeatureFlags.usernames() && UsernameUtil.isValidUsernameForSearch(filter)) {
|
||||||
cursorList.add(getUsernameSearchHeaderCursor());
|
cursorList.add(getUsernameSearchHeaderCursor());
|
||||||
cursorList.add(getUsernameSearchCursor());
|
cursorList.add(getUsernameSearchCursor());
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,14 @@ public class DirectoryHelper {
|
|||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
public static void refreshDirectory(@NonNull Context context, boolean notifyOfNewUsers) throws IOException {
|
public static void refreshDirectory(@NonNull Context context, boolean notifyOfNewUsers) throws IOException {
|
||||||
if (FeatureFlags.UUIDS) {
|
if (FeatureFlags.uuids()) {
|
||||||
// TODO [greyson] Create a DirectoryHelperV2 when appropriate.
|
// TODO [greyson] Create a DirectoryHelperV2 when appropriate.
|
||||||
DirectoryHelperV1.refreshDirectory(context, notifyOfNewUsers);
|
DirectoryHelperV1.refreshDirectory(context, notifyOfNewUsers);
|
||||||
} else {
|
} else {
|
||||||
DirectoryHelperV1.refreshDirectory(context, notifyOfNewUsers);
|
DirectoryHelperV1.refreshDirectory(context, notifyOfNewUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FeatureFlags.STORAGE_SERVICE) {
|
if (FeatureFlags.storageService()) {
|
||||||
ApplicationDependencies.getJobManager().add(new StorageSyncJob());
|
ApplicationDependencies.getJobManager().add(new StorageSyncJob());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,14 +34,14 @@ public class DirectoryHelper {
|
|||||||
RegisteredState originalRegisteredState = recipient.resolve().getRegistered();
|
RegisteredState originalRegisteredState = recipient.resolve().getRegistered();
|
||||||
RegisteredState newRegisteredState = null;
|
RegisteredState newRegisteredState = null;
|
||||||
|
|
||||||
if (FeatureFlags.UUIDS) {
|
if (FeatureFlags.uuids()) {
|
||||||
// TODO [greyson] Create a DirectoryHelperV2 when appropriate.
|
// TODO [greyson] Create a DirectoryHelperV2 when appropriate.
|
||||||
newRegisteredState = DirectoryHelperV1.refreshDirectoryFor(context, recipient, notifyOfNewUsers);
|
newRegisteredState = DirectoryHelperV1.refreshDirectoryFor(context, recipient, notifyOfNewUsers);
|
||||||
} else {
|
} else {
|
||||||
newRegisteredState = DirectoryHelperV1.refreshDirectoryFor(context, recipient, notifyOfNewUsers);
|
newRegisteredState = DirectoryHelperV1.refreshDirectoryFor(context, recipient, notifyOfNewUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FeatureFlags.STORAGE_SERVICE && newRegisteredState != originalRegisteredState) {
|
if (FeatureFlags.storageService() && newRegisteredState != originalRegisteredState) {
|
||||||
ApplicationDependencies.getJobManager().add(new StorageSyncJob());
|
ApplicationDependencies.getJobManager().add(new StorageSyncJob());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2014,7 +2014,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setGroupShareProfileReminder(@NonNull Recipient recipient) {
|
private void setGroupShareProfileReminder(@NonNull Recipient recipient) {
|
||||||
if (!FeatureFlags.MESSAGE_REQUESTS && recipient.isPushGroup() && !recipient.isProfileSharing()) {
|
if (!FeatureFlags.messageRequests() && recipient.isPushGroup() && !recipient.isProfileSharing()) {
|
||||||
groupShareProfileView.get().setRecipient(recipient);
|
groupShareProfileView.get().setRecipient(recipient);
|
||||||
groupShareProfileView.get().setVisibility(View.VISIBLE);
|
groupShareProfileView.get().setVisibility(View.VISIBLE);
|
||||||
} else if (groupShareProfileView.resolved()) {
|
} else if (groupShareProfileView.resolved()) {
|
||||||
|
@ -713,7 +713,7 @@ public class ConversationFragment extends Fragment
|
|||||||
setLastSeen(loader.getLastSeen());
|
setLastSeen(loader.getLastSeen());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FeatureFlags.MESSAGE_REQUESTS) {
|
if (FeatureFlags.messageRequests()) {
|
||||||
if (!loader.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isProfileSharing() && !recipient.get().isBlocked() && recipient.get().isRegistered()) {
|
if (!loader.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isProfileSharing() && !recipient.get().isBlocked() && recipient.get().isRegistered()) {
|
||||||
listener.onMessageRequest();
|
listener.onMessageRequest();
|
||||||
} else {
|
} else {
|
||||||
@ -994,8 +994,8 @@ public class ConversationFragment extends Fragment
|
|||||||
|
|
||||||
if (actionMode != null) return;
|
if (actionMode != null) return;
|
||||||
|
|
||||||
if (FeatureFlags.REACTION_SENDING &&
|
if (FeatureFlags.reactionSending() &&
|
||||||
messageRecord.isSecure() &&
|
messageRecord.isSecure() &&
|
||||||
((ConversationAdapter) list.getAdapter()).getSelectedItems().isEmpty())
|
((ConversationAdapter) list.getAdapter()).getSelectedItems().isEmpty())
|
||||||
{
|
{
|
||||||
isReacting = true;
|
isReacting = true;
|
||||||
|
@ -960,7 +960,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
|||||||
private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) {
|
private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) {
|
||||||
if (groupThread && !messageRecord.isOutgoing()) {
|
if (groupThread && !messageRecord.isOutgoing()) {
|
||||||
|
|
||||||
if (FeatureFlags.PROFILE_DISPLAY) {
|
if (FeatureFlags.profileDisplay()) {
|
||||||
this.groupSender.setText(recipient.getDisplayName(getContext()));
|
this.groupSender.setText(recipient.getDisplayName(getContext()));
|
||||||
this.groupSenderProfileName.setVisibility(View.GONE);
|
this.groupSenderProfileName.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -126,7 +126,7 @@ public class ConversationTitleView extends RelativeLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setRecipientTitle(Recipient recipient) {
|
private void setRecipientTitle(Recipient recipient) {
|
||||||
if (FeatureFlags.PROFILE_DISPLAY) {
|
if (FeatureFlags.profileDisplay()) {
|
||||||
if (recipient.isGroup()) setGroupRecipientTitle(recipient);
|
if (recipient.isGroup()) setGroupRecipientTitle(recipient);
|
||||||
else if (recipient.isLocalNumber()) setSelfTitle();
|
else if (recipient.isLocalNumber()) setSelfTitle();
|
||||||
else setIndividualRecipientTitle(recipient);
|
else setIndividualRecipientTitle(recipient);
|
||||||
@ -166,7 +166,7 @@ public class ConversationTitleView extends RelativeLayout {
|
|||||||
private void setGroupRecipientTitle(Recipient recipient) {
|
private void setGroupRecipientTitle(Recipient recipient) {
|
||||||
String localNumber = TextSecurePreferences.getLocalNumber(getContext());
|
String localNumber = TextSecurePreferences.getLocalNumber(getContext());
|
||||||
|
|
||||||
if (FeatureFlags.PROFILE_DISPLAY) {
|
if (FeatureFlags.profileDisplay()) {
|
||||||
this.title.setText(recipient.getDisplayName(getContext()));
|
this.title.setText(recipient.getDisplayName(getContext()));
|
||||||
} else {
|
} else {
|
||||||
this.title.setText(recipient.getName(getContext()));
|
this.title.setText(recipient.getName(getContext()));
|
||||||
|
@ -1209,7 +1209,7 @@ public class RecipientDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void markDirty(@NonNull RecipientId recipientId, @NonNull DirtyState dirtyState) {
|
void markDirty(@NonNull RecipientId recipientId, @NonNull DirtyState dirtyState) {
|
||||||
if (!FeatureFlags.STORAGE_SERVICE) return;
|
if (!FeatureFlags.storageService()) return;
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues(1);
|
ContentValues contentValues = new ContentValues(1);
|
||||||
contentValues.put(DIRTY, dirtyState.getId());
|
contentValues.put(DIRTY, dirtyState.getId());
|
||||||
|
@ -69,7 +69,7 @@ public class ApplicationDependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized @NonNull KeyBackupService getKeyBackupService() {
|
public static synchronized @NonNull KeyBackupService getKeyBackupService() {
|
||||||
if (!FeatureFlags.KBS) throw new AssertionError();
|
if (!FeatureFlags.kbs()) throw new AssertionError();
|
||||||
return getSignalServiceAccountManager().getKeyBackupService(IasKeyStore.getIasKeyStore(application),
|
return getSignalServiceAccountManager().getKeyBackupService(IasKeyStore.getIasKeyStore(application),
|
||||||
BuildConfig.KEY_BACKUP_ENCLAVE_NAME,
|
BuildConfig.KEY_BACKUP_ENCLAVE_NAME,
|
||||||
BuildConfig.KEY_BACKUP_MRENCLAVE,
|
BuildConfig.KEY_BACKUP_MRENCLAVE,
|
||||||
|
@ -75,6 +75,7 @@ public final class JobManagerFactories {
|
|||||||
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
|
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
|
||||||
put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory());
|
put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory());
|
||||||
put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory());
|
put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory());
|
||||||
|
put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory());
|
||||||
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
|
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
|
||||||
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory());
|
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory());
|
||||||
put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory());
|
put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory());
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class RemoteConfigRefreshJob extends BaseJob {
|
||||||
|
|
||||||
|
public static final String KEY = "RemoteConfigRefreshJob";
|
||||||
|
|
||||||
|
public RemoteConfigRefreshJob() {
|
||||||
|
this(new Job.Parameters.Builder()
|
||||||
|
.setQueue("RemoteConfigRefreshJob")
|
||||||
|
.setMaxInstances(1)
|
||||||
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private RemoteConfigRefreshJob(@NonNull Parameters parameters) {
|
||||||
|
super(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Data serialize() {
|
||||||
|
return Data.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull String getFactoryKey() {
|
||||||
|
return KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRun() throws Exception {
|
||||||
|
Map<String, Boolean> config = ApplicationDependencies.getSignalServiceAccountManager().getRemoteConfig();
|
||||||
|
FeatureFlags.updateDiskCache(config);
|
||||||
|
SignalStore.setRemoteConfigLastFetchTime(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||||
|
return e instanceof PushNetworkException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Factory implements Job.Factory<RemoteConfigRefreshJob> {
|
||||||
|
@Override
|
||||||
|
public @NonNull RemoteConfigRefreshJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||||
|
return new RemoteConfigRefreshJob(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -102,7 +102,7 @@ public class RetrieveProfileJob extends BaseJob {
|
|||||||
|
|
||||||
setProfileName(recipient, profile.getName());
|
setProfileName(recipient, profile.getName());
|
||||||
setProfileAvatar(recipient, profile.getAvatar());
|
setProfileAvatar(recipient, profile.getAvatar());
|
||||||
if (FeatureFlags.USERNAMES) setUsername(recipient, profile.getUsername());
|
if (FeatureFlags.usernames()) setUsername(recipient, profile.getUsername());
|
||||||
setProfileCapabilities(recipient, profile.getCapabilities());
|
setProfileCapabilities(recipient, profile.getCapabilities());
|
||||||
setIdentityKey(recipient, profile.getIdentityKey());
|
setIdentityKey(recipient, profile.getIdentityKey());
|
||||||
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
|
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
|
||||||
|
@ -67,7 +67,7 @@ public class StorageForcePushJob extends BaseJob {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRun() throws IOException, RetryLaterException {
|
protected void onRun() throws IOException, RetryLaterException {
|
||||||
if (!FeatureFlags.STORAGE_SERVICE) throw new AssertionError();
|
if (!FeatureFlags.storageService()) throw new AssertionError();
|
||||||
|
|
||||||
MasterKey kbsMasterKey = SignalStore.kbsValues().getPinBackedMasterKey();
|
MasterKey kbsMasterKey = SignalStore.kbsValues().getPinBackedMasterKey();
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ public class StorageSyncJob extends BaseJob {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRun() throws IOException, RetryLaterException {
|
protected void onRun() throws IOException, RetryLaterException {
|
||||||
if (!FeatureFlags.STORAGE_SERVICE) throw new AssertionError();
|
if (!FeatureFlags.storageService()) throw new AssertionError();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boolean needsMultiDeviceSync = performSync();
|
boolean needsMultiDeviceSync = performSync();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.keyvalue;
|
package org.thoughtcrime.securesms.keyvalue;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
@ -10,12 +12,32 @@ import org.thoughtcrime.securesms.logging.SignalUncaughtExceptionHandler;
|
|||||||
*/
|
*/
|
||||||
public final class SignalStore {
|
public final class SignalStore {
|
||||||
|
|
||||||
|
private static final String REMOTE_CONFIG = "remote_config";
|
||||||
|
private static final String REMOTE_CONFIG_LAST_FETCH_TIME = "remote_config_last_fetch_time";
|
||||||
|
|
||||||
private SignalStore() {}
|
private SignalStore() {}
|
||||||
|
|
||||||
public static KbsValues kbsValues() {
|
public static KbsValues kbsValues() {
|
||||||
return new KbsValues(getStore());
|
return new KbsValues(getStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getRemoteConfig() {
|
||||||
|
return getStore().getString(REMOTE_CONFIG, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setRemoteConfig(String value) {
|
||||||
|
putString(REMOTE_CONFIG, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getRemoteConfigLastFetchTime() {
|
||||||
|
return getStore().getLong(REMOTE_CONFIG_LAST_FETCH_TIME, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setRemoteConfigLastFetchTime(long time) {
|
||||||
|
putLong(REMOTE_CONFIG_LAST_FETCH_TIME, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures any pending writes are finished. Only intended to be called by
|
* Ensures any pending writes are finished. Only intended to be called by
|
||||||
* {@link SignalUncaughtExceptionHandler}.
|
* {@link SignalUncaughtExceptionHandler}.
|
||||||
|
@ -126,7 +126,7 @@ public final class RegistrationLockDialog {
|
|||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
RegistrationLockReminders.scheduleReminder(context, true);
|
RegistrationLockReminders.scheduleReminder(context, true);
|
||||||
|
|
||||||
if (FeatureFlags.KBS) {
|
if (FeatureFlags.kbs()) {
|
||||||
Log.i(TAG, "Pin V1 successfully remembered, scheduling a migration to V2");
|
Log.i(TAG, "Pin V1 successfully remembered, scheduling a migration to V2");
|
||||||
ApplicationDependencies.getJobManager().add(new RegistrationPinV2MigrationJob());
|
ApplicationDependencies.getJobManager().add(new RegistrationPinV2MigrationJob());
|
||||||
}
|
}
|
||||||
@ -201,7 +201,7 @@ public final class RegistrationLockDialog {
|
|||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... voids) {
|
protected Boolean doInBackground(Void... voids) {
|
||||||
try {
|
try {
|
||||||
if (!FeatureFlags.KBS) {
|
if (!FeatureFlags.kbs()) {
|
||||||
Log.i(TAG, "Setting V1 pin");
|
Log.i(TAG, "Setting V1 pin");
|
||||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||||
accountManager.setPin(pinValue);
|
accountManager.setPin(pinValue);
|
||||||
@ -282,7 +282,7 @@ public final class RegistrationLockDialog {
|
|||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... voids) {
|
protected Boolean doInBackground(Void... voids) {
|
||||||
try {
|
try {
|
||||||
if (!FeatureFlags.KBS) {
|
if (!FeatureFlags.kbs()) {
|
||||||
Log.i(TAG, "Removing v1 registration lock pin from server");
|
Log.i(TAG, "Removing v1 registration lock pin from server");
|
||||||
ApplicationDependencies.getSignalServiceAccountManager().removeV1Pin();
|
ApplicationDependencies.getSignalServiceAccountManager().removeV1Pin();
|
||||||
} else {
|
} else {
|
||||||
|
@ -52,6 +52,8 @@ import androidx.fragment.app.Fragment;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
@ -61,6 +63,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.logsubmit.util.Scrubber;
|
import org.thoughtcrime.securesms.logsubmit.util.Scrubber;
|
||||||
import org.thoughtcrime.securesms.util.BucketInfo;
|
import org.thoughtcrime.securesms.util.BucketInfo;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.FrameRateTracker;
|
import org.thoughtcrime.securesms.util.FrameRateTracker;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
@ -111,6 +114,7 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
private static final String HEADER_POWER = "========== POWER ==========";
|
private static final String HEADER_POWER = "========== POWER ==========";
|
||||||
private static final String HEADER_THREADS = "===== BLOCKED THREADS =====";
|
private static final String HEADER_THREADS = "===== BLOCKED THREADS =====";
|
||||||
private static final String HEADER_PERMISSIONS = "======= PERMISSIONS =======";
|
private static final String HEADER_PERMISSIONS = "======= PERMISSIONS =======";
|
||||||
|
private static final String HEADER_FLAGS = "====== FEATURE FLAGS ======";
|
||||||
private static final String HEADER_LOGCAT = "========== LOGCAT =========";
|
private static final String HEADER_LOGCAT = "========== LOGCAT =========";
|
||||||
private static final String HEADER_LOGGER = "========== LOGGER =========";
|
private static final String HEADER_LOGGER = "========== LOGGER =========";
|
||||||
|
|
||||||
@ -411,6 +415,11 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
.append(buildBlockedThreads())
|
.append(buildBlockedThreads())
|
||||||
.append("\n\n\n");
|
.append("\n\n\n");
|
||||||
|
|
||||||
|
stringBuilder.append(HEADER_FLAGS)
|
||||||
|
.append("\n\n")
|
||||||
|
.append(buildFlags())
|
||||||
|
.append("\n\n\n");
|
||||||
|
|
||||||
stringBuilder.append(HEADER_PERMISSIONS)
|
stringBuilder.append(HEADER_PERMISSIONS)
|
||||||
.append("\n\n")
|
.append("\n\n")
|
||||||
.append(buildPermissions(context))
|
.append(buildPermissions(context))
|
||||||
@ -628,6 +637,28 @@ public class SubmitLogFragment extends Fragment {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CharSequence buildFlags() {
|
||||||
|
StringBuilder out = new StringBuilder();
|
||||||
|
Map<String, Boolean> remote = FeatureFlags.getRemoteValues();
|
||||||
|
Map<String, Boolean> forced = FeatureFlags.getForcedValues();
|
||||||
|
int remoteLength = Stream.of(remote.keySet()).map(String::length).max(Integer::compareTo).orElse(0);
|
||||||
|
int forcedLength = Stream.of(forced.keySet()).map(String::length).max(Integer::compareTo).orElse(0);
|
||||||
|
|
||||||
|
out.append("-- Remote\n");
|
||||||
|
for (Map.Entry<String, Boolean> entry : remote.entrySet()) {
|
||||||
|
out.append(Util.rightPad(entry.getKey(), remoteLength)).append(": ").append(entry.getValue()).append("\n");
|
||||||
|
}
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append("-- Forced\n");
|
||||||
|
for (Map.Entry<String, Boolean> entry : forced.entrySet()) {
|
||||||
|
out.append(Util.rightPad(entry.getKey(), forcedLength)).append(": ").append(entry.getValue()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Iterable<String> getSupportedAbis() {
|
private static Iterable<String> getSupportedAbis() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
return Arrays.asList(Build.SUPPORTED_ABIS);
|
return Arrays.asList(Build.SUPPORTED_ABIS);
|
||||||
|
@ -55,7 +55,7 @@ public final class RegistrationPinV2MigrationJob extends BaseJob {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRun() throws IOException, UnauthenticatedResponseException, KeyBackupServicePinException {
|
protected void onRun() throws IOException, UnauthenticatedResponseException, KeyBackupServicePinException {
|
||||||
if (!FeatureFlags.KBS) {
|
if (!FeatureFlags.kbs()) {
|
||||||
Log.i(TAG, "Not migrating pin to KBS");
|
Log.i(TAG, "Not migrating pin to KBS");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ public class EditProfileFragment extends Fragment {
|
|||||||
this.usernameLabel = view.findViewById(R.id.profile_overview_username_label);
|
this.usernameLabel = view.findViewById(R.id.profile_overview_username_label);
|
||||||
this.nextIntent = getArguments().getParcelable(NEXT_INTENT);
|
this.nextIntent = getArguments().getParcelable(NEXT_INTENT);
|
||||||
|
|
||||||
if (FeatureFlags.USERNAMES && getArguments().getBoolean(DISPLAY_USERNAME, false)) {
|
if (FeatureFlags.usernames() && getArguments().getBoolean(DISPLAY_USERNAME, false)) {
|
||||||
username.setVisibility(View.VISIBLE);
|
username.setVisibility(View.VISIBLE);
|
||||||
usernameEditButton.setVisibility(View.VISIBLE);
|
usernameEditButton.setVisibility(View.VISIBLE);
|
||||||
usernameLabel.setVisibility(View.VISIBLE);
|
usernameLabel.setVisibility(View.VISIBLE);
|
||||||
|
@ -167,7 +167,7 @@ public class Recipient {
|
|||||||
} else if (!recipient.isRegistered()) {
|
} else if (!recipient.isRegistered()) {
|
||||||
db.markRegistered(recipient.getId());
|
db.markRegistered(recipient.getId());
|
||||||
|
|
||||||
if (FeatureFlags.UUIDS) {
|
if (FeatureFlags.uuids()) {
|
||||||
Log.i(TAG, "No UUID! Scheduling a fetch.");
|
Log.i(TAG, "No UUID! Scheduling a fetch.");
|
||||||
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(recipient, false));
|
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(recipient, false));
|
||||||
}
|
}
|
||||||
@ -175,7 +175,7 @@ public class Recipient {
|
|||||||
|
|
||||||
return resolved(recipient.getId());
|
return resolved(recipient.getId());
|
||||||
} else if (uuid != null) {
|
} else if (uuid != null) {
|
||||||
if (FeatureFlags.UUIDS || e164 != null) {
|
if (FeatureFlags.uuids() || e164 != null) {
|
||||||
RecipientId id = db.getOrInsertFromUuid(uuid);
|
RecipientId id = db.getOrInsertFromUuid(uuid);
|
||||||
db.markRegistered(id, uuid);
|
db.markRegistered(id, uuid);
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ public class Recipient {
|
|||||||
if (!recipient.isRegistered()) {
|
if (!recipient.isRegistered()) {
|
||||||
db.markRegistered(recipient.getId());
|
db.markRegistered(recipient.getId());
|
||||||
|
|
||||||
if (FeatureFlags.UUIDS) {
|
if (FeatureFlags.uuids()) {
|
||||||
Log.i(TAG, "No UUID! Scheduling a fetch.");
|
Log.i(TAG, "No UUID! Scheduling a fetch.");
|
||||||
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(recipient, false));
|
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(recipient, false));
|
||||||
}
|
}
|
||||||
@ -247,7 +247,7 @@ public class Recipient {
|
|||||||
if (UuidUtil.isUuid(identifier)) {
|
if (UuidUtil.isUuid(identifier)) {
|
||||||
UUID uuid = UuidUtil.parseOrThrow(identifier);
|
UUID uuid = UuidUtil.parseOrThrow(identifier);
|
||||||
|
|
||||||
if (FeatureFlags.UUIDS) {
|
if (FeatureFlags.uuids()) {
|
||||||
id = db.getOrInsertFromUuid(uuid);
|
id = db.getOrInsertFromUuid(uuid);
|
||||||
} else {
|
} else {
|
||||||
Optional<RecipientId> possibleId = db.getByUuid(uuid);
|
Optional<RecipientId> possibleId = db.getByUuid(uuid);
|
||||||
@ -383,8 +383,8 @@ public class Recipient {
|
|||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public @NonNull String toShortString(@NonNull Context context) {
|
public @NonNull String toShortString(@NonNull Context context) {
|
||||||
if (FeatureFlags.PROFILE_DISPLAY) return getDisplayName(context);
|
if (FeatureFlags.profileDisplay()) return getDisplayName(context);
|
||||||
else return Optional.fromNullable(getName(context)).or(getSmsAddress()).or("");
|
else return Optional.fromNullable(getName(context)).or(getSmsAddress()).or("");
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull String getDisplayName(@NonNull Context context) {
|
public @NonNull String getDisplayName(@NonNull Context context) {
|
||||||
@ -408,7 +408,7 @@ public class Recipient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull Optional<String> getUsername() {
|
public @NonNull Optional<String> getUsername() {
|
||||||
if (FeatureFlags.USERNAMES) {
|
if (FeatureFlags.usernames()) {
|
||||||
return Optional.fromNullable(username);
|
return Optional.fromNullable(username);
|
||||||
} else {
|
} else {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
@ -529,7 +529,7 @@ public class Recipient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable String getCustomLabel() {
|
public @Nullable String getCustomLabel() {
|
||||||
if (FeatureFlags.PROFILE_DISPLAY) throw new AssertionError("This method should never be called if PROFILE_DISPLAY is enabled.");
|
if (FeatureFlags.profileDisplay()) throw new AssertionError("This method should never be called if PROFILE_DISPLAY is enabled.");
|
||||||
return customLabel;
|
return customLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -655,10 +655,10 @@ public class Recipient {
|
|||||||
* @return True if this recipient can support receiving UUID-only messages, otherwise false.
|
* @return True if this recipient can support receiving UUID-only messages, otherwise false.
|
||||||
*/
|
*/
|
||||||
public boolean isUuidSupported() {
|
public boolean isUuidSupported() {
|
||||||
if (FeatureFlags.USERNAMES) {
|
if (FeatureFlags.usernames()) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return FeatureFlags.UUIDS && uuidSupported;
|
return FeatureFlags.uuids() && uuidSupported;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ public class RecipientUtil {
|
|||||||
throw new AssertionError(recipient.getId() + " - No UUID or phone number!");
|
throw new AssertionError(recipient.getId() + " - No UUID or phone number!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FeatureFlags.UUIDS && !recipient.getUuid().isPresent()) {
|
if (FeatureFlags.uuids() && !recipient.getUuid().isPresent()) {
|
||||||
Log.i(TAG, recipient.getId() + " is missing a UUID...");
|
Log.i(TAG, recipient.getId() + " is missing a UUID...");
|
||||||
try {
|
try {
|
||||||
RegisteredState state = DirectoryHelper.refreshDirectoryFor(context, recipient, false);
|
RegisteredState state = DirectoryHelper.refreshDirectoryFor(context, recipient, false);
|
||||||
@ -110,7 +110,7 @@ public class RecipientUtil {
|
|||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
public static boolean isRecipientMessageRequestAccepted(@NonNull Context context, @Nullable Recipient recipient) {
|
public static boolean isRecipientMessageRequestAccepted(@NonNull Context context, @Nullable Recipient recipient) {
|
||||||
if (recipient == null || !FeatureFlags.MESSAGE_REQUESTS) return true;
|
if (recipient == null || !FeatureFlags.messageRequests()) return true;
|
||||||
|
|
||||||
Recipient resolved = recipient.resolve();
|
Recipient resolved = recipient.resolve();
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ public final class CodeVerificationRequest {
|
|||||||
|
|
||||||
static TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException {
|
static TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException {
|
||||||
if (basicStorageCredentials == null) return null;
|
if (basicStorageCredentials == null) return null;
|
||||||
if (!FeatureFlags.KBS) return null;
|
if (!FeatureFlags.kbs()) return null;
|
||||||
return ApplicationDependencies.getKeyBackupService().getToken(basicStorageCredentials);
|
return ApplicationDependencies.getKeyBackupService().getToken(basicStorageCredentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +214,7 @@ public final class CodeVerificationRequest {
|
|||||||
//noinspection deprecation Only acceptable place to write the old pin enabled state.
|
//noinspection deprecation Only acceptable place to write the old pin enabled state.
|
||||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, pin != null);
|
TextSecurePreferences.setV1RegistrationLockEnabled(context, pin != null);
|
||||||
if (pin != null) {
|
if (pin != null) {
|
||||||
if (FeatureFlags.KBS) {
|
if (FeatureFlags.kbs()) {
|
||||||
Log.i(TAG, "Pin V1 successfully entered during registration, scheduling a migration to Pin V2");
|
Log.i(TAG, "Pin V1 successfully entered during registration, scheduling a migration to Pin V2");
|
||||||
ApplicationDependencies.getJobManager().add(new RegistrationPinV2MigrationJob());
|
ApplicationDependencies.getJobManager().add(new RegistrationPinV2MigrationJob());
|
||||||
}
|
}
|
||||||
@ -230,7 +230,7 @@ public final class CodeVerificationRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void repostPinToResetTries(@NonNull Context context, @Nullable String pin, @NonNull RegistrationLockData kbsData) {
|
private static void repostPinToResetTries(@NonNull Context context, @Nullable String pin, @NonNull RegistrationLockData kbsData) {
|
||||||
if (!FeatureFlags.KBS) return;
|
if (!FeatureFlags.kbs()) return;
|
||||||
|
|
||||||
if (pin == null) return;
|
if (pin == null) return;
|
||||||
|
|
||||||
@ -264,7 +264,7 @@ public final class CodeVerificationRequest {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FeatureFlags.KBS) {
|
if (!FeatureFlags.kbs()) {
|
||||||
Log.w(TAG, "User appears to have a KBS pin, but this build has KBS off.");
|
Log.w(TAG, "User appears to have a KBS pin, but this build has KBS off.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,192 @@
|
|||||||
package org.thoughtcrime.securesms.util;
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A location for constants that allows us to turn features on and off during development.
|
* A location for flags that can be set locally and remotely. These flags can guard features that
|
||||||
* After a feature has been launched, the flag should be removed.
|
* are not yet ready to be activated.
|
||||||
|
*
|
||||||
|
* When creating a new flag:
|
||||||
|
* - Create a new string constant using {@link #generateKey(String)})
|
||||||
|
* - Add a method to retrieve the value using {@link #getValue(String, boolean)}. You can also add
|
||||||
|
* other checks here, like requiring other flags.
|
||||||
|
* - If you would like to force a value for testing, place an entry in {@link #FORCED_VALUES}. When
|
||||||
|
* launching a feature that is planned to be updated via a remote config, do not forget to
|
||||||
|
* remove the entry!
|
||||||
*/
|
*/
|
||||||
public class FeatureFlags {
|
public final class FeatureFlags {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(FeatureFlags.class);
|
||||||
|
|
||||||
|
private static final String PREFIX = "android.";
|
||||||
|
private static final long FETCH_INTERVAL = TimeUnit.HOURS.toMillis(2);
|
||||||
|
|
||||||
|
private static final String UUIDS = generateKey("uuids");
|
||||||
|
private static final String PROFILE_DISPLAY = generateKey("profileDisplay");
|
||||||
|
private static final String MESSAGE_REQUESTS = generateKey("messageRequests");
|
||||||
|
private static final String USERNAMES = generateKey("usernames");
|
||||||
|
private static final String KBS = generateKey("kbs");
|
||||||
|
private static final String STORAGE_SERVICE = generateKey("storageService");
|
||||||
|
private static final String REACTION_SENDING = generateKey("reactionSending");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Values in this map will take precedence over any value. If you do not wish to have any sort of
|
||||||
|
* override, simply don't put a value in this map. You should never commit additions to this map
|
||||||
|
* for flags that you plan on updating remotely.
|
||||||
|
*/
|
||||||
|
private static final Map<String, Boolean> FORCED_VALUES = new HashMap<String, Boolean>() {{
|
||||||
|
put(UUIDS, false);
|
||||||
|
put(PROFILE_DISPLAY, false);
|
||||||
|
put(MESSAGE_REQUESTS, false);
|
||||||
|
put(USERNAMES, false);
|
||||||
|
put(KBS, false);
|
||||||
|
put(STORAGE_SERVICE, false);
|
||||||
|
put(REACTION_SENDING, false);
|
||||||
|
}};
|
||||||
|
|
||||||
|
private static final Map<String, Boolean> REMOTE_VALUES = new HashMap<>();
|
||||||
|
|
||||||
|
private FeatureFlags() {}
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
scheduleFetchIfNecessary();
|
||||||
|
REMOTE_VALUES.putAll(parseStoredConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateDiskCache(@NonNull Map<String, Boolean> config) {
|
||||||
|
try {
|
||||||
|
JSONObject filtered = new JSONObject();
|
||||||
|
|
||||||
|
for (Map.Entry<String, Boolean> entry : config.entrySet()) {
|
||||||
|
if (entry.getKey().startsWith(PREFIX)) {
|
||||||
|
filtered.put(entry.getKey(), (boolean) entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalStore.setRemoteConfig(filtered.toString());
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** UUID-related stuff that shouldn't be activated until the user-facing launch. */
|
/** UUID-related stuff that shouldn't be activated until the user-facing launch. */
|
||||||
public static final boolean UUIDS = false;
|
public static boolean uuids() {
|
||||||
|
return getValue(UUIDS, false);
|
||||||
|
}
|
||||||
|
|
||||||
/** Favoring profile names when displaying contacts. */
|
/** Favoring profile names when displaying contacts. */
|
||||||
public static final boolean PROFILE_DISPLAY = UUIDS;
|
public static boolean profileDisplay() {
|
||||||
|
return getValue(PROFILE_DISPLAY, false);
|
||||||
|
}
|
||||||
|
|
||||||
/** MessageRequest stuff */
|
/** MessageRequest stuff */
|
||||||
public static final boolean MESSAGE_REQUESTS = UUIDS;
|
public static boolean messageRequests() {
|
||||||
|
return getValue(MESSAGE_REQUESTS, false);
|
||||||
|
}
|
||||||
|
|
||||||
/** Creating usernames, sending messages by username. Requires {@link #UUIDS}. */
|
/** Creating usernames, sending messages by username. Requires {@link #uuids()}. */
|
||||||
public static final boolean USERNAMES = false;
|
public static boolean usernames() {
|
||||||
|
boolean value = getValue(USERNAMES, false);
|
||||||
|
if (value && !uuids()) throw new MissingFlagRequirementError();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/** Set or migrate PIN to KBS */
|
/** Set or migrate PIN to KBS */
|
||||||
public static final boolean KBS = false;
|
public static boolean kbs() {
|
||||||
|
return getValue(KBS, false);
|
||||||
|
}
|
||||||
|
|
||||||
/** Storage service. Requires {@link #KBS}. */
|
/** Storage service. Requires {@link #kbs()}. */
|
||||||
public static final boolean STORAGE_SERVICE = false;
|
public static boolean storageService() {
|
||||||
|
boolean value = getValue(STORAGE_SERVICE, false);
|
||||||
|
if (value && !kbs()) throw new MissingFlagRequirementError();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/** Send support for reactions. */
|
/** Send support for reactions. */
|
||||||
public static final boolean REACTION_SENDING = false;
|
public static boolean reactionSending() {
|
||||||
|
return getValue(REACTION_SENDING, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Only for rendering debug info. */
|
||||||
|
public static @NonNull Map<String, Boolean> getRemoteValues() {
|
||||||
|
return new TreeMap<>(REMOTE_VALUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Only for rendering debug info. */
|
||||||
|
public static @NonNull Map<String, Boolean> getForcedValues() {
|
||||||
|
return new TreeMap<>(FORCED_VALUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NonNull String generateKey(@NonNull String key) {
|
||||||
|
return PREFIX + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getValue(@NonNull String key, boolean defaultValue) {
|
||||||
|
Boolean forced = FORCED_VALUES.get(key);
|
||||||
|
if (forced != null) {
|
||||||
|
return forced;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean remote = REMOTE_VALUES.get(key);
|
||||||
|
if (remote != null) {
|
||||||
|
return remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void scheduleFetchIfNecessary() {
|
||||||
|
long timeSinceLastFetch = System.currentTimeMillis() - SignalStore.getRemoteConfigLastFetchTime();
|
||||||
|
|
||||||
|
if (timeSinceLastFetch > FETCH_INTERVAL) {
|
||||||
|
Log.i(TAG, "Scheduling remote config refresh.");
|
||||||
|
ApplicationDependencies.getJobManager().add(new RemoteConfigRefreshJob());
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Skipping remote config refresh. Refreshed " + timeSinceLastFetch + " ms ago.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Boolean> parseStoredConfig() {
|
||||||
|
Map<String, Boolean> parsed = new HashMap<>();
|
||||||
|
String stored = SignalStore.getRemoteConfig();
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(stored)) {
|
||||||
|
Log.i(TAG, "No remote config stored. Skipping.");
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject root = new JSONObject(stored);
|
||||||
|
Iterator<String> iter = root.keys();
|
||||||
|
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
String key = iter.next();
|
||||||
|
parsed.put(key, root.getBoolean(key));
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
SignalStore.setRemoteConfig(null);
|
||||||
|
throw new AssertionError("Failed to parse! Cleared storage.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MissingFlagRequirementError extends Error {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,19 @@ public class Util {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String rightPad(String value, int length) {
|
||||||
|
if (value.length() >= length) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder out = new StringBuilder(value);
|
||||||
|
while (out.length() < length) {
|
||||||
|
out.append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static ExecutorService newSingleThreadedLifoExecutor() {
|
public static ExecutorService newSingleThreadedLifoExecutor() {
|
||||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingLifoQueue<Runnable>());
|
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingLifoQueue<Runnable>());
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ import org.whispersystems.signalservice.internal.crypto.ProvisioningCipher;
|
|||||||
import org.whispersystems.signalservice.internal.push.ProfileAvatarData;
|
import org.whispersystems.signalservice.internal.push.ProfileAvatarData;
|
||||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||||
import org.whispersystems.signalservice.internal.push.RemoteAttestationUtil;
|
import org.whispersystems.signalservice.internal.push.RemoteAttestationUtil;
|
||||||
|
import org.whispersystems.signalservice.internal.push.RemoteConfigResponse;
|
||||||
import org.whispersystems.signalservice.internal.push.http.ProfileCipherOutputStreamFactory;
|
import org.whispersystems.signalservice.internal.push.http.ProfileCipherOutputStreamFactory;
|
||||||
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
|
||||||
import org.whispersystems.signalservice.internal.storage.protos.ReadOperation;
|
import org.whispersystems.signalservice.internal.storage.protos.ReadOperation;
|
||||||
@ -501,6 +502,16 @@ public class SignalServiceAccountManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Boolean> getRemoteConfig() throws IOException {
|
||||||
|
RemoteConfigResponse response = this.pushServiceSocket.getRemoteConfig();
|
||||||
|
Map<String, Boolean> out = new HashMap<>();
|
||||||
|
|
||||||
|
for (RemoteConfigResponse.Config config : response.getConfig()) {
|
||||||
|
out.put(config.getName(), config.isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getNewDeviceVerificationCode() throws IOException {
|
public String getNewDeviceVerificationCode() throws IOException {
|
||||||
|
@ -731,6 +731,11 @@ public class PushServiceSocket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RemoteConfigResponse getRemoteConfig() throws IOException {
|
||||||
|
String response = makeServiceRequest("/v1/config", "GET", null);
|
||||||
|
return JsonUtil.fromJson(response, RemoteConfigResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
public void setSoTimeoutMillis(long soTimeoutMillis) {
|
public void setSoTimeoutMillis(long soTimeoutMillis) {
|
||||||
this.soTimeoutMillis = soTimeoutMillis;
|
this.soTimeoutMillis = soTimeoutMillis;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package org.whispersystems.signalservice.internal.push;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RemoteConfigResponse {
|
||||||
|
@JsonProperty
|
||||||
|
private List<Config> config;
|
||||||
|
|
||||||
|
public List<Config> getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Config {
|
||||||
|
@JsonProperty
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user