Remove deprecated SMS fields from recipient table.

This commit is contained in:
Greyson Parrelli 2023-07-31 15:47:27 -04:00
parent e7972d4903
commit e3ec53c2d0
52 changed files with 34 additions and 2044 deletions

View file

@ -17,7 +17,6 @@ object MmsHelper {
recipient: Recipient = Recipient.UNKNOWN,
body: String = "body",
sentTimeMillis: Long = System.currentTimeMillis(),
subscriptionId: Int = -1,
expiresIn: Long = 0,
viewOnce: Boolean = false,
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
@ -32,7 +31,6 @@ object MmsHelper {
recipient = recipient,
body = body,
timestamp = sentTimeMillis,
subscriptionId = subscriptionId,
expiresIn = expiresIn,
viewOnce = viewOnce,
distributionType = distributionType,

View file

@ -6,7 +6,6 @@ import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.insights.InsightsOptOut;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
@ -30,7 +29,6 @@ public final class AppInitialization {
public static void onFirstEverAppLaunch(@NonNull Context context) {
Log.i(TAG, "onFirstEverAppLaunch()");
InsightsOptOut.userRequestedOptOut(context);
TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION);
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode());
@ -71,7 +69,6 @@ public final class AppInitialization {
public static void onRepairFirstEverAppLaunch(@NonNull Context context) {
Log.w(TAG, "onRepairFirstEverAppLaunch()");
InsightsOptOut.userRequestedOptOut(context);
TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION);
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode());

View file

@ -254,13 +254,8 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
for (SelectedContact contact : contacts) {
RecipientId recipientId = contact.getOrCreateRecipientId(context);
Recipient recipient = Recipient.resolved(recipientId);
int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1);
MessageSender.send(context, OutgoingMessage.sms(recipient, message, subscriptionId), -1L, MessageSender.SendType.SMS, null, null);
if (recipient.getContactUri() != null) {
SignalDatabase.recipients().setHasSentInvite(recipient.getId());
}
MessageSender.send(context, OutgoingMessage.sms(recipient, message), -1L, MessageSender.SendType.SMS, null, null);
}
return null;

View file

@ -11,7 +11,6 @@ import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.recipients.RecipientId;
import io.reactivex.rxjava3.disposables.Disposable;
@ -78,10 +77,6 @@ public class MainNavigator {
activity.startActivity(intent);
}
public void goToInsights() {
InsightsLauncher.showInsightsDashboard(activity.getSupportFragmentManager());
}
private @NonNull FragmentManager getFragmentManager() {
return activity.getSupportFragmentManager();
}

View file

@ -1,25 +0,0 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.R;
public final class FirstInviteReminder extends Reminder {
private final int percentIncrease;
public FirstInviteReminder(final int percentIncrease) {
super(R.string.FirstInviteReminder__title, NO_RESOURCE);
this.percentIncrease = percentIncrease;
addAction(new Action(R.string.InsightsReminder__invite, R.id.reminder_action_invite));
addAction(new Action(R.string.InsightsReminder__view_insights, R.id.reminder_action_view_insights));
}
@Override
public @NonNull CharSequence getText(@NonNull Context context) {
return context.getString(R.string.FirstInviteReminder__description, percentIncrease);
}
}

View file

@ -1,37 +0,0 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
public final class SecondInviteReminder extends Reminder {
private final Recipient recipient;
private final int progress;
public SecondInviteReminder(final @NonNull Context context,
final @NonNull Recipient recipient,
final int percent)
{
super(R.string.SecondInviteReminder__title, NO_RESOURCE);
this.recipient = recipient;
this.progress = percent;
addAction(new Action(R.string.InsightsReminder__invite, R.id.reminder_action_invite));
addAction(new Action(R.string.InsightsReminder__view_insights, R.id.reminder_action_view_insights));
}
@Override
public @NonNull CharSequence getText(@NonNull Context context) {
return context.getString(R.string.SecondInviteReminder__description, recipient.getDisplayName(context));
}
@Override
public int getProgress() {
return progress;
}
}

View file

@ -573,7 +573,7 @@ open class ContactSearchAdapter(
}
private fun isSmsContact(model: T): Boolean {
return (getRecipient(model).isForceSmsSelection || getRecipient(model).isUnregistered) && !getRecipient(model).isDistributionList
return getRecipient(model).isUnregistered && !getRecipient(model).isDistributionList
}
private fun isNotRegistered(model: T): Boolean {

View file

@ -184,10 +184,7 @@ import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog;
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity;
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInitiationBottomSheetDialogFragment;
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationSuggestionsDialog;
import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.invites.InviteActions;
import org.thoughtcrime.securesms.invites.InviteReminderModel;
import org.thoughtcrime.securesms.invites.InviteReminderRepository;
import org.thoughtcrime.securesms.jobs.ForceUpdateGroupV2Job;
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
@ -437,7 +434,6 @@ public class ConversationParentFragment extends Fragment
private ConversationSearchViewModel searchViewModel;
private ConversationStickerViewModel stickerViewModel;
private ConversationViewModel viewModel;
private InviteReminderModel inviteReminderModel;
private ConversationGroupViewModel groupViewModel;
private MentionsPickerViewModel mentionsViewModel;
private InlineQueryViewModel inlineQueryViewModel;
@ -1364,8 +1360,7 @@ public class ConversationParentFragment extends Fragment
if (paymentsValues.getPaymentsAvailability().isSendAllowed() &&
!recipient.get().isSelf() &&
!recipient.get().isGroup() &&
recipient.get().isRegistered() &&
!recipient.get().isForceSmsSelection())
recipient.get().isRegistered())
{
attachmentKeyboardStub.get().filterAttachmentKeyboardButtons(null);
} else {
@ -1425,16 +1420,11 @@ public class ConversationParentFragment extends Fragment
sendButton.disableTransportType(MessageSendType.TransportType.SIGNAL);
}
if (!recipient.get().isPushGroup() && recipient.get().isForceSmsSelection() && smsEnabled) {
if (isPushAvailable || isPushGroupConversation() || recipient.get().isServiceIdOnly() || recipient.get().isReleaseNotes() || !smsEnabled) {
sendButton.setDefaultTransport(MessageSendType.TransportType.SIGNAL);
} else {
sendButton.setDefaultTransport(MessageSendType.TransportType.SMS);
viewModel.insertSmsExportUpdateEvent(recipient.get());
} else {
if (isPushAvailable || isPushGroupConversation() || recipient.get().isServiceIdOnly() || recipient.get().isReleaseNotes() || !smsEnabled) {
sendButton.setDefaultTransport(MessageSendType.TransportType.SIGNAL);
} else {
sendButton.setDefaultTransport(MessageSendType.TransportType.SMS);
viewModel.insertSmsExportUpdateEvent(recipient.get());
}
}
calculateCharactersRemaining();
@ -1710,12 +1700,9 @@ public class ConversationParentFragment extends Fragment
private void onSecurityUpdated() {
Log.i(TAG, "onSecurityUpdated()");
updateReminders();
updateDefaultSubscriptionId(recipient.get().getDefaultSubscriptionId());
}
private void initializeInsightObserver() {
inviteReminderModel = new InviteReminderModel(requireContext(), new InviteReminderRepository(requireContext()));
inviteReminderModel.loadReminder(recipient, this::updateReminders);
}
protected void updateReminders() {
@ -1724,7 +1711,6 @@ public class ConversationParentFragment extends Fragment
return;
}
Optional<Reminder> inviteReminder = inviteReminderModel.getReminder();
Integer actionableRequestingMembers = groupViewModel.getActionableRequestingMembers().getValue();
List<RecipientId> gv1MigrationSuggestions = groupViewModel.getGroupV1MigrationSuggestions().getValue();
@ -1740,11 +1726,8 @@ public class ConversationParentFragment extends Fragment
} else if (SignalStore.account().isRegistered() &&
TextSecurePreferences.isShowInviteReminders(context) &&
!viewModel.isPushAvailable() &&
inviteReminder.isPresent() &&
!recipient.get().isGroup()) {
reminderView.get().setOnActionClickListener(this::handleReminderAction);
reminderView.get().setOnDismissListener(() -> inviteReminderModel.dismissReminder());
reminderView.get().showReminder(inviteReminder.get());
} else if (actionableRequestingMembers != null && actionableRequestingMembers > 0) {
reminderView.get().showReminder(new PendingGroupJoinRequestsReminder(actionableRequestingMembers));
reminderView.get().setOnActionClickListener(id -> {
@ -1785,8 +1768,6 @@ public class ConversationParentFragment extends Fragment
if (reminderActionId == R.id.reminder_action_invite) {
handleInviteLink();
reminderView.get().requestDismiss();
} else if (reminderActionId == R.id.reminder_action_view_insights) {
InsightsLauncher.showInsightsDashboard(getChildFragmentManager());
} else if (reminderActionId == R.id.reminder_action_update_now) {
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext());
} else if (reminderActionId == R.id.reminder_action_re_register) {
@ -1958,8 +1939,6 @@ public class ConversationParentFragment extends Fragment
composeText.setMessageSendType(newMessageSendType);
updateSendButtonColor(newMessageSendType);
if (manuallySelected) recordTransportPreference(newMessageSendType);
});
titleView.setOnStoryRingClickListener(v -> handleStoryRingClick());
@ -2461,7 +2440,6 @@ public class ConversationParentFragment extends Fragment
titleView.setVerified(identityRecords.isVerified() && !recipient.isSelf());
setBlockedUserState(recipient, viewModel.getConversationStateSnapshot().getSecurityInfo());
updateReminders();
updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId());
updatePaymentsAvailable();
updateSendButtonColor(sendButton.getSelectedSendType());
@ -2904,7 +2882,6 @@ public class ConversationParentFragment extends Fragment
result.getBody(),
Collections.emptyList(),
System.currentTimeMillis(),
-1,
expiresIn,
result.isViewOnce(),
distributionType,
@ -3033,7 +3010,6 @@ public class ConversationParentFragment extends Fragment
OutgoingMessage.buildMessage(slideDeck, body),
slideDeck.asAttachments(),
System.currentTimeMillis(),
sendType.getSimSubscriptionIdOr(-1),
expiresIn,
viewOnce,
distributionType,
@ -3126,7 +3102,7 @@ public class ConversationParentFragment extends Fragment
}
ApplicationDependencies.getTypingStatusSender().onTypingStopped(thread);
} else {
message = OutgoingMessage.sms(recipient.get(), messageBody, sendType.getSimSubscriptionIdOr(-1));
message = OutgoingMessage.sms(recipient.get(), messageBody);
}
Permissions.with(this)
@ -3246,23 +3222,6 @@ public class ConversationParentFragment extends Fragment
}
}
private void recordTransportPreference(MessageSendType sendType) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
RecipientTable recipientTable = SignalDatabase.recipients();
recipientTable.setDefaultSubscriptionId(recipient.getId(), sendType.getSimSubscriptionIdOr(-1));
if (!recipient.resolve().isPushGroup()) {
recipientTable.setForceSmsSelection(recipient.getId(), recipient.get().getRegistered() == RegisteredState.REGISTERED && sendType.usesSmsTransport());
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onRecorderPermissionRequired() {
Permissions.with(this)

View file

@ -104,8 +104,7 @@ class AttachmentKeyboardFragment : LoggingFragment(R.layout.attachment_keyboard_
if (paymentsValues.paymentsAvailability.isSendAllowed &&
!recipient.isSelf &&
!recipient.isGroup &&
recipient.isRegistered &&
!recipient.isForceSmsSelection
recipient.isRegistered
) {
attachmentKeyboardView.filterAttachmentKeyboardButtons(null)
} else {

View file

@ -138,7 +138,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.exporter.flow.SmsExportDialogs;
import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
@ -472,10 +471,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
itemAnimator.disable();
SpoilerAnnotation.resetRevealedSpoilers();
if (Util.isDefaultSmsProvider(requireContext())) {
InsightsLauncher.showInsightsModal(requireContext(), requireFragmentManager());
}
if ((!requireCallback().getSearchToolbar().resolved() || !(requireCallback().getSearchToolbar().get().getVisibility() == View.VISIBLE)) && list.getAdapter() != defaultAdapter) {
setAdapter(defaultAdapter);
}
@ -561,7 +556,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
@Override
public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.menu_insights).setVisible(Util.isDefaultSmsProvider(requireContext()));
menu.findItem(R.id.menu_clear_passphrase).setVisible(!TextSecurePreferences.isPasswordDisabled(requireContext()));
ConversationFilterRequest request = viewModel.getConversationFilterRequest();
@ -592,9 +586,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
} else if (itemId == R.id.menu_invite) {
handleInvite();
return true;
} else if (itemId == R.id.menu_insights) {
handleInsights();
return true;
} else if (itemId == R.id.menu_notification_profile) {
handleNotificationProfile();
return true;
@ -1143,10 +1134,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
getNavigator().goToInvite();
}
private void handleInsights() {
getNavigator().goToInsights();
}
private void handleNotificationProfile() {
NotificationProfileSelectionFragment.show(getParentFragmentManager());
}

View file

@ -112,7 +112,6 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchove
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.insights.InsightsConstants
import org.thoughtcrime.securesms.jobs.OptimizeMessageSearchIndexJob
import org.thoughtcrime.securesms.jobs.ThreadUpdateJob
import org.thoughtcrime.securesms.jobs.TrimThreadJob
@ -1084,10 +1083,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
if (unread && editedMessage == null) {
threads.incrementUnread(threadId, 1, 0)
}
if (message.subscriptionId != -1) {
recipients.setDefaultSubscriptionId(message.authorId, message.subscriptionId)
}
}
id
@ -2507,7 +2502,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
body = body,
attachments = attachments,
timestamp = timestamp,
subscriptionId = subscriptionId,
expiresIn = expiresIn,
viewOnce = viewOnce,
distributionType = distributionType,
@ -3712,15 +3706,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
.readToSingleInt()
}
fun getInsecureMessageSentCount(threadId: Long): Int {
return readableDatabase
.select("COUNT(*)")
.from(TABLE_NAME)
.where("$THREAD_ID = ? AND $outgoingInsecureMessageClause AND $DATE_SENT > ?", threadId, (System.currentTimeMillis() - InsightsConstants.PERIOD_IN_MILLIS))
.run()
.readToSingleInt()
}
fun getSecureMessageCount(threadId: Long): Int {
return readableDatabase
.select("COUNT(*)")
@ -3739,14 +3724,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
.readToSingleInt()
}
fun getInsecureMessageCountForInsights(): Int {
return getMessageCountForRecipientsAndType(outgoingInsecureMessageClause)
}
fun getSecureMessageCountForInsights(): Int {
return getMessageCountForRecipientsAndType(outgoingSecureMessageClause)
}
private fun hasSmsExportMessage(threadId: Long): Boolean {
return readableDatabase
.exists(TABLE_NAME)
@ -3754,15 +3731,6 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
.run()
}
private fun getMessageCountForRecipientsAndType(typeClause: String): Int {
return readableDatabase
.select("COUNT(*)")
.from(TABLE_NAME)
.where("$typeClause AND $DATE_SENT > ?", (System.currentTimeMillis() - InsightsConstants.PERIOD_IN_MILLIS))
.run()
.readToSingleInt()
}
private val outgoingInsecureMessageClause = "($TYPE & ${MessageTypes.BASE_TYPE_MASK}) = ${MessageTypes.BASE_SENT_TYPE} AND NOT ($TYPE & ${MessageTypes.SECURE_MESSAGE_BIT})"
private val outgoingSecureMessageClause = "($TYPE & ${MessageTypes.BASE_TYPE_MASK}) = ${MessageTypes.BASE_SENT_TYPE} AND ($TYPE & ${MessageTypes.SECURE_MESSAGE_BIT or MessageTypes.PUSH_MESSAGE_BIT})"

