Support for sealed sender - Part 2

This commit is contained in:
Greyson Parrelli
2018-10-11 16:45:22 -07:00
parent 5f31762220
commit 2acab563d9
51 changed files with 819 additions and 394 deletions

View File

@@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
import org.thoughtcrime.securesms.logging.Log;
@@ -109,6 +110,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
initializeCircumvention();
initializeWebRtc();
initializePendingMessages();
initializeUnidentifiedDeliveryAbilityRefresh();
NotificationChannels.create(this);
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
@@ -281,4 +283,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
TextSecurePreferences.setNeedsMessagePull(this, false);
}
}
private void initializeUnidentifiedDeliveryAbilityRefresh() {
if (TextSecurePreferences.isMultiDevice(this) && !TextSecurePreferences.isUnidentifiedDeliveryEnabled(this)) {
jobManager.add(new RefreshUnidentifiedDeliveryAbilityJob(this));
}
}
}

View File

@@ -52,7 +52,6 @@ import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.FileUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -95,6 +94,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
public static final int IMAGE_CACHE_CLEANUP = 406;
public static final int WORKMANAGER_MIGRATION = 408;
public static final int COLOR_MIGRATION = 412;
public static final int UNIDENTIFIED_DELIVERY = 422;
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
@@ -120,6 +120,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
add(IMAGE_CACHE_CLEANUP);
add(WORKMANAGER_MIGRATION);
add(COLOR_MIGRATION);
add(UNIDENTIFIED_DELIVERY);
}};
private MasterSecret masterSecret;
@@ -358,6 +359,18 @@ public class DatabaseUpgradeActivity extends BaseActivity {
Log.i(TAG, "Color migration took " + (System.currentTimeMillis() - startTime) + " ms");
}
if (params[0] < UNIDENTIFIED_DELIVERY) {
if (TextSecurePreferences.isMultiDevice(context)) {
Log.i(TAG, "MultiDevice: Disabling UD (will be re-enabled if possible after pending refresh).");
TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
}
Log.i(TAG, "Scheduling UD attributes refresh.");
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RefreshAttributesJob(context));
}
return null;
}

View File

@@ -11,6 +11,7 @@ import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.transition.TransitionInflater;
import org.thoughtcrime.securesms.logging.Log;
import android.view.MenuItem;
import android.view.View;
@@ -179,6 +180,7 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
Optional<byte[]> profileKey = Optional.of(ProfileKeyUtil.getProfileKey(getContext()));
TextSecurePreferences.setMultiDevice(DeviceActivity.this, true);
TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, profileKey, verificationCode);
return SUCCESS;

View File

@@ -9,6 +9,8 @@ import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AlertDialog;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.logging.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -172,6 +174,10 @@ public class DeviceListFragment extends ListFragment
protected Void doInBackground(Void... params) {
try {
accountManager.removeDevice(deviceId);
ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new RefreshUnidentifiedDeliveryAbilityJob(getContext()));
} catch (IOException e) {
Log.w(TAG, e);
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();

View File

@@ -52,6 +52,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -87,6 +88,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
private View metadataContainer;
private View expiresContainer;
private TextView errorText;
private View resendButton;
private TextView sentDate;
private TextView receivedDate;
private TextView expiresInText;
@@ -176,6 +178,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
recipientsList = findViewById(R.id.recipients_list);
metadataContainer = header.findViewById(R.id.metadata_container);
errorText = header.findViewById(R.id.error_text);
resendButton = header.findViewById(R.id.resend_button);
sentDate = header.findViewById(R.id.sent_time);
receivedContainer = header.findViewById(R.id.received_container);
receivedDate = header.findViewById(R.id.received_time);
@@ -362,7 +365,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
List<RecipientDeliveryStatus> recipients = new LinkedList<>();
if (!messageRecord.getRecipient().isGroupRecipient()) {
recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), -1));
recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), messageRecord.isUnidentified(), -1));
} else {
List<GroupReceiptInfo> receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId());
@@ -370,12 +373,13 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
List<Recipient> group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().getAddress().toGroupString(), false);
for (Recipient recipient : group) {
recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, -1));
recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, false, -1));
}
} else {
for (GroupReceiptInfo info : receiptInfoList) {
recipients.add(new RecipientDeliveryStatus(Recipient.from(context, info.getAddress(), true),
getStatusFor(info.getStatus(), messageRecord.isPending()),
getStatusFor(info.getStatus(), messageRecord.isPending(), messageRecord.isFailed()),
info.isUnidentified(),
info.getTimestamp()));
}
}
@@ -392,16 +396,28 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
}
inflateMessageViewIfAbsent(messageRecord);
updateRecipients(messageRecord, messageRecord.getRecipient(), recipients);
if (messageRecord.isFailed()) {
boolean isGroupNetworkFailure = messageRecord.isFailed() && !messageRecord.getNetworkFailures().isEmpty();
boolean isIndividualNetworkFailure = messageRecord.isFailed() && !isPushGroup && messageRecord.getIdentityKeyMismatches().isEmpty();
if (isGroupNetworkFailure || isIndividualNetworkFailure) {
errorText.setVisibility(View.VISIBLE);
resendButton.setVisibility(View.VISIBLE);
resendButton.setOnClickListener(this::onResendClicked);
metadataContainer.setVisibility(View.GONE);
} else if (messageRecord.isFailed()) {
errorText.setVisibility(View.VISIBLE);
resendButton.setVisibility(View.GONE);
resendButton.setOnClickListener(null);
metadataContainer.setVisibility(View.GONE);
} else {
updateTransport(messageRecord);
updateTime(messageRecord);
updateExpirationTime(messageRecord);
errorText.setVisibility(View.GONE);
resendButton.setVisibility(View.GONE);
resendButton.setOnClickListener(null);
metadataContainer.setVisibility(View.VISIBLE);
}
}
@@ -413,14 +429,19 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
else return RecipientDeliveryStatus.Status.PENDING;
}
private RecipientDeliveryStatus.Status getStatusFor(int groupStatus, boolean pending) {
private RecipientDeliveryStatus.Status getStatusFor(int groupStatus, boolean pending, boolean failed) {
if (groupStatus == GroupReceiptDatabase.STATUS_READ) return RecipientDeliveryStatus.Status.READ;
else if (groupStatus == GroupReceiptDatabase.STATUS_DELIVERED) return RecipientDeliveryStatus.Status.DELIVERED;
else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED && failed) return RecipientDeliveryStatus.Status.UNKNOWN;
else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED && !pending) return RecipientDeliveryStatus.Status.SENT;
else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED) return RecipientDeliveryStatus.Status.PENDING;
else if (groupStatus == GroupReceiptDatabase.STATUS_UNKNOWN) return RecipientDeliveryStatus.Status.UNKNOWN;
throw new AssertionError();
}
private void onResendClicked(View v) {
MessageSender.resend(MessageDetailsActivity.this, messageRecord);
resendButton.setVisibility(View.GONE);
}
}
}

View File

@@ -81,11 +81,13 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.
private final Recipient recipient;
private final Status deliveryStatus;
private final boolean isUnidentified;
private final long timestamp;
RecipientDeliveryStatus(Recipient recipient, Status deliveryStatus, long timestamp) {
RecipientDeliveryStatus(Recipient recipient, Status deliveryStatus, boolean isUnidentified, long timestamp) {
this.recipient = recipient;
this.deliveryStatus = deliveryStatus;
this.isUnidentified = isUnidentified;
this.timestamp = timestamp;
}
@@ -93,6 +95,10 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.
return deliveryStatus;
}
boolean isUnidentified() {
return isUnidentified;
}
public long getTimestamp() {
return timestamp;
}

View File

@@ -16,13 +16,12 @@
*/
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -30,15 +29,13 @@ import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDelive
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.DeliveryStatusView;
import org.thoughtcrime.securesms.components.FromTextView;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
/**
@@ -58,8 +55,8 @@ public class MessageRecipientListItem extends RelativeLayout
private TextView errorDescription;
private TextView actionDescription;
private Button conflictButton;
private Button resendButton;
private AvatarImageView contactPhotoImage;
private ImageView unidentifiedDeliveryIcon;
private DeliveryStatusView deliveryStatusView;
public MessageRecipientListItem(Context context) {
@@ -73,13 +70,13 @@ public class MessageRecipientListItem extends RelativeLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
this.fromView = findViewById(R.id.from);
this.errorDescription = findViewById(R.id.error_description);
this.actionDescription = findViewById(R.id.action_description);
this.contactPhotoImage = findViewById(R.id.contact_photo_image);
this.conflictButton = findViewById(R.id.conflict_button);
this.resendButton = findViewById(R.id.resend_button);
this.deliveryStatusView = findViewById(R.id.delivery_status);
this.fromView = findViewById(R.id.from);
this.errorDescription = findViewById(R.id.error_description);
this.actionDescription = findViewById(R.id.action_description);
this.contactPhotoImage = findViewById(R.id.contact_photo_image);
this.conflictButton = findViewById(R.id.conflict_button);
this.unidentifiedDeliveryIcon = findViewById(R.id.ud_indicator);
this.deliveryStatusView = findViewById(R.id.delivery_status);
}
public void set(final GlideRequests glideRequests,
@@ -94,6 +91,7 @@ public class MessageRecipientListItem extends RelativeLayout
fromView.setText(member.getRecipient());
contactPhotoImage.setAvatar(glideRequests, member.getRecipient(), false);
setIssueIndicators(record, isPushGroup);
unidentifiedDeliveryIcon.setVisibility(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext()) && member.isUnidentified() ? VISIBLE : GONE);
}
private void setIssueIndicators(final MessageRecord record,
@@ -105,25 +103,13 @@ public class MessageRecipientListItem extends RelativeLayout
String errorText = "";
if (keyMismatch != null) {
resendButton.setVisibility(View.GONE);
conflictButton.setVisibility(View.VISIBLE);
errorText = getContext().getString(R.string.MessageDetailsRecipient_new_safety_number);
conflictButton.setOnClickListener(v -> new ConfirmIdentityDialog(getContext(), record, keyMismatch).show());
} else if (networkFailure != null || (!isPushGroup && record.isFailed())) {
resendButton.setVisibility(View.VISIBLE);
resendButton.setEnabled(true);
resendButton.requestFocus();
} else if ((networkFailure != null && !record.isPending()) || (!isPushGroup && record.isFailed())) {
conflictButton.setVisibility(View.GONE);
errorText = getContext().getString(R.string.MessageDetailsRecipient_failed_to_send);
resendButton.setOnClickListener(v -> {
resendButton.setVisibility(View.GONE);
errorDescription.setVisibility(View.GONE);
actionDescription.setVisibility(View.VISIBLE);
actionDescription.setText(R.string.message_recipients_list_item__resending);
new ResendAsyncTask(record, networkFailure).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
});
} else {
if (record.isOutgoing()) {
if (member.getDeliveryStatus() == RecipientDeliveryStatus.Status.PENDING || member.getDeliveryStatus() == RecipientDeliveryStatus.Status.UNKNOWN) {
@@ -142,7 +128,6 @@ public class MessageRecipientListItem extends RelativeLayout
deliveryStatusView.setVisibility(View.GONE);
}
resendButton.setVisibility(View.GONE);
conflictButton.setVisibility(View.GONE);
}
@@ -183,31 +168,4 @@ public class MessageRecipientListItem extends RelativeLayout
contactPhotoImage.setAvatar(glideRequests, recipient, false);
});
}
@SuppressLint("StaticFieldLeak")
private class ResendAsyncTask extends AsyncTask<Void,Void,Void> {
private final Context context;
private final MessageRecord record;
private final NetworkFailure failure;
ResendAsyncTask(MessageRecord record, NetworkFailure failure) {
this.context = getContext().getApplicationContext();
this.record = record;
this.failure = failure;
}
@Override
protected Void doInBackground(Void... params) {
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
mmsDatabase.removeFailure(record.getId(), failure);
if (record.getRecipient().isPushGroupRecipient()) {
MessageSender.resendGroupMessage(context, record, failure.getAddress());
} else {
MessageSender.resend(context, record);
}
return null;
}
}
}

View File

@@ -7,9 +7,8 @@ import android.support.v7.widget.SwitchCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadReceiptUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -39,7 +38,9 @@ public class ReadReceiptsIntroFragment extends Fragment {
TextSecurePreferences.setReadReceiptsEnabled(getContext(), isChecked);
ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new MultiDeviceReadReceiptUpdateJob(getContext(), isChecked));
.add(new MultiDeviceConfigurationUpdateJob(getContext(),
isChecked,
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext())));
});
return v;

View File

@@ -30,6 +30,7 @@ import android.telephony.PhoneNumberUtils;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair;
import android.view.MenuItem;
@@ -740,6 +741,12 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
}
}
if (blocked && (recipient.resolve().isSystemContact() || recipient.resolve().isProfileSharing())) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RotateProfileKeyJob(context));
}
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceBlockedUpdateJob(context));

View File

@@ -31,4 +31,8 @@ public class ProfileKeyUtil {
}
}
public static synchronized @NonNull byte[] rotateProfileKey(@NonNull Context context) {
TextSecurePreferences.setProfileKey(context, null);
return getProfileKey(context);
}
}

View File

@@ -10,6 +10,7 @@ import android.util.Log;
import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -40,6 +41,11 @@ public class UnidentifiedAccessUtil {
public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context,
@NonNull Recipient recipient)
{
if (!TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
Log.i(TAG, "Unidentified delivery is disabled. [other]");
return Optional.absent();
}
try {
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
@@ -49,9 +55,9 @@ public class UnidentifiedAccessUtil {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
Log.w(TAG, "Their access key: " + (theirUnidentifiedAccessKey == null));
Log.w(TAG, "Our access key: " + (ourUnidentifiedAccessKey == null));
Log.w(TAG, "Our certificatE: " + (ourUnidentifiedAccessCertificate == null));
Log.i(TAG, "Their access key present? " + (theirUnidentifiedAccessKey != null));
Log.i(TAG, "Our access key present? " + (ourUnidentifiedAccessKey != null));
Log.i(TAG, "Our certificate present? " + (ourUnidentifiedAccessCertificate != null));
if (theirUnidentifiedAccessKey != null &&
ourUnidentifiedAccessKey != null &&
@@ -71,6 +77,11 @@ public class UnidentifiedAccessUtil {
}
public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
if (!TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
Log.i(TAG, "Unidentified delivery is disabled. [self]");
return Optional.absent();
}
try {
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = TextSecurePreferences.getUnidentifiedAccessCertificate(context);

View File

@@ -34,6 +34,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
public abstract void markExpireStarted(long messageId, long startTime);
public abstract void markAsSent(long messageId, boolean secure);
public abstract void markUnidentified(long messageId, boolean unidentified);
public void setMismatchedIdentity(long messageId, final Address address, final IdentityKey identityKey) {
List<IdentityKeyMismatch> items = new ArrayList<IdentityKeyMismatch>() {{

View File

@@ -343,11 +343,11 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
}
// public void markAsSending(long messageId) {
// long threadId = getThreadIdForMessage(messageId);
// updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE, Optional.of(threadId));
// notifyConversationListeners(threadId);
// }
public void markAsSending(long messageId) {
long threadId = getThreadIdForMessage(messageId);
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE, Optional.of(threadId));
notifyConversationListeners(threadId);
}
public void markAsSentFailed(long messageId) {
long threadId = getThreadIdForMessage(messageId);
@@ -403,6 +403,7 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
}
@Override
public void markUnidentified(long messageId, boolean unidentified) {
ContentValues contentValues = new ContentValues();
contentValues.put(UNIDENTIFIED, unidentified ? 1 : 0);

View File

@@ -270,8 +270,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.QUOTE_BODY,
MmsDatabase.QUOTE_MISSING,
MmsDatabase.QUOTE_ATTACHMENT,
MmsDatabase.SHARED_CONTACTS,
MmsDatabase.UNIDENTIFIED};
MmsDatabase.SHARED_CONTACTS};
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();

View File

@@ -239,10 +239,15 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENT_TYPE | (isSecure ? Types.PUSH_MESSAGE_BIT | Types.SECURE_MESSAGE_BIT : 0));
}
public void markAsSending(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE);
}
public void markAsMissedCall(long id) {
updateTypeBitmask(id, Types.TOTAL_MASK, Types.MISSED_CALL_TYPE);
}
@Override
public void markUnidentified(long id, boolean unidentified) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(UNIDENTIFIED, unidentified ? 1 : 0);
@@ -695,7 +700,9 @@ public class SmsDatabase extends MessagingDatabase {
public Cursor getMessageCursor(long messageId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
return db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null);
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null);
setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId));
return cursor;
}
public boolean deleteMessage(long messageId) {

View File

@@ -289,6 +289,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN unidentified_access_mode INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE push ADD COLUMN server_timestamp INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE push ADD COLUMN server_guid TEXT DEFAULT NULL");
db.execSQL("ALTER TABLE group_receipts ADD COLUMN unidentified INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE mms ADD COLUMN unidentified INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE sms ADD COLUMN unidentified INTEGER DEFAULT 0");
}
db.setTransactionSuccessful();

View File

@@ -3,6 +3,9 @@ package org.thoughtcrime.securesms.dependencies;
import android.content.Context;
import org.thoughtcrime.securesms.gcm.GcmBroadcastReceiver;
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
import org.thoughtcrime.securesms.logging.Log;
import org.greenrobot.eventbus.EventBus;
@@ -87,7 +90,10 @@ import dagger.Provides;
AppProtectionPreferenceFragment.class,
GcmBroadcastReceiver.class,
RotateCertificateJob.class,
SendDeliveryReceiptJob.class})
SendDeliveryReceiptJob.class,
RotateProfileKeyJob.class,
MultiDeviceConfigurationUpdateJob.class,
RefreshUnidentifiedDeliveryAbilityJob.class})
public class SignalCommunicationModule {
private static final String TAG = SignalCommunicationModule.class.getSimpleName();

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
@@ -13,6 +14,8 @@ import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
@@ -61,6 +64,11 @@ public class MultiDeviceBlockedUpdateJob extends MasterSecretJob implements Inje
public void onRun(MasterSecret masterSecret)
throws IOException, UntrustedIdentityException
{
if (!TextSecurePreferences.isMultiDevice(context)) {
Log.i(TAG, "Not multi device, aborting...");
return;
}
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
try (RecipientReader reader = database.readerForBlocked(database.getBlocked())) {

View File

@@ -0,0 +1,87 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import javax.inject.Inject;
import androidx.work.Data;
public class MultiDeviceConfigurationUpdateJob extends ContextJob implements InjectableType {
private static final long serialVersionUID = 1L;
private static final String TAG = MultiDeviceConfigurationUpdateJob.class.getSimpleName();
private static final String KEY_READ_RECEIPTS_ENABLED = "read_receipts_enabled";
private static final String KEY_UNIDENTIFIED_DELIVERY_INDICATORS_ENABLED = "unidentified_delivery_indicators_enabled";
@Inject transient SignalServiceMessageSender messageSender;
private boolean readReceiptsEnabled;
private boolean unidentifiedDeliveryIndicatorsEnabled;
public MultiDeviceConfigurationUpdateJob() {
super(null, null);
}
public MultiDeviceConfigurationUpdateJob(Context context, boolean readReceiptsEnabled, boolean unidentifiedDeliveryIndicatorsEnabled) {
super(context, JobParameters.newBuilder()
.withGroupId("__MULTI_DEVICE_CONFIGURATION_UPDATE_JOB__")
.withNetworkRequirement()
.create());
this.readReceiptsEnabled = readReceiptsEnabled;
this.unidentifiedDeliveryIndicatorsEnabled = unidentifiedDeliveryIndicatorsEnabled;
}
@Override
protected void initialize(@NonNull SafeData data) {
readReceiptsEnabled = data.getBoolean(KEY_READ_RECEIPTS_ENABLED);
unidentifiedDeliveryIndicatorsEnabled = data.getBoolean(KEY_UNIDENTIFIED_DELIVERY_INDICATORS_ENABLED);
}
@Override
protected @NonNull Data serialize(@NonNull Data.Builder dataBuilder) {
return dataBuilder.putBoolean(KEY_READ_RECEIPTS_ENABLED, readReceiptsEnabled)
.putBoolean(KEY_UNIDENTIFIED_DELIVERY_INDICATORS_ENABLED, unidentifiedDeliveryIndicatorsEnabled)
.build();
}
@Override
public void onRun() throws IOException, UntrustedIdentityException {
if (!TextSecurePreferences.isMultiDevice(context)) {
Log.i(TAG, "Not multi device, aborting...");
return;
}
messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(readReceiptsEnabled), Optional.of(unidentifiedDeliveryIndicatorsEnabled))),
UnidentifiedAccessUtil.getAccessForSync(context));
}
@Override
public boolean onShouldRetry(Exception e) {
return e instanceof PushNetworkException;
}
@Override
public void onCanceled() {
Log.w(TAG, "**** Failed to synchronize read receipts state!");
}
}

View File

@@ -116,7 +116,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
throws IOException, UntrustedIdentityException, NetworkException
{
if (!TextSecurePreferences.isMultiDevice(context)) {
Log.w(TAG, "Not multi device, aborting...");
Log.i(TAG, "Not multi device, aborting...");
return;
}

View File

@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@@ -67,6 +68,11 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject
@Override
public void onRun(MasterSecret masterSecret) throws Exception {
if (!TextSecurePreferences.isMultiDevice(context)) {
Log.i(TAG, "Not multi device, aborting...");
return;
}
File contactDataFile = createTempFile("multidevice-contact-update");
GroupDatabase.Reader reader = null;

View File

@@ -62,7 +62,7 @@ public class MultiDeviceProfileKeyUpdateJob extends MasterSecretJob implements I
@Override
public void onRun(MasterSecret masterSecret) throws IOException, UntrustedIdentityException {
if (!TextSecurePreferences.isMultiDevice(getContext())) {
Log.w(TAG, "Not multi device...");
Log.i(TAG, "Not multi device...");
return;
}

View File

@@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@@ -22,6 +23,10 @@ import javax.inject.Inject;
import androidx.work.Data;
/**
* Use {@link MultiDeviceConfigurationUpdateJob}.
*/
@Deprecated
public class MultiDeviceReadReceiptUpdateJob extends ContextJob implements InjectableType {
private static final long serialVersionUID = 1L;
@@ -59,7 +64,12 @@ public class MultiDeviceReadReceiptUpdateJob extends ContextJob implements Injec
@Override
public void onRun() throws IOException, UntrustedIdentityException {
messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(enabled))),
if (!TextSecurePreferences.isMultiDevice(context)) {
Log.i(TAG, "Not multi device, aborting...");
return;
}
messageSender.sendMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(enabled), Optional.absent())),
UnidentifiedAccessUtil.getAccessForSync(context));
}