View file

@ -285,8 +285,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
CALL_VIBRATE,
MUTE_UNTIL,
AVATAR_COLOR,
SEEN_INVITE_REMINDER,
DEFAULT_SUBSCRIPTION_ID,
MESSAGE_EXPIRATION_TIME,
REGISTERED,
PROFILE_KEY,
@ -305,7 +303,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
LAST_PROFILE_FETCH,
NOTIFICATION_CHANNEL,
UNIDENTIFIED_ACCESS_MODE,
FORCE_SMS_SELECTION,
CAPABILITIES,
STORAGE_SERVICE_ID,
MENTION_SETTING,
@ -396,21 +393,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
"""
)
private val INSIGHTS_INVITEE_LIST =
"""
SELECT $TABLE_NAME.$ID
FROM $TABLE_NAME INNER JOIN ${ThreadTable.TABLE_NAME} ON $TABLE_NAME.$ID = ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID}
WHERE
$TABLE_NAME.$GROUP_ID IS NULL AND
$TABLE_NAME.$REGISTERED = ${RegisteredState.NOT_REGISTERED.id} AND
$TABLE_NAME.$SEEN_INVITE_REMINDER < ${InsightsBannerTier.TIER_TWO.id} AND
${ThreadTable.TABLE_NAME}.${ThreadTable.HAS_SENT} AND
${ThreadTable.TABLE_NAME}.${ThreadTable.DATE} > ? AND
${ThreadTable.TABLE_NAME}.${ThreadTable.ACTIVE} = 1 AND
$TABLE_NAME.$HIDDEN = 0
ORDER BY ${ThreadTable.TABLE_NAME}.${ThreadTable.DATE} DESC LIMIT 50
"""
/** Used as a placeholder recipient for self during migrations when self isn't yet available. */
private val PLACEHOLDER_SELF_ID = -2L
}
@ -1348,24 +1330,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
}
fun setDefaultSubscriptionId(id: RecipientId, defaultSubscriptionId: Int) {
val values = ContentValues().apply {
put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId)
}
if (update(id, values)) {
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
}
}
fun setForceSmsSelection(id: RecipientId, forceSmsSelection: Boolean) {
val contentValues = ContentValues(1).apply {
put(FORCE_SMS_SELECTION, if (forceSmsSelection) 1 else 0)
}
if (update(id, contentValues)) {
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
}
}
fun setBlocked(id: RecipientId, blocked: Boolean) {
val values = ContentValues().apply {
put(BLOCKED, if (blocked) 1 else 0)
@ -1452,29 +1416,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
StorageSyncHelper.scheduleSyncForDataChange()
}
fun setSeenFirstInviteReminder(id: RecipientId) {
setInsightsBannerTier(id, InsightsBannerTier.TIER_ONE)
}
fun setSeenSecondInviteReminder(id: RecipientId) {
setInsightsBannerTier(id, InsightsBannerTier.TIER_TWO)
}
fun setHasSentInvite(id: RecipientId) {
setSeenSecondInviteReminder(id)
}
private fun setInsightsBannerTier(id: RecipientId, insightsBannerTier: InsightsBannerTier) {
val query = "$ID = ? AND $SEEN_INVITE_REMINDER < ?"
val args = arrayOf(id.serialize(), insightsBannerTier.toString())
val values = ContentValues(1).apply {
put(SEEN_INVITE_REMINDER, insightsBannerTier.id)
}
writableDatabase.update(TABLE_NAME, values, query, args)
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
}
fun setExpireMessages(id: RecipientId, expiration: Int) {
val values = ContentValues(1).apply {
put(MESSAGE_EXPIRATION_TIME, expiration)
@ -3096,19 +3037,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
return operations
}
fun getUninvitedRecipientsForInsights(): List<RecipientId> {
val results: MutableList<RecipientId> = LinkedList()
val args = arrayOf((System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)).toString())
readableDatabase.rawQuery(INSIGHTS_INVITEE_LIST, args).use { cursor ->
while (cursor != null && cursor.moveToNext()) {
results.add(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID))))
}
}
return results
}
fun getRegistered(): List<RecipientId> {
val results: MutableList<RecipientId> = LinkedList()
@ -3899,8 +3827,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
CHAT_COLORS to Optional.ofNullable(primaryRecord.chatColors).or(Optional.ofNullable(secondaryRecord.chatColors)).map { colors: ChatColors? -> colors!!.serialize().toByteArray() }.orElse(null),
AVATAR_COLOR to primaryRecord.avatarColor.serialize(),
CUSTOM_CHAT_COLORS_ID to Optional.ofNullable(primaryRecord.chatColors).or(Optional.ofNullable(secondaryRecord.chatColors)).map { colors: ChatColors? -> colors!!.id.longValue }.orElse(null),
SEEN_INVITE_REMINDER to secondaryRecord.insightsBannerTier.id,
DEFAULT_SUBSCRIPTION_ID to secondaryRecord.getDefaultSubscriptionId().orElse(-1),
MESSAGE_EXPIRATION_TIME to if (primaryRecord.expireMessages > 0) primaryRecord.expireMessages else secondaryRecord.expireMessages,
REGISTERED to RegisteredState.REGISTERED.id,
SYSTEM_GIVEN_NAME to secondaryRecord.systemProfileName.givenName,
@ -4246,7 +4172,6 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
callVibrateState = VibrateState.fromId(cursor.requireInt(CALL_VIBRATE)),
messageRingtone = Util.uri(cursor.requireString(MESSAGE_RINGTONE)),
callRingtone = Util.uri(cursor.requireString(CALL_RINGTONE)),
defaultSubscriptionId = cursor.requireInt(DEFAULT_SUBSCRIPTION_ID),
expireMessages = cursor.requireInt(MESSAGE_EXPIRATION_TIME),
registered = RegisteredState.fromId(cursor.requireInt(REGISTERED)),
profileKey = profileKey,
@ -4263,9 +4188,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
lastProfileFetch = cursor.requireLong(LAST_PROFILE_FETCH),
notificationChannel = cursor.requireString(NOTIFICATION_CHANNEL),
unidentifiedAccessMode = UnidentifiedAccessMode.fromMode(cursor.requireInt(UNIDENTIFIED_ACCESS_MODE)),
forceSmsSelection = cursor.requireBoolean(FORCE_SMS_SELECTION),
capabilities = readCapabilities(cursor),
insightsBannerTier = InsightsBannerTier.fromId(cursor.requireInt(SEEN_INVITE_REMINDER)),
storageId = Base64.decodeNullableOrThrow(cursor.requireString(STORAGE_SERVICE_ID)),
mentionSetting = MentionSetting.fromId(cursor.requireInt(MENTION_SETTING)),
wallpaper = chatWallpaper,

View file

@ -8,7 +8,6 @@ import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.RecipientTable.InsightsBannerTier
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
@ -22,7 +21,6 @@ import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.Optional
/**
* Database model for [RecipientTable].
@ -43,7 +41,6 @@ data class RecipientRecord(
val callVibrateState: VibrateState,
val messageRingtone: Uri?,
val callRingtone: Uri?,
private val defaultSubscriptionId: Int,
val expireMessages: Int,
val registered: RegisteredState,
val profileKey: ByteArray?,
@ -63,10 +60,7 @@ data class RecipientRecord(
val lastProfileFetch: Long,
val notificationChannel: String?,
val unidentifiedAccessMode: UnidentifiedAccessMode,
@get:JvmName("isForceSmsSelection")
val forceSmsSelection: Boolean,
val capabilities: Capabilities,
val insightsBannerTier: InsightsBannerTier,
val storageId: ByteArray?,
val mentionSetting: MentionSetting,
val wallpaper: ChatWallpaper?,
@ -85,10 +79,6 @@ data class RecipientRecord(
val callLinkRoomId: CallLinkRoomId?
) {
fun getDefaultSubscriptionId(): Optional<Int> {
return if (defaultSubscriptionId != -1) Optional.of(defaultSubscriptionId) else Optional.empty()
}
fun e164Only(): Boolean {
return this.e164 != null && this.aci == null
}

View file

@ -1,94 +0,0 @@
package org.thoughtcrime.securesms.insights;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
final class InsightsAnimatorSetFactory {
private static final int PROGRESS_ANIMATION_DURATION = 800;
private static final int DETAILS_ANIMATION_DURATION = 200;
private static final int PERCENT_SECURE_ANIMATION_DURATION = 400;
private static final int LOTTIE_ANIMATION_DURATION = 1500;
private static final int ANIMATION_START_DELAY = PROGRESS_ANIMATION_DURATION - DETAILS_ANIMATION_DURATION;
private static final float PERCENT_SECURE_MAX_SCALE = 1.3f;
private InsightsAnimatorSetFactory() {
}
static AnimatorSet create(int insecurePercent,
@Nullable final UpdateListener progressUpdateListener,
@Nullable final UpdateListener detailsUpdateListener,
@Nullable final UpdateListener percentSecureListener,
@Nullable final UpdateListener lottieListener)
{
final int securePercent = 100 - insecurePercent;
final AnimatorSet animatorSet = new AnimatorSet();
final ValueAnimator[] animators = Stream.of(createProgressAnimator(securePercent, progressUpdateListener),
createDetailsAnimator(detailsUpdateListener),
createPercentSecureAnimator(percentSecureListener),
createLottieAnimator(lottieListener))
.filter(a -> a != null)
.toArray(ValueAnimator[]::new);
animatorSet.setInterpolator(new DecelerateInterpolator());
animatorSet.playTogether(animators);
return animatorSet;
}
private static @Nullable Animator createProgressAnimator(int securePercent, @Nullable UpdateListener updateListener) {
if (updateListener == null) return null;
final ValueAnimator progressAnimator = ValueAnimator.ofFloat(0, securePercent / 100f);
progressAnimator.setDuration(PROGRESS_ANIMATION_DURATION);
progressAnimator.addUpdateListener(animation -> updateListener.onUpdate((float) animation.getAnimatedValue()));
return progressAnimator;
}
private static @Nullable Animator createDetailsAnimator(@Nullable UpdateListener updateListener) {
if (updateListener == null) return null;
final ValueAnimator detailsAnimator = ValueAnimator.ofFloat(0, 1f);
detailsAnimator.setDuration(DETAILS_ANIMATION_DURATION);
detailsAnimator.setStartDelay(ANIMATION_START_DELAY);
detailsAnimator.addUpdateListener(animation -> updateListener.onUpdate((float) animation.getAnimatedValue()));
return detailsAnimator;
}
private static @Nullable Animator createPercentSecureAnimator(@Nullable UpdateListener updateListener) {
if (updateListener == null) return null;
final ValueAnimator percentSecureAnimator = ValueAnimator.ofFloat(1f, PERCENT_SECURE_MAX_SCALE, 1f);
percentSecureAnimator.setStartDelay(ANIMATION_START_DELAY);
percentSecureAnimator.setDuration(PERCENT_SECURE_ANIMATION_DURATION);
percentSecureAnimator.addUpdateListener(animation -> updateListener.onUpdate((float) animation.getAnimatedValue()));
return percentSecureAnimator;
}
private static @Nullable Animator createLottieAnimator(@Nullable UpdateListener updateListener) {
if (updateListener == null) return null;
final ValueAnimator lottieAnimator = ValueAnimator.ofFloat(0, 1f);
lottieAnimator.setStartDelay(ANIMATION_START_DELAY);
lottieAnimator.setDuration(LOTTIE_ANIMATION_DURATION);
lottieAnimator.addUpdateListener(animation -> updateListener.onUpdate((float) animation.getAnimatedValue()));
return lottieAnimator;
}
interface UpdateListener {
void onUpdate(float value);
}
}

View file

@ -1,13 +0,0 @@
package org.thoughtcrime.securesms.insights;
import java.util.concurrent.TimeUnit;
public final class InsightsConstants {
public static final long PERIOD_IN_DAYS = 7L;
public static final long PERIOD_IN_MILLIS = TimeUnit.DAYS.toMillis(PERIOD_IN_DAYS);
private InsightsConstants() {
}
}

View file

@ -1,271 +0,0 @@
package org.thoughtcrime.securesms.insights;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.RecyclerView;
import com.airbnb.lottie.LottieAnimationView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.NewConversationActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.ArcProgressBar;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.List;
public final class InsightsDashboardDialogFragment extends DialogFragment {
private TextView securePercentage;
private ArcProgressBar progress;
private View progressContainer;
private TextView tagline;
private TextView encryptedMessages;
private TextView title;
private TextView description;
private RecyclerView insecureRecipients;
private TextView locallyGenerated;
private AvatarImageView avatarImageView;
private InsightsInsecureRecipientsAdapter adapter;
private LottieAnimationView lottieAnimationView;
private AnimatorSet animatorSet;
private Button startAConversation;
private Toolbar toolbar;
private InsightsDashboardViewModel viewModel;
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
requireFragmentManager().beginTransaction()
.detach(this)
.attach(this)
.commit();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (ThemeUtil.isDarkTheme(requireActivity())) {
setStyle(STYLE_NO_FRAME, R.style.TextSecure_DarkTheme);
} else {
setStyle(STYLE_NO_FRAME, R.style.TextSecure_LightTheme);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.insights_dashboard, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
securePercentage = view.findViewById(R.id.insights_dashboard_percent_secure);
progress = view.findViewById(R.id.insights_dashboard_progress);
progressContainer = view.findViewById(R.id.insights_dashboard_percent_container);
encryptedMessages = view.findViewById(R.id.insights_dashboard_encrypted_messages);
tagline = view.findViewById(R.id.insights_dashboard_tagline);
title = view.findViewById(R.id.insights_dashboard_make_signal_secure);
description = view.findViewById(R.id.insights_dashboard_invite_your_contacts);
insecureRecipients = view.findViewById(R.id.insights_dashboard_recycler);
locallyGenerated = view.findViewById(R.id.insights_dashboard_this_stat_was_generated_locally);
avatarImageView = view.findViewById(R.id.insights_dashboard_avatar);
startAConversation = view.findViewById(R.id.insights_dashboard_start_a_conversation);
lottieAnimationView = view.findViewById(R.id.insights_dashboard_lottie_animation);
toolbar = view.findViewById(R.id.insights_dashboard_toolbar);
setupStartAConversation();
setDashboardDetailsAlpha(0f);
setNotEnoughDataAlpha(0f);
setupToolbar();
setupRecycler();
initializeViewModel();
}
private void setupStartAConversation() {
startAConversation.setOnClickListener(v -> startActivity(new Intent(requireActivity(), NewConversationActivity.class)));
}
private void setDashboardDetailsAlpha(float alpha) {
tagline.setAlpha(alpha);
title.setAlpha(alpha);
description.setAlpha(alpha);
insecureRecipients.setAlpha(alpha);
locallyGenerated.setAlpha(alpha);
encryptedMessages.setAlpha(alpha);
}
private void setupToolbar() {
toolbar.setNavigationOnClickListener(v -> dismiss());
}
private void setupRecycler() {
adapter = new InsightsInsecureRecipientsAdapter(this::handleInviteRecipient);
insecureRecipients.setAdapter(adapter);
}
private void initializeViewModel() {
final InsightsDashboardViewModel.Repository repository = new InsightsRepository(requireContext());
final InsightsDashboardViewModel.Factory factory = new InsightsDashboardViewModel.Factory(repository);
viewModel = new ViewModelProvider(this, factory).get(InsightsDashboardViewModel.class);
viewModel.getState().observe(getViewLifecycleOwner(), state -> {
updateInsecurePercent(state.getData());
updateInsecureRecipients(state.getInsecureRecipients());
updateUserAvatar(state.getUserAvatar());
});
}
private void updateInsecurePercent(@Nullable InsightsData insightsData) {
if (insightsData == null) return;
if (insightsData.hasEnoughData()) {
setTitleAndDescriptionText(insightsData.getPercentInsecure());
animateProgress(insightsData.getPercentInsecure());
} else {
setNotEnoughDataText();
animateNotEnoughData();
}
}
private void animateProgress(int insecurePercent) {
startAConversation.setVisibility(View.GONE);
if (animatorSet == null) {
animatorSet = InsightsAnimatorSetFactory.create(insecurePercent,
this::setProgressPercentage,
this::setDashboardDetailsAlpha,
this::setPercentSecureScale,
insecurePercent == 0 ? this::setLottieProgress : null);
if (insecurePercent == 0) {
animatorSet.addListener(new ToolbarBackgroundColorAnimationListener());
}
animatorSet.start();
}
}
private void setProgressPercentage(float percent) {
securePercentage.setText(String.valueOf(Math.round(percent * 100)));
progress.setProgress(percent);
}
private void setPercentSecureScale(float scale) {
progressContainer.setScaleX(scale);
progressContainer.setScaleY(scale);
}
private void setLottieProgress(float progress) {
lottieAnimationView.setProgress(progress);
}
private void setTitleAndDescriptionText(int insecurePercent) {
startAConversation.setVisibility(View.GONE);
progressContainer.setVisibility(View.VISIBLE);
insecureRecipients.setVisibility(View.VISIBLE);
encryptedMessages.setText(R.string.InsightsDashboardFragment__encrypted_messages);
tagline.setText(getString(R.string.InsightsDashboardFragment__signal_protocol_automatically_protected, 100 - insecurePercent, InsightsConstants.PERIOD_IN_DAYS));
if (insecurePercent == 0) {
lottieAnimationView.setVisibility(View.VISIBLE);
title.setVisibility(View.GONE);
description.setVisibility(View.GONE);
} else {
lottieAnimationView.setVisibility(View.GONE);
title.setText(R.string.InsightsDashboardFragment__spread_the_word);
description.setText(R.string.InsightsDashboardFragment__invite_your_contacts);
title.setVisibility(View.VISIBLE);
description.setVisibility(View.VISIBLE);
}
}
private void setNotEnoughDataText() {
startAConversation.setVisibility(View.VISIBLE);
progressContainer.setVisibility(View.INVISIBLE);
insecureRecipients.setVisibility(View.GONE);
encryptedMessages.setText(R.string.InsightsDashboardFragment__not_enough_data);
tagline.setText(getString(R.string.InsightsDashboardFragment__your_insights_percentage_is_calculated_based_on, InsightsConstants.PERIOD_IN_DAYS));
}
private void animateNotEnoughData() {
if (animatorSet == null) {
animatorSet = InsightsAnimatorSetFactory.create(0, null, this::setNotEnoughDataAlpha, null, null);
animatorSet.start();
}
}
private void setNotEnoughDataAlpha(float alpha) {
encryptedMessages.setAlpha(alpha);
tagline.setAlpha(alpha);
startAConversation.setAlpha(alpha);
}
private void updateInsecureRecipients(@NonNull List<Recipient> recipients) {
adapter.updateData(recipients);
}
private void updateUserAvatar(@Nullable InsightsUserAvatar userAvatar) {
if (userAvatar == null) avatarImageView.setImageDrawable(null);
else userAvatar.load(avatarImageView);
}
private void handleInviteRecipient(final @NonNull Recipient recipient) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_invites, 1, 1))
.setMessage(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)))
.setPositiveButton(R.string.InsightsDashboardFragment__send, (dialog, which) -> viewModel.sendSmsInvite(recipient))
.setNegativeButton(R.string.InsightsDashboardFragment__cancel, (dialog, which) -> dialog.dismiss())
.show();
}
@Override
public void onDestroyView() {
if (animatorSet != null) {
animatorSet.cancel();
animatorSet = null;
}
super.onDestroyView();
}
private final class ToolbarBackgroundColorAnimationListener implements Animator.AnimatorListener {
@Override
public void onAnimationStart(Animator animation) {
toolbar.setBackgroundResource(R.color.transparent);
}
@Override
public void onAnimationEnd(Animator animation) {
toolbar.setBackgroundColor(ThemeUtil.getThemedColor(requireContext(), android.R.attr.windowBackground));
}
@Override
public void onAnimationCancel(Animator animation) {
toolbar.setBackgroundColor(ThemeUtil.getThemedColor(requireContext(), android.R.attr.windowBackground));
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
}

View file

@ -1,70 +0,0 @@
package org.thoughtcrime.securesms.insights;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.Collections;
import java.util.List;
final class InsightsDashboardState {
private final List<Recipient> insecureRecipients;
private final InsightsData insightsData;
private final InsightsUserAvatar userAvatar;
private InsightsDashboardState(@NonNull Builder builder) {
this.insecureRecipients = builder.insecureRecipients;
this.insightsData = builder.insightsData;
this.userAvatar = builder.userAvatar;
}
static @NonNull InsightsDashboardState.Builder builder() {
return new InsightsDashboardState.Builder();
}
@NonNull InsightsDashboardState.Builder buildUpon() {
return builder().withData(insightsData).withUserAvatar(userAvatar).withInsecureRecipients(insecureRecipients);
}
@NonNull List<Recipient> getInsecureRecipients() {
return insecureRecipients;
}
@Nullable InsightsUserAvatar getUserAvatar() {
return userAvatar;
}
@Nullable InsightsData getData() {
return insightsData;
}
static final class Builder {
private List<Recipient> insecureRecipients = Collections.emptyList();
private InsightsUserAvatar userAvatar;
private InsightsData insightsData;
private Builder() {
}
@NonNull Builder withInsecureRecipients(@NonNull List<Recipient> insecureRecipients) {
this.insecureRecipients = insecureRecipients;
return this;
}
@NonNull Builder withData(@NonNull InsightsData insightsData) {
this.insightsData = insightsData;
return this;
}
@NonNull Builder withUserAvatar(@NonNull InsightsUserAvatar userAvatar) {
this.userAvatar = userAvatar;
return this;
}
@NonNull InsightsDashboardState build() {
return new InsightsDashboardState(this);
}
}
}

View file

@ -1,68 +0,0 @@
package org.thoughtcrime.securesms.insights;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.List;
final class InsightsDashboardViewModel extends ViewModel {
private final MutableLiveData<InsightsDashboardState> internalState = new MutableLiveData<>(InsightsDashboardState.builder().build());
private final Repository repository;
private InsightsDashboardViewModel(@NonNull Repository repository) {
this.repository = repository;
repository.getInsightsData(data -> internalState.setValue(getNewState(b -> b.withData(data))));
repository.getUserAvatar(avatar -> internalState.setValue(getNewState(b -> b.withUserAvatar(avatar))));
updateInsecureRecipients();
}
private void updateInsecureRecipients() {
repository.getInsecureRecipients(recipients -> internalState.setValue(getNewState(b -> b.withInsecureRecipients(recipients))));
}
@MainThread
private InsightsDashboardState getNewState(Consumer<InsightsDashboardState.Builder> builderConsumer) {
InsightsDashboardState.Builder builder = internalState.getValue().buildUpon();
builderConsumer.accept(builder);
return builder.build();
}
@NonNull LiveData<InsightsDashboardState> getState() {
return internalState;
}
public void sendSmsInvite(@NonNull Recipient recipient) {
repository.sendSmsInvite(recipient, this::updateInsecureRecipients);
}
interface Repository {
void getInsightsData(@NonNull Consumer<InsightsData> insightsDataConsumer);
void getInsecureRecipients(@NonNull Consumer<List<Recipient>> insecureRecipientsConsumer);
void getUserAvatar(@NonNull Consumer<InsightsUserAvatar> userAvatarConsumer);
void sendSmsInvite(@NonNull Recipient recipient, Runnable onSmsMessageSent);
}
final static class Factory implements ViewModelProvider.Factory {
private final Repository repository;
Factory(@NonNull Repository repository) {
this.repository = repository;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new InsightsDashboardViewModel(repository);
}
}
}

View file

@ -1,19 +0,0 @@
package org.thoughtcrime.securesms.insights;
final class InsightsData {
private final boolean hasEnoughData;
private final int percentInsecure;
InsightsData(boolean hasEnoughData, int percentInsecure) {
this.hasEnoughData = hasEnoughData;
this.percentInsecure = percentInsecure;
}
public boolean hasEnoughData() {
return hasEnoughData;
}
public int getPercentInsecure() {
return percentInsecure;
}
}

View file

@ -1,118 +0,0 @@
package org.thoughtcrime.securesms.insights;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.Collections;
import java.util.List;
final class InsightsInsecureRecipientsAdapter extends RecyclerView.Adapter<InsightsInsecureRecipientsAdapter.ViewHolder> {
private List<Recipient> data = Collections.emptyList();
private final Consumer<Recipient> onInviteClickedConsumer;
InsightsInsecureRecipientsAdapter(Consumer<Recipient> onInviteClickedConsumer) {
this.onInviteClickedConsumer = onInviteClickedConsumer;
}
public void updateData(List<Recipient> recipients) {
List<Recipient> oldData = data;
data = recipients;
DiffUtil.calculateDiff(new DiffCallback(oldData, data)).dispatchUpdatesTo(this);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.insights_dashboard_adapter_item, parent, false), this::handleInviteClicked);
}
private void handleInviteClicked(@NonNull Integer position) {
onInviteClickedConsumer.accept(data.get(position));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.bind(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
static final class ViewHolder extends RecyclerView.ViewHolder {
private AvatarImageView avatarImageView;
private TextView displayName;
private ViewHolder(@NonNull View itemView, Consumer<Integer> onInviteClicked) {
super(itemView);
avatarImageView = itemView.findViewById(R.id.recipient_avatar);
displayName = itemView.findViewById(R.id.recipient_display_name);
Button invite = itemView.findViewById(R.id.recipient_invite);
invite.setOnClickListener(v -> {
int adapterPosition = getAdapterPosition();
if (adapterPosition == RecyclerView.NO_POSITION) return;
onInviteClicked.accept(adapterPosition);
});
}
private void bind(@NonNull Recipient recipient) {
displayName.setText(recipient.getDisplayName(itemView.getContext()));
avatarImageView.setAvatar(GlideApp.with(itemView), recipient, false);
}
}
private static class DiffCallback extends DiffUtil.Callback {
private final List<Recipient> oldData;
private final List<Recipient> newData;
private DiffCallback(@NonNull List<Recipient> oldData,
@NonNull List<Recipient> newData)
{
this.oldData = oldData;
this.newData = newData;
}
@Override
public int getOldListSize() {
return oldData.size();
}
@Override
public int getNewListSize() {
return newData.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldData.get(oldItemPosition).getId() == newData.get(newItemPosition).getId();
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldData.get(oldItemPosition).equals(newData.get(newItemPosition));
}
}
}

View file

@ -1,25 +0,0 @@
package org.thoughtcrime.securesms.insights;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
public final class InsightsLauncher {
private static final String MODAL_TAG = "modal.fragment";
public static void showInsightsModal(@NonNull Context context, @NonNull FragmentManager fragmentManager) {
if (InsightsOptOut.userHasOptedOut(context)) return;
final Fragment fragment = fragmentManager.findFragmentByTag(MODAL_TAG);
if (fragment == null) new InsightsModalDialogFragment().show(fragmentManager, MODAL_TAG);
}
public static void showInsightsDashboard(@NonNull FragmentManager fragmentManager) {
new InsightsDashboardDialogFragment().show(fragmentManager, null);
}
}

View file

@ -1,132 +0,0 @@
package org.thoughtcrime.securesms.insights;
import android.animation.AnimatorSet;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.ArcProgressBar;
import org.thoughtcrime.securesms.components.AvatarImageView;
public final class InsightsModalDialogFragment extends DialogFragment {
private ArcProgressBar progress;
private TextView securePercentage;
private AvatarImageView avatarImageView;
private AnimatorSet animatorSet;
private View progressContainer;
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
requireFragmentManager().beginTransaction()
.detach(this)
.attach(this)
.commit();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_FRAME, R.style.Theme_Signal_Insights_Modal);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
return dialog;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.insights_modal, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
View close = view.findViewById(R.id.insights_modal_close);
Button viewInsights = view.findViewById(R.id.insights_modal_view_insights);
progress = view.findViewById(R.id.insights_modal_progress);
securePercentage = view.findViewById(R.id.insights_modal_percent_secure);
avatarImageView = view.findViewById(R.id.insights_modal_avatar);
progressContainer = view.findViewById(R.id.insights_modal_percent_container);
close.setOnClickListener(v -> dismiss());
viewInsights.setOnClickListener(v -> openInsightsAndDismiss());
initializeViewModel();
}
private void initializeViewModel() {
final InsightsModalViewModel.Repository repository = new InsightsRepository(requireContext());
final InsightsModalViewModel.Factory factory = new InsightsModalViewModel.Factory(repository);
final InsightsModalViewModel viewModel = new ViewModelProvider(this, factory).get(InsightsModalViewModel.class);
viewModel.getState().observe(getViewLifecycleOwner(), state -> {
updateInsecurePercent(state.getData());
updateUserAvatar(state.getUserAvatar());
});
}
private void updateInsecurePercent(@Nullable InsightsData insightsData) {
if (insightsData == null) return;
if (animatorSet == null) {
animatorSet = InsightsAnimatorSetFactory.create(insightsData.getPercentInsecure(), this::setProgressPercentage, null, this::setPercentSecureScale, null);
animatorSet.start();
}
}
private void setProgressPercentage(float percent) {
securePercentage.setText(String.valueOf(Math.round(percent * 100)));
progress.setProgress(percent);
}
private void setPercentSecureScale(float scale) {
progressContainer.setScaleX(scale);
progressContainer.setScaleY(scale);
}
private void updateUserAvatar(@Nullable InsightsUserAvatar userAvatar) {
if (userAvatar == null) avatarImageView.setImageDrawable(null);
else userAvatar.load(avatarImageView);
}
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
InsightsOptOut.userRequestedOptOut(requireContext());
}
private void openInsightsAndDismiss() {
InsightsLauncher.showInsightsDashboard(requireFragmentManager());
dismiss();
}
@Override
public void onDestroyView() {
if (animatorSet != null) {
animatorSet.cancel();
animatorSet = null;
}
super.onDestroyView();
}
}

View file

@ -1,53 +0,0 @@
package org.thoughtcrime.securesms.insights;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
final class InsightsModalState {
private final InsightsData insightsData;
private final InsightsUserAvatar userAvatar;
private InsightsModalState(@NonNull Builder builder) {
this.insightsData = builder.insightsData;
this.userAvatar = builder.userAvatar;
}
static @NonNull InsightsModalState.Builder builder() {
return new InsightsModalState.Builder();
}
@NonNull InsightsModalState.Builder buildUpon() {
return builder().withUserAvatar(userAvatar).withData(insightsData);
}
@Nullable InsightsUserAvatar getUserAvatar() {
return userAvatar;
}
@Nullable InsightsData getData() {
return insightsData;
}
static final class Builder {
private InsightsData insightsData;
private InsightsUserAvatar userAvatar;
private Builder() {
}
@NonNull Builder withData(@NonNull InsightsData insightsData) {
this.insightsData = insightsData;
return this;
}
@NonNull Builder withUserAvatar(@NonNull InsightsUserAvatar userAvatar) {
this.userAvatar = userAvatar;
return this;
}
@NonNull InsightsModalState build() {
return new InsightsModalState(this);
}
}
}

View file

@ -1,50 +0,0 @@
package org.thoughtcrime.securesms.insights;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
final class InsightsModalViewModel extends ViewModel {
private final MutableLiveData<InsightsModalState> internalState = new MutableLiveData<>(InsightsModalState.builder().build());
private InsightsModalViewModel(@NonNull Repository repository) {
repository.getInsightsData(data -> internalState.setValue(getNewState(b -> b.withData(data))));
repository.getUserAvatar(avatar -> internalState.setValue(getNewState(b -> b.withUserAvatar(avatar))));
}
@MainThread
private InsightsModalState getNewState(Consumer<InsightsModalState.Builder> builderConsumer) {
InsightsModalState.Builder builder = internalState.getValue().buildUpon();
builderConsumer.accept(builder);
return builder.build();
}
@NonNull LiveData<InsightsModalState> getState() {
return internalState;
}
interface Repository {
void getInsightsData(Consumer<InsightsData> insecurePercentConsumer);
void getUserAvatar(@NonNull Consumer<InsightsUserAvatar> userAvatarConsumer);
}
final static class Factory implements ViewModelProvider.Factory {
private final Repository repository;
Factory(@NonNull Repository repository) {
this.repository = repository;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new InsightsModalViewModel(repository);
}
}
}

View file

@ -1,22 +0,0 @@
package org.thoughtcrime.securesms.insights;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public final class InsightsOptOut {
private static final String INSIGHTS_OPT_OUT_PREFERENCE = "insights.opt.out";
private InsightsOptOut() {
}
static boolean userHasOptedOut(@NonNull Context context) {
return TextSecurePreferences.getBooleanPreference(context, INSIGHTS_OPT_OUT_PREFERENCE, false);
}
public static void userRequestedOptOut(@NonNull Context context) {
TextSecurePreferences.setBooleanPreference(context, INSIGHTS_OPT_OUT_PREFERENCE, true);
}
}

View file

@ -1,89 +0,0 @@
package org.thoughtcrime.securesms.insights;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.mms.OutgoingMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.Util;
import org.signal.core.util.concurrent.SimpleTask;
import java.util.List;
import java.util.Optional;
public class InsightsRepository implements InsightsDashboardViewModel.Repository, InsightsModalViewModel.Repository {
private final Context context;
public InsightsRepository(Context context) {
this.context = context.getApplicationContext();
}
@Override
public void getInsightsData(@NonNull Consumer<InsightsData> insightsDataConsumer) {
SimpleTask.run(() -> {
MessageTable messageTable = SignalDatabase.messages();
int insecure = messageTable.getInsecureMessageCountForInsights();
int secure = messageTable.getSecureMessageCountForInsights();
if (insecure + secure == 0) {
return new InsightsData(false, 0);
} else {
return new InsightsData(true, Util.clamp((int) Math.ceil((insecure * 100f) / (insecure + secure)), 0, 100));
}
}, insightsDataConsumer::accept);
}
@Override
public void getInsecureRecipients(@NonNull Consumer<List<Recipient>> insecureRecipientsConsumer) {
SimpleTask.run(() -> {
RecipientTable recipientTable = SignalDatabase.recipients();
List<RecipientId> unregisteredRecipients = recipientTable.getUninvitedRecipientsForInsights();
return Stream.of(unregisteredRecipients)
.map(Recipient::resolved)
.toList();
},
insecureRecipientsConsumer::accept);
}
@Override
public void getUserAvatar(@NonNull Consumer<InsightsUserAvatar> avatarConsumer) {
SimpleTask.run(() -> {
Recipient self = Recipient.self().resolve();
String name = Optional.of(self.getDisplayName(context)).orElse("");
return new InsightsUserAvatar(new ProfileContactPhoto(self),
self.getAvatarColor(),
new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40));
}, avatarConsumer::accept);
}
@Override
public void sendSmsInvite(@NonNull Recipient recipient, Runnable onSmsMessageSent) {
SimpleTask.run(() -> {
Recipient resolved = recipient.resolve();
int subscriptionId = resolved.getDefaultSubscriptionId().orElse(-1);
String message = context.getString(R.string.InviteActivity_lets_switch_to_signal, context.getString(R.string.install_url));
MessageSender.send(context, OutgoingMessage.sms(resolved, message, subscriptionId), -1L, MessageSender.SendType.SMS, null, null);
RecipientTable database = SignalDatabase.recipients();
database.setHasSentInvite(recipient.getId());
return null;
}, v -> onSmsMessageSent.run());
}
}

View file

@ -1,39 +0,0 @@
package org.thoughtcrime.securesms.insights;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.mms.GlideApp;
class InsightsUserAvatar {
private final ProfileContactPhoto profileContactPhoto;
private final AvatarColor fallbackColor;
private final FallbackContactPhoto fallbackContactPhoto;
InsightsUserAvatar(@NonNull ProfileContactPhoto profileContactPhoto, @NonNull AvatarColor fallbackColor, @NonNull FallbackContactPhoto fallbackContactPhoto) {
this.profileContactPhoto = profileContactPhoto;
this.fallbackColor = fallbackColor;
this.fallbackContactPhoto = fallbackContactPhoto;
}
private Drawable fallbackDrawable(@NonNull Context context) {
return fallbackContactPhoto.asDrawable(context, fallbackColor);
}
void load(ImageView into) {
GlideApp.with(into)
.load(profileContactPhoto)
.error(fallbackDrawable(into.getContext()))
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(into);
}
}

View file

@ -1,143 +0,0 @@
package org.thoughtcrime.securesms.invites;
import android.content.Context;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import org.thoughtcrime.securesms.components.reminder.FirstInviteReminder;
import org.thoughtcrime.securesms.components.reminder.Reminder;
import org.thoughtcrime.securesms.components.reminder.SecondInviteReminder;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.signal.core.util.concurrent.SimpleTask;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
public final class InviteReminderModel {
private static final int FIRST_INVITE_REMINDER_MESSAGE_THRESHOLD = 10;
private static final int SECOND_INVITE_REMINDER_MESSAGE_THRESHOLD = 500;
private final Context context;
private final Repository repository;
private final AtomicReference<ReminderInfo> reminderInfo = new AtomicReference<>();
public InviteReminderModel(@NonNull Context context, @NonNull Repository repository) {
this.context = context;
this.repository = repository;
}
@MainThread
public void loadReminder(LiveRecipient liveRecipient, Runnable reminderCheckComplete) {
SimpleTask.run(() -> createReminderInfo(liveRecipient.resolve()), result -> {
reminderInfo.set(result);
reminderCheckComplete.run();
});
}
@WorkerThread
private @NonNull ReminderInfo createReminderInfo(Recipient recipient) {
Recipient resolved = recipient.resolve();
if (resolved.isRegistered() || resolved.isGroup() || resolved.hasSeenSecondInviteReminder()) {
return new NoReminderInfo();
}
ThreadTable threadTable = SignalDatabase.threads();
Long threadId = threadTable.getThreadIdFor(recipient.getId());
if (threadId != null) {
int conversationCount = SignalDatabase.messages().getInsecureMessageSentCount(threadId);
if (conversationCount >= SECOND_INVITE_REMINDER_MESSAGE_THRESHOLD && !resolved.hasSeenSecondInviteReminder()) {
return new SecondInviteReminderInfo(context, resolved, repository, repository.getPercentOfInsecureMessages(conversationCount));
} else if (conversationCount >= FIRST_INVITE_REMINDER_MESSAGE_THRESHOLD && !resolved.hasSeenFirstInviteReminder()) {
return new FirstInviteReminderInfo(resolved, repository, repository.getPercentOfInsecureMessages(conversationCount));
}
}
return new NoReminderInfo();
}
public @NonNull Optional<Reminder> getReminder() {
ReminderInfo info = reminderInfo.get();
if (info == null) return Optional.empty();
else return Optional.ofNullable(info.reminder);
}
public void dismissReminder() {
final ReminderInfo info = reminderInfo.getAndSet(null);
SimpleTask.run(() -> {
info.dismiss();
return null;
}, (v) -> {});
}
interface Repository {
void setHasSeenFirstInviteReminder(Recipient recipient);
void setHasSeenSecondInviteReminder(Recipient recipient);
int getPercentOfInsecureMessages(int insecureCount);
}
private static abstract class ReminderInfo {
private final Reminder reminder;
ReminderInfo(Reminder reminder) {
this.reminder = reminder;
}
@WorkerThread
void dismiss() {
}
}
private static class NoReminderInfo extends ReminderInfo {
private NoReminderInfo() {
super(null);
}
}
private class FirstInviteReminderInfo extends ReminderInfo {
private final Repository repository;
private final Recipient recipient;
private FirstInviteReminderInfo(@NonNull Recipient recipient, @NonNull Repository repository, int percentInsecure) {
super(new FirstInviteReminder(percentInsecure));
this.recipient = recipient;
this.repository = repository;
}
@Override
@WorkerThread
void dismiss() {
repository.setHasSeenFirstInviteReminder(recipient);
}
}
private static class SecondInviteReminderInfo extends ReminderInfo {
private final Repository repository;
private final Recipient recipient;
private SecondInviteReminderInfo(@NonNull Context context, @NonNull Recipient recipient, @NonNull Repository repository, int percentInsecure) {
super(new SecondInviteReminder(context, recipient, percentInsecure));
this.repository = repository;
this.recipient = recipient;
}
@Override
@WorkerThread
void dismiss() {
repository.setHasSeenSecondInviteReminder(recipient);
}
}
}

View file

@ -1,39 +0,0 @@
package org.thoughtcrime.securesms.invites;
import android.content.Context;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
public final class InviteReminderRepository implements InviteReminderModel.Repository {
private final Context context;
public InviteReminderRepository(Context context) {
this.context = context;
}
@Override
public void setHasSeenFirstInviteReminder(Recipient recipient) {
RecipientTable recipientTable = SignalDatabase.recipients();
recipientTable.setSeenFirstInviteReminder(recipient.getId());
}
@Override
public void setHasSeenSecondInviteReminder(Recipient recipient) {
RecipientTable recipientTable = SignalDatabase.recipients();
recipientTable.setSeenSecondInviteReminder(recipient.getId());
}
@Override
public int getPercentOfInsecureMessages(int insecureCount) {
MessageTable messageTable = SignalDatabase.messages();
int insecure = messageTable.getInsecureMessageCountForInsights();
int secure = messageTable.getSecureMessageCountForInsights();
if (insecure + secure == 0) return 0;
return Math.round(100f * (insecureCount / (float) (insecure + secure)));
}
}

View file

@ -91,7 +91,7 @@ public final class MmsSendJob extends SendJob {
}
List<Job> compressionJobs = Stream.of(message.getAttachments())
.map(a -> (Job) AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, true, message.getSubscriptionId()))
.map(a -> (Job) AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, true, -1))
.toList();
MmsSendJob sendJob = new MmsSendJob(messageId);
@ -139,7 +139,7 @@ public final class MmsSendJob extends SendJob {
validateDestinations(message, pdu);
final byte[] pduBytes = getPduBytes(pdu);
final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, message.getSubscriptionId());
final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, -1);
final MmsSendResult result = getSendResult(sendConf, pdu);
database.markAsSent(messageId, false);
@ -231,7 +231,7 @@ public final class MmsSendJob extends SendJob {
{
SendReq req = new SendReq();
String lineNumber = getMyNumber(context);
MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(message.getSubscriptionId());
MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(-1);
List<Attachment> scaledAttachments = message.getAttachments();
if (!TextUtils.isEmpty(lineNumber)) {

View file

@ -108,7 +108,7 @@ public class TypingSendJob extends BaseJob {
return;
}
if (!recipient.isRegistered() || recipient.isForceSmsSelection()) {
if (!recipient.isRegistered()) {
Log.w(TAG, "Not sending typing indicators to non-Signal recipients.");
return;
}

View file

@ -414,8 +414,6 @@ public class MessageContentProcessor {
warn(String.valueOf(content.getTimestamp()), "Got unrecognized message!");
}
resetRecipientToPush(senderRecipient);
if (pending != null) {
warn(content.getTimestamp(), "Pending retry was processed. Deleting.");
ApplicationDependencies.getPendingRetryReceiptCache().delete(pending);
@ -2219,7 +2217,6 @@ public class MessageContentProcessor {
body,
Collections.emptyList(),
message.getTimestamp(),
-1,
expiresInMillis,
false,
ThreadTable.DistributionTypes.DEFAULT,
@ -2342,7 +2339,6 @@ public class MessageContentProcessor {
textStoryBody,
pendingAttachments,
sentAtTimestamp,
-1,
0,
false,
ThreadTable.DistributionTypes.DEFAULT,
@ -2442,7 +2438,6 @@ public class MessageContentProcessor {
message.getDataMessage().get().getBody().orElse(null),
syncAttachments,
message.getTimestamp(),
-1,
TimeUnit.SECONDS.toMillis(message.getDataMessage().get().getExpiresInSeconds()),
viewOnce,
ThreadTable.DistributionTypes.DEFAULT,
@ -2657,7 +2652,6 @@ public class MessageContentProcessor {
new SlideDeck(),
body,
message.getTimestamp(),
-1,
expiresInMillis,
false,
StoryType.NONE,
@ -3403,12 +3397,6 @@ public class MessageContentProcessor {
return false;
}
private void resetRecipientToPush(@NonNull Recipient recipient) {
if (recipient.isForceSmsSelection()) {
SignalDatabase.recipients().setForceSmsSelection(recipient.getId(), false);
}
}
private void forceStickerDownloadIfNecessary(long messageId, List<DatabaseAttachment> stickerAttachments) {
if (stickerAttachments.isEmpty()) return;

View file

@ -284,12 +284,6 @@ open class MessageContentProcessorV2(private val context: Context) {
null
}
}
private fun resetRecipientToPush(recipient: Recipient) {
if (recipient.isForceSmsSelection) {
SignalDatabase.recipients.setForceSmsSelection(recipient.id, false)
}
}
}
/**
@ -425,8 +419,6 @@ open class MessageContentProcessorV2(private val context: Context) {
}
}
resetRecipientToPush(senderRecipient)
if (pending != null) {
warn(envelope.timestamp, "Pending retry was processed. Deleting.")
ApplicationDependencies.getPendingRetryReceiptCache().delete(pending)

View file

@ -23,7 +23,6 @@ data class OutgoingMessage(
val sentTimeMillis: Long,
val body: String = "",
val distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
val subscriptionId: Int = -1,
val expiresIn: Long = 0L,
val isViewOnce: Boolean = false,
val outgoingQuote: QuoteModel? = null,
@ -66,7 +65,6 @@ data class OutgoingMessage(
body: String? = "",
attachments: List<Attachment> = emptyList(),
timestamp: Long,
subscriptionId: Int = -1,
expiresIn: Long = 0L,
viewOnce: Boolean = false,
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
@ -89,7 +87,6 @@ data class OutgoingMessage(
body = body ?: "",
attachments = attachments,
sentTimeMillis = timestamp,
subscriptionId = subscriptionId,
expiresIn = expiresIn,
isViewOnce = viewOnce,
distributionType = distributionType,
@ -117,7 +114,6 @@ data class OutgoingMessage(
slideDeck: SlideDeck,
body: String? = "",
timestamp: Long,
subscriptionId: Int = -1,
expiresIn: Long = 0L,
viewOnce: Boolean = false,
storyType: StoryType = StoryType.NONE,
@ -131,7 +127,6 @@ data class OutgoingMessage(
body = buildMessage(slideDeck, body ?: ""),
attachments = slideDeck.asAttachments(),
sentTimeMillis = timestamp,
subscriptionId = subscriptionId,
expiresIn = expiresIn,
isViewOnce = viewOnce,
storyType = storyType,
@ -142,6 +137,8 @@ data class OutgoingMessage(
sharedContacts = contacts
)
val subscriptionId = -1
fun withExpiry(expiresIn: Long): OutgoingMessage {
return copy(expiresIn = expiresIn)
}
@ -172,12 +169,11 @@ data class OutgoingMessage(
* A literal, insecure SMS message.
*/
@JvmStatic
fun sms(threadRecipient: Recipient, body: String, subscriptionId: Int): OutgoingMessage {
fun sms(threadRecipient: Recipient, body: String): OutgoingMessage {
return OutgoingMessage(
threadRecipient = threadRecipient,
sentTimeMillis = System.currentTimeMillis(),
body = body,
subscriptionId = subscriptionId,
isSecure = false
)
}

View file

@ -76,7 +76,6 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
long threadId;
Recipient recipient = Recipient.resolved(recipientId);
int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1);
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
ParentStoryId parentStoryId = groupStoryId != Long.MIN_VALUE ? ParentStoryId.deserialize(groupStoryId) : null;
@ -86,7 +85,6 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
responseText.toString(),
new LinkedList<>(),
System.currentTimeMillis(),
subscriptionId,
expiresIn,
false,
0,
@ -112,11 +110,6 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
threadId = MessageSender.send(context, reply, -1, MessageSender.SendType.SIGNAL, null, null);
break;
}
case UnsecuredSmsMessage: {
OutgoingMessage reply = OutgoingMessage.sms(recipient, responseText.toString(), subscriptionId);
threadId = MessageSender.send(context, reply, -1, MessageSender.SendType.SMS, null, null);
break;
}
default:
throw new AssertionError("Unknown Reply method");
}

View file

@ -1,26 +1,19 @@
package org.thoughtcrime.securesms.notifications;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
public enum ReplyMethod {
GroupMessage,
SecureMessage,
UnsecuredSmsMessage;
SecureMessage;
public static @NonNull ReplyMethod forRecipient(Context context, Recipient recipient) {
public static @NonNull ReplyMethod forRecipient(Recipient recipient) {
if (recipient.isGroup()) {
return ReplyMethod.GroupMessage;
} else if (SignalStore.account().isRegistered() && recipient.getRegistered() == RecipientTable.RegisteredState.REGISTERED && !recipient.isForceSmsSelection()) {
return ReplyMethod.SecureMessage;
} else {
return ReplyMethod.UnsecuredSmsMessage;
return ReplyMethod.SecureMessage;
}
}
}

View file

@ -123,7 +123,7 @@ sealed class NotificationBuilder(protected val context: Context) {
}
}
addActions(ReplyMethod.forRecipient(context, conversation.recipient), conversation)
addActions(ReplyMethod.forRecipient(conversation.recipient), conversation)
}
}
@ -504,6 +504,5 @@ private fun ReplyMethod.toLongDescription(): Int {
return when (this) {
ReplyMethod.GroupMessage -> R.string.MessageNotifier_reply
ReplyMethod.SecureMessage -> R.string.MessageNotifier_signal_message
ReplyMethod.UnsecuredSmsMessage -> R.string.MessageNotifier_unsecured_sms
}
}

View file

@ -104,7 +104,6 @@ public class Recipient {
private final VibrateState callVibrate;
private final Uri messageRingtone;
private final Uri callRingtone;
private final Optional<Integer> defaultSubscriptionId;
private final int expireMessages;
private final RegisteredState registered;
private final byte[] profileKey;
@ -121,9 +120,7 @@ public class Recipient {
private final long lastProfileFetch;
private final String notificationChannel;
private final UnidentifiedAccessMode unidentifiedAccessMode;
private final boolean forceSmsSelection;
private final RecipientRecord.Capabilities capabilities;
private final InsightsBannerTier insightsBannerTier;
private final byte[] storageId;
private final MentionSetting mentionSetting;
private final ChatWallpaper wallpaper;
@ -392,8 +389,6 @@ public class Recipient {
this.callVibrate = VibrateState.DEFAULT;
this.messageRingtone = null;
this.callRingtone = null;
this.insightsBannerTier = InsightsBannerTier.TIER_TWO;
this.defaultSubscriptionId = Optional.empty();
this.expireMessages = 0;
this.registered = RegisteredState.UNKNOWN;
this.profileKey = null;
@ -410,7 +405,6 @@ public class Recipient {
this.lastProfileFetch = 0;
this.notificationChannel = null;
this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED;
this.forceSmsSelection = false;
this.capabilities = RecipientRecord.Capabilities.UNKNOWN;
this.storageId = null;
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
@ -450,8 +444,6 @@ public class Recipient {
this.callVibrate = details.callVibrateState;
this.messageRingtone = details.messageRingtone;
this.callRingtone = details.callRingtone;
this.insightsBannerTier = details.insightsBannerTier;
this.defaultSubscriptionId = details.defaultSubscriptionId;
this.expireMessages = details.expireMessages;
this.registered = details.registered;
this.profileKey = details.profileKey;
@ -468,7 +460,6 @@ public class Recipient {
this.lastProfileFetch = details.lastProfileFetch;
this.notificationChannel = details.notificationChannel;
this.unidentifiedAccessMode = details.unidentifiedAccessMode;
this.forceSmsSelection = details.forceSmsSelection;
this.capabilities = details.capabilities;
this.storageId = details.storageId;
this.mentionSetting = details.mentionSetting;
@ -840,10 +831,6 @@ public class Recipient {
return requireSmsAddress();
}
public Optional<Integer> getDefaultSubscriptionId() {
return defaultSubscriptionId;
}
public @NonNull ProfileName getProfileName() {
return signalProfileName;
}
@ -1029,14 +1016,6 @@ public class Recipient {
return expireMessages;
}
public boolean hasSeenFirstInviteReminder() {
return insightsBannerTier.seen(InsightsBannerTier.TIER_ONE);
}
public boolean hasSeenSecondInviteReminder() {
return insightsBannerTier.seen(InsightsBannerTier.TIER_TWO);
}
public @NonNull RegisteredState getRegistered() {
if (isPushGroup() || isDistributionList()) {
return RegisteredState.REGISTERED;
@ -1063,10 +1042,6 @@ public class Recipient {
return !NotificationChannels.supported() ? null : notificationChannel;
}
public boolean isForceSmsSelection() {
return forceSmsSelection;
}
public @NonNull Capability getStoriesCapability() {
return capabilities.getStoriesCapability();
}
@ -1353,7 +1328,6 @@ public class Recipient {
Objects.equals(profileAvatarFileDetails, other.profileAvatarFileDetails) &&
profileSharing == other.profileSharing &&
isHidden == other.isHidden &&
forceSmsSelection == other.forceSmsSelection &&
Objects.equals(aci, other.aci) &&
Objects.equals(username, other.username) &&
Objects.equals(e164, other.e164) &&
@ -1365,7 +1339,6 @@ public class Recipient {
callVibrate == other.callVibrate &&
Objects.equals(messageRingtone, other.messageRingtone) &&
Objects.equals(callRingtone, other.callRingtone) &&
Objects.equals(defaultSubscriptionId, other.defaultSubscriptionId) &&
registered == other.registered &&
Arrays.equals(profileKey, other.profileKey) &&
Objects.equals(expiringProfileKeyCredential, other.expiringProfileKeyCredential) &&
@ -1378,7 +1351,6 @@ public class Recipient {
Objects.equals(profileAvatar, other.profileAvatar) &&
Objects.equals(notificationChannel, other.notificationChannel) &&
unidentifiedAccessMode == other.unidentifiedAccessMode &&
insightsBannerTier == other.insightsBannerTier &&
Arrays.equals(storageId, other.storageId) &&
mentionSetting == other.mentionSetting &&
Objects.equals(wallpaper, other.wallpaper) &&

View file

@ -58,7 +58,6 @@ public class RecipientDetails {
final int expireMessages;
final List<RecipientId> participantIds;
final ProfileName profileName;
final Optional<Integer> defaultSubscriptionId;
final RegisteredState registered;
final byte[] profileKey;
final ExpiringProfileKeyCredential expiringProfileKeyCredential;
@ -72,9 +71,7 @@ public class RecipientDetails {
final boolean isSelf;
final String notificationChannel;
final UnidentifiedAccessMode unidentifiedAccessMode;
final boolean forceSmsSelection;
final RecipientRecord.Capabilities capabilities;
final InsightsBannerTier insightsBannerTier;
final byte[] storageId;
final MentionSetting mentionSetting;
final ChatWallpaper wallpaper;
@ -125,7 +122,6 @@ public class RecipientDetails {
this.participantIds = participantIds == null ? new LinkedList<>() : participantIds;
this.isActiveGroup = isActiveGroup;
this.profileName = record.getProfileName();
this.defaultSubscriptionId = record.getDefaultSubscriptionId();
this.registered = registeredState;
this.profileKey = record.getProfileKey();
this.expiringProfileKeyCredential = record.getExpiringProfileKeyCredential();
@ -138,9 +134,7 @@ public class RecipientDetails {
this.isSelf = isSelf;
this.notificationChannel = record.getNotificationChannel();
this.unidentifiedAccessMode = record.getUnidentifiedAccessMode();
this.forceSmsSelection = record.isForceSmsSelection();
this.capabilities = record.getCapabilities();
this.insightsBannerTier = record.getInsightsBannerTier();
this.storageId = record.getStorageId();
this.mentionSetting = record.getMentionSetting();
this.wallpaper = record.getWallpaper();
@ -181,8 +175,6 @@ public class RecipientDetails {
this.expireMessages = 0;
this.participantIds = new LinkedList<>();
this.profileName = ProfileName.EMPTY;
this.insightsBannerTier = InsightsBannerTier.TIER_TWO;
this.defaultSubscriptionId = Optional.empty();
this.registered = RegisteredState.UNKNOWN;
this.profileKey = null;
this.expiringProfileKeyCredential = null;
@ -195,7 +187,6 @@ public class RecipientDetails {
this.isSelf = false;
this.notificationChannel = null;
this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
this.forceSmsSelection = false;
this.groupName = null;
this.capabilities = RecipientRecord.Capabilities.UNKNOWN;
this.storageId = null;

View file

@ -297,7 +297,6 @@ public class RecipientUtil {
threadRecipient.isProfileSharing() ||
threadRecipient.isSystemContact() ||
!threadRecipient.isRegistered() ||
threadRecipient.isForceSmsSelection() ||
threadRecipient.isHidden();
}
@ -346,7 +345,6 @@ public class RecipientUtil {
threadRecipient.isSelf() ||
threadRecipient.isProfileSharing() ||
threadRecipient.isSystemContact() ||
threadRecipient.isForceSmsSelection() ||
!threadRecipient.isRegistered() ||
(!threadRecipient.isHidden() && (
hasSentMessageInThread(threadId) ||

View file

@ -46,11 +46,10 @@ public class QuickResponseService extends IntentService {
number = URLDecoder.decode(number);
}
Recipient recipient = Recipient.external(this, number);
int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1);
Recipient recipient = Recipient.external(this, number);
if (!TextUtils.isEmpty(content)) {
MessageSender.send(this, OutgoingMessage.sms(recipient, content, subscriptionId), -1, MessageSender.SendType.SIGNAL, null, null);
MessageSender.send(this, OutgoingMessage.sms(recipient, content), -1, MessageSender.SendType.SIGNAL, null, null);
}
} catch (URISyntaxException e) {
Toast.makeText(this, R.string.QuickResponseService_problem_sending_message, Toast.LENGTH_LONG).show();

View file

@ -108,9 +108,7 @@ public final class MultiShareSender {
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
List<Mention> mentions = getValidMentionsForRecipient(recipient, multiShareArgs.getMentions());
MessageSendType sendType = resolveTransportOption(context, recipient);
boolean forceSms = recipient.isForceSmsSelection() && sendType.usesSmsTransport();
int subscriptionId = sendType.getSimSubscriptionIdOr(-1);
MessageSendType sendType = MessageSendType.SignalMessageSendType.INSTANCE;
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
List<Contact> contacts = multiShareArgs.getSharedContacts();
boolean needsSplit = !sendType.usesSmsTransport() &&
@ -139,10 +137,8 @@ public final class MultiShareSender {
slideDeck,
sendType,
threadId,
forceSms,
expiresIn,
multiShareArgs.isViewOnce(),
subscriptionId,
mentions,
recipientSearchKey.isStory(),
sentTimestamp,
@ -154,7 +150,7 @@ public final class MultiShareSender {
} else if (recipientSearchKey.isStory()) {
results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.INVALID_SHARE_TO_STORY));
} else {
sendTextMessage(context, multiShareArgs, recipient, threadId, forceSms, expiresIn, subscriptionId);
sendTextMessage(context, multiShareArgs, recipient, threadId, expiresIn);
results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.SUCCESS));
}
@ -179,39 +175,14 @@ public final class MultiShareSender {
return new MultiShareSendResultCollection(results);
}
public static @NonNull MessageSendType getWorstTransportOption(@NonNull Context context, @NonNull Set<ContactSearchKey.RecipientSearchKey> recipientSearchKeys) {
for (ContactSearchKey.RecipientSearchKey recipientSearchKey : recipientSearchKeys) {
MessageSendType type = resolveTransportOption(context, Recipient.resolved(recipientSearchKey.getRecipientId()).isForceSmsSelection() && !recipientSearchKey.isStory());
if (type.usesSmsTransport()) {
return type;
}
}
return MessageSendType.SignalMessageSendType.INSTANCE;
}
private static @NonNull MessageSendType resolveTransportOption(@NonNull Context context, @NonNull Recipient recipient) {
return resolveTransportOption(context, !recipient.isDistributionList() && (recipient.isForceSmsSelection() || !recipient.isRegistered()));
}
public static @NonNull MessageSendType resolveTransportOption(@NonNull Context context, boolean forceSms) {
if (forceSms && SignalStore.misc().getSmsExportPhase().allowSmsFeatures()) {
return MessageSendType.getFirstForTransport(context, false, MessageSendType.TransportType.SMS);
} else {
return MessageSendType.SignalMessageSendType.INSTANCE;
}
}
private static void sendMediaMessageOrCollectStoryToBatch(@NonNull Context context,
@NonNull MultiShareArgs multiShareArgs,
@NonNull Recipient recipient,
@NonNull SlideDeck slideDeck,
@NonNull MessageSendType sendType,
long threadId,
boolean forceSms,
long expiresIn,
boolean isViewOnce,
int subscriptionId,
@NonNull List<Mention> validatedMentions,
boolean isStory,
@NonNull MultiShareTimestampProvider sentTimestamps,
@ -221,7 +192,7 @@ public final class MultiShareSender {
@NonNull List<Contact> contacts)
{
String body = multiShareArgs.getDraftText();
if (sendType.usesSignalTransport() && !forceSms && body != null) {
if (sendType.usesSignalTransport() && body != null) {
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body, sendType.calculateCharacters(body).maxPrimaryMessageSize);
body = splitMessage.getBody();
@ -249,7 +220,6 @@ public final class MultiShareSender {
new SlideDeck(),
body,
sentTimestamps.getMillis(0),
subscriptionId,
0L,
false,
storyType.toTextStoryType(),
@ -289,7 +259,6 @@ public final class MultiShareSender {
singletonDeck,
body,
sentTimestamps.getMillis(i),
subscriptionId,
0L,
false,
storyType,
@ -307,7 +276,6 @@ public final class MultiShareSender {
slideDeck,
body,
sentTimestamps.getMillis(0),
subscriptionId,
expiresIn,
isViewOnce,
StoryType.NONE,
@ -322,7 +290,7 @@ public final class MultiShareSender {
if (isStory) {
storiesToBatchSend.addAll(outgoingMessages);
} else if (shouldSendAsPush(recipient, forceSms)) {
} else if (shouldSendAsPush(recipient)) {
for (final OutgoingMessage outgoingMessage : outgoingMessages) {
MessageSender.send(context, outgoingMessage.makeSecure(), threadId, SendType.SIGNAL, null, null);
}
@ -399,20 +367,18 @@ public final class MultiShareSender {
@NonNull MultiShareArgs multiShareArgs,
@NonNull Recipient recipient,
long threadId,
boolean forceSms,
long expiresIn,
int subscriptionId)
long expiresIn)
{
String body = multiShareArgs.getDraftText() == null ? "" : multiShareArgs.getDraftText();
OutgoingMessage outgoingMessage;
if (shouldSendAsPush(recipient, forceSms)) {
if (shouldSendAsPush(recipient)) {
outgoingMessage = OutgoingMessage.text(recipient, body, expiresIn, System.currentTimeMillis(), multiShareArgs.getBodyRanges());
} else {
outgoingMessage = OutgoingMessage.sms(recipient, body, subscriptionId);
outgoingMessage = OutgoingMessage.sms(recipient, body);
}
MessageSender.send(context, outgoingMessage, threadId, forceSms ? SendType.SMS : SendType.SIGNAL, null, null);
MessageSender.send(context, outgoingMessage, threadId, SendType.SIGNAL, null, null);
}
private static @NonNull OutgoingMessage generateTextStory(@NonNull Context context,
@ -458,10 +424,10 @@ public final class MultiShareSender {
return trimmed.replace(linkPreview.getUrl(), "").trim();
}
private static boolean shouldSendAsPush(@NonNull Recipient recipient, boolean forceSms) {
private static boolean shouldSendAsPush(@NonNull Recipient recipient) {
return recipient.isDistributionList() ||
recipient.isServiceIdOnly() ||
(recipient.isRegistered() && !forceSms);
recipient.isRegistered();
}
private static @NonNull SlideDeck buildSlideDeck(@NonNull Context context, @NonNull MultiShareArgs multiShareArgs) throws SlideNotFoundException {

View file

@ -93,7 +93,7 @@ public class ShareInterstitialActivity extends PassphraseRequiredActivity {
return false;
}
return !recipient.isRegistered() || recipient.isForceSmsSelection();
return !recipient.isRegistered();
});
if (hasSms) {

View file

@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFullScreenDialogFragment
@ -271,7 +272,7 @@ class ShareActivity : PassphraseRequiredActivity(), MultiselectForwardFragment.C
val intent = share(
this,
MultiShareSender.getWorstTransportOption(this, multiShareArgs.recipientSearchKeys),
MessageSendType.SignalMessageSendType,
media,
multiShareArgs.recipientSearchKeys.toList(),
multiShareArgs.draftText,

View file

@ -1,200 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:viewBindingIgnore="true"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constraintBottom_toTopOf="@id/insights_dashboard_this_stat_was_generated_locally"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="?actionBarSize">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/insights_dashboard_lottie_animation"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/insights_dashboard_progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/insights_dashboard_progress"
app:lottie_rawRes="@raw/lottie_insights_100" />
<org.thoughtcrime.securesms.components.ArcProgressBar
android:id="@+id/insights_dashboard_progress"
style="@style/Widget.Signal.ArcProgressBar"
android:layout_width="187dp"
android:layout_height="187dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/insights_dashboard_avatar"
android:layout_width="140dp"
android:layout_height="140dp"
app:layout_constraintBottom_toBottomOf="@id/insights_dashboard_progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/insights_dashboard_progress" />
<LinearLayout
android:id="@+id/insights_dashboard_percent_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_dashboard_avatar">
<TextView
android:id="@+id/insights_dashboard_percent_secure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textAppearance="@style/TextAppearance.Signal.Headline.Insights"
tools:text="100" />
<TextView
android:id="@+id/insights_dashboard_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="1dp"
android:gravity="center"
android:text="@string/Insights__percent"
android:textAppearance="@style/TextAppearance.Signal.SubHead.Insights" />
</LinearLayout>
<TextView
android:id="@+id/insights_dashboard_encrypted_messages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@string/InsightsDashboardFragment__encrypted_messages"
android:textAppearance="@style/TextAppearance.Signal.SubHead.Insights"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_dashboard_percent_container" />
<TextView
android:id="@+id/insights_dashboard_tagline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:lineSpacingMultiplier="1.2"
android:textAppearance="@style/TextAppearance.Signal.Body.Insights"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_dashboard_encrypted_messages"
tools:text="100% of your outgoing messages in the past 7 days were end-to-end encrypted with Signal Protocol." />
<com.google.android.material.button.MaterialButton
android:id="@+id/insights_dashboard_start_a_conversation"
style="@style/Signal.Widget.Button.Large.Secondary"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="22dp"
android:layout_marginEnd="16dp"
android:text="@string/InsightsDashboardFragment__start_a_conversation"
android:textAppearance="@style/TextAppearance.Signal.Caption.Insights"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_dashboard_tagline" />
<TextView
android:id="@+id/insights_dashboard_make_signal_secure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:gravity="center"
android:text="@string/InsightsDashboardFragment__spread_the_word"
android:textAppearance="@style/TextAppearance.Signal.SubHead.Insights"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_dashboard_tagline" />
<TextView
android:id="@+id/insights_dashboard_invite_your_contacts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:lineSpacingMultiplier="1.2"
android:text="@string/InsightsDashboardFragment__invite_your_contacts"
android:textAppearance="@style/TextAppearance.Signal.Body.Insights"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_dashboard_make_signal_secure" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/insights_dashboard_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_dashboard_invite_your_contacts"
tools:itemCount="10"
tools:listitem="@layout/insights_dashboard_adapter_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<androidx.appcompat.widget.Toolbar
android:id="@+id/insights_dashboard_toolbar"
style="?actionBarStyle"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?android:windowBackground"
android:theme="?actionBarStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_x_tinted"
app:title="@string/InsightsDashboardFragment__title" />
<TextView
android:id="@+id/insights_dashboard_this_stat_was_generated_locally"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/signal_background_secondary"
android:gravity="center"
android:lineSpacingMultiplier="1.2"
android:paddingStart="16dp"
android:paddingTop="24dp"
android:paddingEnd="16dp"
android:paddingBottom="24dp"
android:text="@string/InsightsDashboardFragment__this_stat_was_generated_locally"
android:textAppearance="@style/TextAppearance.Signal.Body2.Insights"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
tools:viewBindingIgnore="true"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="68dp">
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/recipient_avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/recipient_display_name"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_marginStart="9dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Signal.Title.Insights"
app:layout_constraintEnd_toStartOf="@id/recipient_invite"
app:layout_constraintStart_toEndOf="@id/recipient_avatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Peter Parker (This is a long name to make sure we stick within our bounds)" />
<com.google.android.material.button.MaterialButton
android:id="@+id/recipient_invite"
style="@style/Signal.Widget.Button.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/conversation_insecure__invite"
android:textAppearance="@style/TextAppearance.Signal.Caption.Insights"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,115 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:viewBindingIgnore="true"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/insight_modal_background"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/insights_modal_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:tint="@color/signal_icon_tint_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_x" />
<TextView
android:id="@+id/insights_modal_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/insights_modal_title_margin_top"
android:text="@string/InsightsModalFragment__title"
android:textAppearance="@style/TextAppearance.Signal.SubHead.Insights"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/insights_modal_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="@dimen/insights_modal_description_margin_top"
android:layout_marginEnd="16dp"
android:gravity="center"
android:lineSpacingMultiplier="1.2"
android:text="@string/InsightsModalFragment__description"
android:textAppearance="@style/TextAppearance.Signal.Title.Insights"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_modal_title" />
<org.thoughtcrime.securesms.components.ArcProgressBar
android:id="@+id/insights_modal_progress"
style="@style/Widget.Signal.ArcProgressBar"
android:layout_width="@dimen/insights_modal_progress_size"
android:layout_height="@dimen/insights_modal_progress_size"
android:layout_marginTop="@dimen/insights_modal_progress_margin_top"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_modal_description" />
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/insights_modal_avatar"
android:layout_width="@dimen/insights_modal_avatar_size"
android:layout_height="@dimen/insights_modal_avatar_size"
app:layout_constraintBottom_toBottomOf="@id/insights_modal_progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/insights_modal_progress" />
<LinearLayout
android:id="@+id/insights_modal_percent_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_modal_avatar">
<TextView
android:id="@+id/insights_modal_percent_secure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textAppearance="@style/TextAppearance.Signal.Headline.Insights.Modal.Percent"
tools:text="100" />
<TextView
android:id="@+id/insights_modal_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="center"
android:text="@string/Insights__percent"
android:textAppearance="@style/TextAppearance.Signal.SubHead.Insights.Modal.Percent" />
</LinearLayout>
<Button
android:id="@+id/insights_modal_view_insights"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="@dimen/insights_modal_view_insights_margin_top"
android:layout_marginEnd="16dp"
android:layout_marginBottom="20dp"
android:background="@drawable/insights_cta_button_background"
android:text="@string/InsightsModalFragment__view_insights"
android:textAppearance="@style/TextAppearance.Signal.Caption.Insights"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/insights_modal_progress" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -26,8 +26,4 @@
<item android:title="@string/ConversationListFragment__notification_profile"
android:id="@+id/menu_notification_profile" />
<item android:title="@string/Insights__title"
android:id="@+id/menu_insights"
android:visible="false" />
</menu>

View file

@ -4,7 +4,6 @@
<item type="id" name="contact_info_tag"/>
<item type="id" name="motion_view_edittext"/>
<item name="reminder_action_view_insights" type="id" />
<item name="reminder_action_invite" type="id" />
<item name="reminder_action_update_now" type="id" />
<item name="reminder_action_re_register" type="id" />

View file

@ -3445,33 +3445,6 @@
<string name="new_conversation_activity__refresh">Refresh</string>
<!-- redphone_audio_popup_menu -->
<!-- Insights -->
<string name="Insights__percent">%</string>
<string name="Insights__title">Insights</string>
<string name="InsightsDashboardFragment__title">Insights</string>
<!-- Body text for a page describing how many signal messages you\'ve sent. The first placeholder represents the number of signal messages sent, and the second placeholder represents how many days have passed. -->
<string name="InsightsDashboardFragment__signal_protocol_automatically_protected">Signal Protocol automatically protected %1$d%% of your outgoing messages over the past %2$d days. Chats between Signal users are always end-to-end encrypted.</string>
<string name="InsightsDashboardFragment__spread_the_word">Spread the word</string>
<string name="InsightsDashboardFragment__not_enough_data">Not enough data</string>
<string name="InsightsDashboardFragment__your_insights_percentage_is_calculated_based_on">Your Insights percentage is calculated based on outgoing messages within the past %1$d days that have not disappeared or been deleted.</string>
<!-- Label for a button that, when pressed, will begin a chat with a person whose name is showed on the screen. -->
<string name="InsightsDashboardFragment__start_a_conversation">Start a chat</string>
<string name="InsightsDashboardFragment__invite_your_contacts">Start communicating securely and enable new features that go beyond the limitations of unencrypted SMS messages by inviting more contacts to join Signal.</string>
<string name="InsightsDashboardFragment__this_stat_was_generated_locally">These statistics were locally generated on your device and can only be seen by you. They are never transmitted anywhere.</string>
<string name="InsightsDashboardFragment__encrypted_messages">Encrypted messages</string>
<string name="InsightsDashboardFragment__cancel">Cancel</string>
<string name="InsightsDashboardFragment__send">Send</string>
<string name="InsightsModalFragment__title">Introducing Insights</string>
<string name="InsightsModalFragment__description">Find out how many of your outgoing messages were sent securely, then quickly invite new contacts to boost your Signal percentage.</string>
<string name="InsightsModalFragment__view_insights">View Insights</string>
<string name="FirstInviteReminder__title">Invite to Signal</string>
<string name="FirstInviteReminder__description">You could increase the number of encrypted messages you send by %1$d%%</string>
<string name="SecondInviteReminder__title">Boost your Signal</string>
<string name="SecondInviteReminder__description">Invite %1$s</string>
<string name="InsightsReminder__view_insights">View Insights</string>
<string name="InsightsReminder__invite">Invite</string>
<!-- Edit KBS Pin -->
<!-- BaseKbsPinFragment -->

View file

@ -46,7 +46,6 @@ object RecipientDatabaseTestUtils {
callVibrateState: RecipientTable.VibrateState = RecipientTable.VibrateState.DEFAULT,
messageRingtone: Uri = Uri.EMPTY,
callRingtone: Uri = Uri.EMPTY,
defaultSubscriptionId: Int = 0,
expireMessages: Int = 0,
registered: RecipientTable.RegisteredState = RecipientTable.RegisteredState.REGISTERED,
profileKey: ByteArray = Random.nextBytes(32),
@ -63,9 +62,7 @@ object RecipientDatabaseTestUtils {
lastProfileFetch: Long = 0L,
notificationChannel: String? = null,
unidentifiedAccessMode: RecipientTable.UnidentifiedAccessMode = RecipientTable.UnidentifiedAccessMode.UNKNOWN,
forceSmsSelection: Boolean = false,
capabilities: Long = 0L,
insightBannerTier: RecipientTable.InsightsBannerTier = RecipientTable.InsightsBannerTier.NO_TIER,
storageId: ByteArray? = null,
mentionSetting: RecipientTable.MentionSetting = RecipientTable.MentionSetting.ALWAYS_NOTIFY,
wallpaper: ChatWallpaper? = null,
@ -114,7 +111,6 @@ object RecipientDatabaseTestUtils {
callVibrateState,
messageRingtone,
callRingtone,
defaultSubscriptionId,
expireMessages,
registered,
profileKey,
@ -131,7 +127,6 @@ object RecipientDatabaseTestUtils {
lastProfileFetch,
notificationChannel,
unidentifiedAccessMode,
forceSmsSelection,
RecipientRecord.Capabilities(
capabilities,
Recipient.Capability.deserialize(Bitmask.read(capabilities, RecipientTable.Capabilities.GROUPS_V1_MIGRATION, RecipientTable.Capabilities.BIT_LENGTH).toInt()),
@ -143,7 +138,6 @@ object RecipientDatabaseTestUtils {
Recipient.Capability.deserialize(Bitmask.read(capabilities, RecipientTable.Capabilities.PNP, RecipientTable.Capabilities.BIT_LENGTH).toInt()),
Recipient.Capability.deserialize(Bitmask.read(capabilities, RecipientTable.Capabilities.PAYMENT_ACTIVATION, RecipientTable.Capabilities.BIT_LENGTH).toInt())
),
insightBannerTier,
storageId,
mentionSetting,
wallpaper,

View file

@ -20,7 +20,6 @@ object TestMms {
body: String = "body",
sentTimeMillis: Long = System.currentTimeMillis(),
receivedTimestampMillis: Long = System.currentTimeMillis(),
subscriptionId: Int = -1,
expiresIn: Long = 0,
viewOnce: Boolean = false,
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
@ -35,7 +34,6 @@ object TestMms {
body,
emptyList(),
sentTimeMillis,
subscriptionId,
expiresIn,
viewOnce,
distributionType,