View File

@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
@@ -91,7 +92,7 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta
@Override
public void onRun(MasterSecret masterSecret) throws IOException, UntrustedIdentityException {
if (!TextSecurePreferences.isMultiDevice(context)) {
Log.w(TAG, "Not multi device...");
Log.i(TAG, "Not multi device...");
return;
}
@@ -101,8 +102,7 @@ public class MultiDeviceReadUpdateJob extends MasterSecretJob implements Injecta
readMessages.add(new ReadMessage(messageId.sender, messageId.timestamp));
}
messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages),
UnidentifiedAccessUtil.getAccessForSync(context));
messageSender.sendMessage(SignalServiceSyncMessage.forRead(readMessages), UnidentifiedAccessUtil.getAccessForSync(context));
}
@Override

View File

@@ -91,7 +91,7 @@ public class MultiDeviceVerifiedUpdateJob extends ContextJob implements Injectab
public void onRun() throws IOException, UntrustedIdentityException {
try {
if (!TextSecurePreferences.isMultiDevice(context)) {
Log.w(TAG, "Not multi device...");
Log.i(TAG, "Not multi device...");
return;
}

View File

@@ -20,6 +20,7 @@ import org.signal.libsignal.metadata.ProtocolInvalidVersionException;
import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
import org.signal.libsignal.metadata.ProtocolNoSessionException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.signal.libsignal.metadata.SelfSendException;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
@@ -36,6 +37,7 @@ import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
@@ -242,6 +244,8 @@ public class PushDecryptJob extends ContextJob {
handleNeedsDeliveryReceipt(content, message);
}
} else if (content.getSyncMessage().isPresent()) {
TextSecurePreferences.setMultiDevice(context, true);
SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get());
@@ -273,7 +277,11 @@ public class PushDecryptJob extends ContextJob {
} catch (ProtocolInvalidVersionException e) {
Log.w(TAG, e);
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (ProtocolInvalidMessageException | ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
} catch (ProtocolInvalidMessageException e) {
Log.w(TAG, e);
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
}
catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
Log.w(TAG, e);
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (StorageFailedException e) {
@@ -290,6 +298,8 @@ public class PushDecryptJob extends ContextJob {
handleDuplicateMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (InvalidMetadataVersionException | InvalidMetadataMessageException e) {
Log.w(TAG, e);
} catch (SelfSendException e) {
Log.i(TAG, "Dropping UD message from self.");
}
}
@@ -542,6 +552,10 @@ public class PushDecryptJob extends ContextJob {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceContactUpdateJob(getContext(), true));
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RefreshUnidentifiedDeliveryAbilityJob(context));
}
if (message.isGroupsRequest()) {
@@ -559,7 +573,9 @@ public class PushDecryptJob extends ContextJob {
if (message.isConfigurationRequest()) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceReadReceiptUpdateJob(getContext(), TextSecurePreferences.isReadReceiptsEnabled(getContext())));
.add(new MultiDeviceConfigurationUpdateJob(getContext(),
TextSecurePreferences.isReadReceiptsEnabled(getContext()),
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext())));
}
}
@@ -670,7 +686,17 @@ public class PushDecryptJob extends ContextJob {
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, null);
if (recipients.getAddress().isGroup()) {
GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipients.getAddress().toGroupString(), false);
for (Recipient member : members) {
receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize()));
}
}
database.markAsSent(messageId, true);
database.markUnidentified(messageId, message.isUnidentified(recipients.getAddress().serialize()));
for (DatabaseAttachment attachment : DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageId)) {
ApplicationContext.getInstance(context)
@@ -753,11 +779,19 @@ public class PushDecryptJob extends ContextJob {
messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null);
database = DatabaseFactory.getMmsDatabase(context);
GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.getAddress().toGroupString(), false);
for (Recipient member : members) {
receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize()));
}
} else {
OutgoingTextMessage outgoingTextMessage = new OutgoingEncryptedMessage(recipient, body, expiresInMillis);
messageId = DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoingTextMessage, false, message.getTimestamp(), null);
database = DatabaseFactory.getSmsDatabase(context);
database.markUnidentified(messageId, message.isUnidentified(recipient.getAddress().serialize()));
}
database.markAsSent(messageId, true);

View File

@@ -45,6 +45,7 @@ import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -99,6 +100,11 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
.build();
}
@Override
protected void onAdded() {
DatabaseFactory.getMmsDatabase(context).markAsSending(messageId);
}
@Override
public void onPushSend()
throws IOException, MmsException, NoSuchMessageException, RetryLaterException
@@ -110,6 +116,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
try {
Log.i(TAG, "Sending message: " + messageId);
List<Address> target;
if (filterAddress != null) target = Collections.singletonList(Address.fromSerialized(filterAddress));

View File

@@ -10,16 +10,19 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
@@ -68,6 +71,11 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
return dataBuilder.putLong(KEY_MESSAGE_ID, messageId).build();
}
@Override
protected void onAdded() {
DatabaseFactory.getMmsDatabase(context).markAsSending(messageId);
}
@Override
public void onPushSend()
throws RetryLaterException, MmsException, NoSuchMessageException,
@@ -85,6 +93,19 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
markAttachmentsUploaded(messageId, message.getAttachments());
database.markUnidentified(messageId, unidentified);
if (TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
Recipient recipient = message.getRecipient().resolve();
UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
if (unidentified && (accessMode == UnidentifiedAccessMode.UNKNOWN || accessMode == UnidentifiedAccessMode.DISABLED)) {
Log.i(TAG, "Marking recipient as UD-enabled following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
Log.i(TAG, "Marking recipient as UD-disabled following a non-UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
}
}
if (message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
database.markExpireStarted(messageId);
expirationManager.scheduleDeletion(messageId, true, message.getExpiresIn());

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
@@ -19,11 +20,11 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
@@ -68,6 +69,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
@Override
public void onAdded() {
Log.i(TAG, "onAdded() messageId: " + messageId);
DatabaseFactory.getSmsDatabase(context).markAsSending(messageId);
}
@Override
@@ -83,6 +85,19 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
database.markAsSent(messageId, true);
database.markUnidentified(messageId, unidentified);
if (TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
Recipient recipient = record.getRecipient().resolve();
UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
if (unidentified && (accessMode == UnidentifiedAccessMode.UNKNOWN || accessMode == UnidentifiedAccessMode.DISABLED)) {
Log.i(TAG, "Marking recipient as UD-enabled following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
Log.i(TAG, "Marking recipient as UD-disabled following a non-UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
}
}
if (record.getExpiresIn() > 0) {
database.markExpireStarted(messageId);
expirationManager.scheduleDeletion(record.getId(), record.isMms(), record.getExpiresIn());

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
@@ -10,10 +11,6 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
@@ -63,6 +60,10 @@ public class RefreshAttributesJob extends ContextJob implements InjectableType {
signalAccountManager.setAccountAttributes(signalingKey, registrationId, fetchesMessages, pin,
unidentifiedAccessKey, universalUnidentifiedAccess);
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RefreshUnidentifiedDeliveryAbilityJob(context));
}
@Override

View File

@@ -0,0 +1,96 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import javax.inject.Inject;
import androidx.work.Data;
public class RefreshUnidentifiedDeliveryAbilityJob extends ContextJob implements InjectableType {
private static final String TAG = RefreshUnidentifiedDeliveryAbilityJob.class.getSimpleName();
@Inject transient SignalServiceMessageReceiver receiver;
public RefreshUnidentifiedDeliveryAbilityJob() {
super(null, null);
}
public RefreshUnidentifiedDeliveryAbilityJob(Context context) {
super(context, new JobParameters.Builder()
.withNetworkRequirement()
.create());
}
@Override
protected void initialize(@NonNull SafeData data) { }
@Override
protected @NonNull Data serialize(@NonNull Data.Builder dataBuilder) {
return dataBuilder.build();
}
@Override
public void onRun() throws Exception {
byte[] profileKey = ProfileKeyUtil.getProfileKey(context);
SignalServiceProfile profile = retrieveProfile(TextSecurePreferences.getLocalNumber(context));
boolean enabled = profile.getUnidentifiedAccess() != null && isValidVerifier(profileKey, profile.getUnidentifiedAccess());
TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, enabled);
Log.i(TAG, "Set UD status to: " + enabled);
}
@Override
protected void onCanceled() {
}
@Override
protected boolean onShouldRetry(Exception exception) {
return exception instanceof PushNetworkException;
}
private SignalServiceProfile retrieveProfile(@NonNull String number) throws IOException {
SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe();
if (pipe != null) {
try {
return pipe.getProfile(new SignalServiceAddress(number), Optional.absent());
} catch (IOException e) {
Log.w(TAG, e);
}
}
return receiver.retrieveProfile(new SignalServiceAddress(number), Optional.absent());
}
private boolean isValidVerifier(@NonNull byte[] profileKey, @NonNull String verifier) {
ProfileCipher profileCipher = new ProfileCipher(profileKey);
try {
return profileCipher.verifyUnidentifiedAccess(Base64.decode(verifier));
} catch (IOException e) {
Log.w(TAG, e);
return false;
}
}
}

View File

@@ -30,7 +30,7 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException;
@@ -101,9 +101,8 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
try {
profile = retrieveProfile(number, unidentifiedAccess);
} catch (AuthorizationFailedException e) {
} catch (NonSuccessfulResponseCodeException e) {
if (unidentifiedAccess.isPresent()) {
// XXX Update UI
profile = retrieveProfile(number, Optional.absent());
} else {
throw e;
@@ -129,7 +128,10 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
private SignalServiceProfile retrieveProfile(@NonNull String number, Optional<UnidentifiedAccess> unidentifiedAccess)
throws IOException
{
SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe();
SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe
: authPipe;
if (pipe != null) {
try {
@@ -169,10 +171,11 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
byte[] profileKey = recipient.getProfileKey();
// XXX Update UI
if (unrestrictedUnidentifiedAccess) {
Log.i(TAG, "Marking recipient UD status as unrestricted.");
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
Log.i(TAG, "Marking recipient UD status as disabled.");
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
} else {
ProfileCipher profileCipher = new ProfileCipher(profileKey);
@@ -185,7 +188,9 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
verifiedUnidentifiedAccess = false;
}
recipientDatabase.setUnidentifiedAccessMode(recipient, verifiedUnidentifiedAccess ? UnidentifiedAccessMode.ENABLED : UnidentifiedAccessMode.DISABLED);
UnidentifiedAccessMode mode = verifiedUnidentifiedAccess ? UnidentifiedAccessMode.ENABLED : UnidentifiedAccessMode.DISABLED;
Log.i(TAG, "Marking recipient UD status as " + mode.name() + " after verification.");
recipientDatabase.setUnidentifiedAccessMode(recipient, mode);
}
}

View File

@@ -0,0 +1,88 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.inject.Inject;
import androidx.work.Data;
public class RotateProfileKeyJob extends ContextJob implements InjectableType {
@Inject SignalServiceAccountManager accountManager;
public RotateProfileKeyJob() {
super(null, null);
}
public RotateProfileKeyJob(Context context) {
super(context, new JobParameters.Builder()
.withGroupId("__ROTATE_PROFILE_KEY__")
.withDuplicatesIgnored(true)
.withNetworkRequirement()
.create());
}
@NonNull
@Override
protected Data serialize(@NonNull Data.Builder dataBuilder) {
return dataBuilder.build();
}
@Override
protected void initialize(@NonNull SafeData data) {
}
@Override
public void onRun() throws Exception {
byte[] profileKey = ProfileKeyUtil.rotateProfileKey(context);
accountManager.setProfileName(profileKey, TextSecurePreferences.getProfileName(context));
accountManager.setProfileAvatar(profileKey, getProfileAvatar());
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RefreshAttributesJob(context));
}
@Override
protected void onCanceled() {
}
@Override
protected boolean onShouldRetry(Exception exception) {
return exception instanceof PushNetworkException;
}
private @Nullable StreamDetails getProfileAvatar() {
try {
Address localAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(context));
File avatarFile = AvatarHelper.getAvatarFile(context, localAddress);
if (avatarFile.exists()) {
return new StreamDetails(new FileInputStream(avatarFile), "image/jpeg", avatarFile.length());
}
} catch (IOException e) {
return null;
}
return null;
}
}

View File

@@ -10,6 +10,7 @@ import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.Preference;
import android.view.View;
import android.widget.Toast;
import org.thoughtcrime.securesms.ApplicationContext;
@@ -20,9 +21,12 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadReceiptUpdateJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
@@ -34,7 +38,8 @@ import mobi.upod.timedurationpicker.TimeDurationPickerDialog;
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment implements InjectableType {
private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
private static final String PREFERENCE_UNIDENTIFIED_LEARN_MORE = "pref_unidentified_learn_more";
private CheckBoxPreference disablePassphrase;
@@ -61,6 +66,9 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setOnPreferenceClickListener(new PassphraseIntervalClickListener());
this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
this.findPreference(PREFERENCE_CATEGORY_BLOCKED).setOnPreferenceClickListener(new BlockedContactsClickListener());
this.findPreference(TextSecurePreferences.SHOW_UNIDENTIFIED_DELIVERY_INDICATORS).setOnPreferenceChangeListener(new ShowUnidentifiedDeliveryIndicatorsChangedListener());
this.findPreference(TextSecurePreferences.UNIVERSAL_UNIDENTIFIED_ACCESS).setOnPreferenceChangeListener(new UniversalUnidentifiedAccessChangedListener());
this.findPreference(PREFERENCE_UNIDENTIFIED_LEARN_MORE).setOnPreferenceClickListener(new UnidentifiedLearnMoreClickListener());
disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
initializeVisibility();
@@ -177,7 +185,9 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
boolean enabled = (boolean)newValue;
ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new MultiDeviceReadReceiptUpdateJob(getContext(), enabled));
.add(new MultiDeviceConfigurationUpdateJob(getContext(),
enabled,
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext())));
return true;
}
@@ -271,4 +281,35 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
}
}
private class ShowUnidentifiedDeliveryIndicatorsChangedListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean enabled = (boolean) newValue;
ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new MultiDeviceConfigurationUpdateJob(getContext(),
TextSecurePreferences.isReadReceiptsEnabled(getContext()),
enabled));
return true;
}
}
private class UniversalUnidentifiedAccessChangedListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new RefreshAttributesJob(getContext()));
return true;
}
}
private class UnidentifiedLearnMoreClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
CommunicationActions.openBrowserLink(preference.getContext(), "https://signal.org/blog/secret-sender/");
return true;
}
}
}

View File

@@ -24,12 +24,4 @@ public class SecurityEventListener implements SignalServiceMessageSender.EventLi
public void onSecurityEvent(SignalServiceAddress textSecureAddress) {
SecurityEvent.broadcastSecurityUpdateEvent(context);
}
@Override
public void onUnidentifiedAccessFailed(SignalServiceAddress address) {
// XXX Update UI
DatabaseFactory.getRecipientDatabase(context)
.setUnidentifiedAccessMode(Recipient.from(context, Address.fromSerialized(address.getNumber()), false),
RecipientDatabase.UnidentifiedAccessMode.DISABLED);
}
}

View File

@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;

View File

@@ -41,7 +41,7 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe
private static final long REQUEST_TIMEOUT_MINUTES = 1;
private static SignalServiceMessagePipe pipe = null;
public static SignalServiceMessagePipe unidentifiedPipe = null;
private static SignalServiceMessagePipe unidentifiedPipe = null;
private final Context context;
private final NetworkRequirement networkRequirement;
@@ -158,7 +158,7 @@ public class IncomingMessageObserver implements InjectableType, RequirementListe
Log.i(TAG, "Reading message...");
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
envelope -> {
Log.i(TAG, "Retrieved envelope! " + envelope.getSource());
Log.i(TAG, "Retrieved envelope! " + String.valueOf(envelope.getSource()));
new PushContentReceiveJob(context).processEnvelope(envelope);
});
} catch (TimeoutException e) {

View File

@@ -1,254 +0,0 @@
package org.thoughtcrime.securesms.service;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.gcm.GcmBroadcastReceiver;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirementProvider;
import org.thoughtcrime.securesms.jobmanager.requirements.RequirementListener;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
public class MessageRetrievalService extends Service implements InjectableType, RequirementListener {
private static final String TAG = MessageRetrievalService.class.getSimpleName();
public static final String ACTION_ACTIVITY_STARTED = "ACTIVITY_STARTED";
public static final String ACTION_ACTIVITY_FINISHED = "ACTIVITY_FINISHED";
public static final String ACTION_PUSH_RECEIVED = "PUSH_RECEIVED";
public static final String ACTION_INITIALIZE = "INITIALIZE";
public static final int FOREGROUND_ID = 313399;
private static final long REQUEST_TIMEOUT_MINUTES = 1;
private NetworkRequirement networkRequirement;
private NetworkRequirementProvider networkRequirementProvider;
@Inject
public SignalServiceMessageReceiver receiver;
private int activeActivities = 0;
private List<Intent> pushPending = new LinkedList<>();
private MessageRetrievalThread retrievalThread = null;
public static SignalServiceMessagePipe pipe = null;
public static SignalServiceMessagePipe unidentifiedPipe = null;
@Override
public void onCreate() {
super.onCreate();
ApplicationContext.getInstance(this).injectDependencies(this);
networkRequirement = new NetworkRequirement(this);
networkRequirementProvider = new NetworkRequirementProvider(this);
networkRequirementProvider.setListener(this);
retrievalThread = new MessageRetrievalThread();
retrievalThread.start();
setForegroundIfNecessary();
}
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) return START_STICKY;
if (ACTION_ACTIVITY_STARTED.equals(intent.getAction())) incrementActive();
else if (ACTION_ACTIVITY_FINISHED.equals(intent.getAction())) decrementActive();
else if (ACTION_PUSH_RECEIVED.equals(intent.getAction())) incrementPushReceived(intent);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (retrievalThread != null) {
retrievalThread.stopThread();
}
sendBroadcast(new Intent("org.thoughtcrime.securesms.RESTART"));
}
@Override
public void onRequirementStatusChanged() {
synchronized (this) {
notifyAll();
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void setForegroundIfNecessary() {
if (TextSecurePreferences.isGcmDisabled(this)) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationChannels.OTHER);
builder.setContentTitle(getString(R.string.MessageRetrievalService_signal));
builder.setContentText(getString(R.string.MessageRetrievalService_background_connection_enabled));
builder.setPriority(NotificationCompat.PRIORITY_MIN);
builder.setWhen(0);
builder.setSmallIcon(R.drawable.ic_signal_grey_24dp);
startForeground(FOREGROUND_ID, builder.build());
}
}
private synchronized void incrementActive() {
activeActivities++;
Log.d(TAG, "Active Count: " + activeActivities);
notifyAll();
}
private synchronized void decrementActive() {
activeActivities--;
Log.d(TAG, "Active Count: " + activeActivities);
notifyAll();
}
private synchronized void incrementPushReceived(Intent intent) {
pushPending.add(intent);
notifyAll();
}
private synchronized void decrementPushReceived() {
if (!pushPending.isEmpty()) {
Intent intent = pushPending.remove(0);
GcmBroadcastReceiver.completeWakefulIntent(intent);
notifyAll();
}
}
private synchronized boolean isConnectionNecessary() {
boolean isGcmDisabled = TextSecurePreferences.isGcmDisabled(this);
Log.d(TAG, String.format("Network requirement: %s, active activities: %s, push pending: %s, gcm disabled: %b",
networkRequirement.isPresent(), activeActivities, pushPending.size(), isGcmDisabled));
return TextSecurePreferences.isPushRegistered(this) &&
TextSecurePreferences.isWebsocketRegistered(this) &&
(activeActivities > 0 || !pushPending.isEmpty() || isGcmDisabled) &&
networkRequirement.isPresent();
}
private synchronized void waitForConnectionNecessary() {
try {
while (!isConnectionNecessary()) wait();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
private void shutdown(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
try {
pipe.shutdown();
unidentifiedPipe.shutdown();
} catch (Throwable t) {
Log.w(TAG, t);
}
}
public static void registerActivityStarted(Context activity) {
Intent intent = new Intent(activity, MessageRetrievalService.class);
intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_STARTED);
activity.startService(intent);
}
public static void registerActivityStopped(Context activity) {
Intent intent = new Intent(activity, MessageRetrievalService.class);
intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_FINISHED);
activity.startService(intent);
}
public static @Nullable SignalServiceMessagePipe getPipe() {
return pipe;
}
public static @Nullable SignalServiceMessagePipe getUnidentifiedPipe() {
return unidentifiedPipe;
}
private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
private AtomicBoolean stopThread = new AtomicBoolean(false);
MessageRetrievalThread() {
super("MessageRetrievalService");
setUncaughtExceptionHandler(this);
}
@Override
public void run() {
while (!stopThread.get()) {
Log.i(TAG, "Waiting for websocket state change....");
waitForConnectionNecessary();
Log.i(TAG, "Making websocket connection....");
pipe = receiver.createMessagePipe();
unidentifiedPipe = receiver.createUnidentifiedMessagePipe();
SignalServiceMessagePipe localPipe = pipe;
SignalServiceMessagePipe unidentifiedLocalPipe = unidentifiedPipe;
try {
while (isConnectionNecessary() && !stopThread.get()) {
try {
Log.i(TAG, "Reading message...");
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
envelope -> {
Log.i(TAG, "Retrieved envelope! " + envelope.getSource());
new PushContentReceiveJob(getApplicationContext()).processEnvelope(envelope);
decrementPushReceived();
});
} catch (TimeoutException e) {
Log.w(TAG, "Application level read timeout...");
} catch (InvalidVersionException e) {
Log.w(TAG, e);
}
}
} catch (Throwable e) {
Log.w(TAG, e);
} finally {
Log.w(TAG, "Shutting down pipe...");
shutdown(localPipe, unidentifiedLocalPipe);
}
Log.i(TAG, "Looping...");
}
Log.i(TAG, "Exiting...");
}
private void stopThread() {
stopThread.set(true);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Log.w(TAG, "*** Uncaught exception!");
Log.w(TAG, e);
}
}
}

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util;
import android.Manifest;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -10,6 +11,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
import android.widget.Toast;
import org.thoughtcrime.securesms.ConversationActivity;
import org.thoughtcrime.securesms.R;
@@ -87,4 +89,13 @@ public class CommunicationActions {
}
context.startActivity(intent);
}
public static void openBrowserLink(@NonNull Context context, @NonNull String link) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(context, R.string.CommunicationActions_no_browser_found, Toast.LENGTH_SHORT).show();
}
}
}

View File

@@ -164,10 +164,12 @@ public class TextSecurePreferences {
private static final String NOTIFICATION_MESSAGES_CHANNEL_VERSION = "pref_notification_messages_channel_version";
private static final String NEEDS_MESSAGE_PULL = "pref_needs_message_pull";
private static final String UNIDENTIFIED_ACCESS_CERTIFICATE_ROTATION_TIME_PREF = "pref_unidentified_access_certificate_rotation_time";
private static final String UNIDENTIFIED_ACCESS_CERTIFICATE = "pref_unidentified_access_certificate";
private static final String UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access";
public static final String UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access";
public static final String SHOW_UNIDENTIFIED_DELIVERY_INDICATORS = "pref_show_unidentifed_delivery_indicators";
private static final String UNIDENTIFIED_DELIVERY_ENABLED = "pref_unidentified_delivery_enabled";
public static boolean isScreenLockEnabled(@NonNull Context context) {
return getBooleanPreference(context, SCREEN_LOCK, false);
@@ -540,8 +542,16 @@ public class TextSecurePreferences {
return getBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, false);
}
public static void setUniversalUnidentifiedAccess(Context context, boolean value) {
setBooleanPreference(context, UNIVERSAL_UNIDENTIFIED_ACCESS, value);
public static boolean isShowUnidentifiedDeliveryIndicatorsEnabled(Context context) {
return getBooleanPreference(context, SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, false);
}
public static void setIsUnidentifiedDeliveryEnabled(Context context, boolean enabled) {
setBooleanPreference(context, UNIDENTIFIED_DELIVERY_ENABLED, enabled);
}
public static boolean isUnidentifiedDeliveryEnabled(Context context) {
return getBooleanPreference(context, UNIDENTIFIED_DELIVERY_ENABLED, true);
}
public static long getSignedPreKeyRotationTime(Context context) {