mirror of
https://github.com/oxen-io/session-android.git
synced 2025-03-13 21:30:56 +00:00
Migrate from SQLite and ciphertext blobs to SQLCipher + KeyStore
This commit is contained in:
parent
d1819b6361
commit
f36b296e2e
@ -117,6 +117,7 @@ dependencies {
|
||||
}
|
||||
compile 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
|
||||
compile 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
|
||||
compile 'net.zetetic:android-database-sqlcipher:3.5.9'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.assertj:assertj-core:1.7.1'
|
||||
@ -183,6 +184,7 @@ dependencyVerification {
|
||||
'com.annimon:stream:5da6e2e3e0551d61a3ea7014f04312276549e3dd739cf637996e4cf43c5535b9',
|
||||
'com.takisoft.fix:colorpicker:f5d0dbabe406a1800498ca9c1faf34db36e021d8488bf10360f29961fe3ab0d1',
|
||||
'com.github.dmytrodanylyk.circular-progress-button:library:8dc6a29a5a8db7b2ad5a9a7fda1dc9ae0893f4c8f0545732b2c63854ea693e8e',
|
||||
'net.zetetic:android-database-sqlcipher:eff93b3222f4bdc349ffee2d2e3b2a2507241f17435fb998947bcce486618f1d',
|
||||
'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70',
|
||||
'com.google.android.gms:play-services-base:0ca636a8fc9a5af45e607cdcd61783bf5d561cbbb0f862021ce69606eee5ad49',
|
||||
'com.google.android.gms:play-services-tasks:69ec265168e601d0203d04cd42e34bb019b2f029aa1e16fabd38a5153eea2086',
|
||||
|
@ -1,2 +1,5 @@
|
||||
-keep class org.sqlite.** { *; }
|
||||
-keep class org.sqlite.database.** { *; }
|
||||
-keep class org.sqlite.database.** { *; }
|
||||
|
||||
-keep class net.sqlcipher.** { *; }
|
||||
-dontwarn net.sqlcipher.**
|
@ -11,8 +11,7 @@ import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public interface BindableConversationItem extends Unbindable {
|
||||
void bind(@NonNull MasterSecret masterSecret,
|
||||
@NonNull MessageRecord messageRecord,
|
||||
void bind(@NonNull MessageRecord messageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
|
@ -11,7 +11,7 @@ import java.util.Set;
|
||||
|
||||
public interface BindableConversationListItem extends Unbindable {
|
||||
|
||||
public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread,
|
||||
public void bind(@NonNull ThreadRecord thread,
|
||||
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
||||
@NonNull Set<Long> selectedThreads, boolean batchMode);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.database.Cursor;
|
||||
@ -10,7 +11,6 @@ import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@ -35,12 +35,12 @@ import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
public class ConfirmIdentityDialog extends AlertDialog {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = ConfirmIdentityDialog.class.getSimpleName();
|
||||
|
||||
private OnClickListener callback;
|
||||
|
||||
public ConfirmIdentityDialog(Context context,
|
||||
MasterSecret masterSecret,
|
||||
MessageRecord messageRecord,
|
||||
IdentityKeyMismatch mismatch)
|
||||
{
|
||||
@ -59,7 +59,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||
setTitle(name);
|
||||
setMessage(spannableString);
|
||||
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(masterSecret, messageRecord, mismatch, recipient.getAddress()));
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(messageRecord, mismatch, recipient.getAddress()));
|
||||
setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), new CancelListener());
|
||||
}
|
||||
|
||||
@ -76,18 +76,17 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||
|
||||
private class AcceptListener implements OnClickListener {
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
private final MessageRecord messageRecord;
|
||||
private final IdentityKeyMismatch mismatch;
|
||||
private final Address address;
|
||||
|
||||
private AcceptListener(MasterSecret masterSecret, MessageRecord messageRecord, IdentityKeyMismatch mismatch, Address address) {
|
||||
this.masterSecret = masterSecret;
|
||||
private AcceptListener(MessageRecord messageRecord, IdentityKeyMismatch mismatch, Address address) {
|
||||
this.messageRecord = messageRecord;
|
||||
this.mismatch = mismatch;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new AsyncTask<Void, Void, Void>()
|
||||
@ -115,7 +114,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||
private void processPendingMessageRecords(long threadId, IdentityKeyMismatch mismatch) {
|
||||
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(getContext());
|
||||
Cursor cursor = mmsSmsDatabase.getIdentityConflictMessagesForThread(threadId);
|
||||
MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(cursor, masterSecret);
|
||||
MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(cursor);
|
||||
MessageRecord record;
|
||||
|
||||
try {
|
||||
@ -144,14 +143,14 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||
if (messageRecord.getRecipient().isPushGroupRecipient()) {
|
||||
MessageSender.resendGroupMessage(getContext(), messageRecord, mismatch.getAddress());
|
||||
} else {
|
||||
MessageSender.resend(getContext(), masterSecret, messageRecord);
|
||||
MessageSender.resend(getContext(), messageRecord);
|
||||
}
|
||||
} else {
|
||||
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
|
||||
mismatch.getAddress(),
|
||||
mismatch.getIdentityKey());
|
||||
|
||||
MessageSender.resend(getContext(), masterSecret, messageRecord);
|
||||
MessageSender.resend(getContext(), messageRecord);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2015 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -24,7 +24,6 @@ import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
|
||||
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
@ -53,8 +52,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
||||
|
||||
protected ContactSelectionListFragment contactsFragment;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private ContactFilterToolbar toolbar;
|
||||
private ContactFilterToolbar toolbar;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
@ -64,7 +62,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
|
||||
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE,
|
||||
TextSecurePreferences.isSmsEnabled(this)
|
||||
@ -94,6 +91,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
||||
this.toolbar = ViewUtil.findById(this, R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
getSupportActionBar().setIcon(null);
|
||||
@ -107,11 +105,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
||||
}
|
||||
|
||||
private void initializeSearch() {
|
||||
toolbar.setOnFilterChangedListener(new OnFilterChangedListener() {
|
||||
@Override public void onFilterChanged(String filter) {
|
||||
contactsFragment.setQueryFilter(filter);
|
||||
}
|
||||
});
|
||||
toolbar.setOnFilterChangedListener(filter -> contactsFragment.setQueryFilter(filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -128,19 +122,16 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
|
||||
private static class RefreshDirectoryTask extends AsyncTask<Context, Void, Void> {
|
||||
|
||||
private final WeakReference<ContactSelectionActivity> activity;
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
private RefreshDirectoryTask(ContactSelectionActivity activity) {
|
||||
this.activity = new WeakReference<>(activity);
|
||||
this.masterSecret = activity.masterSecret;
|
||||
this.activity = new WeakReference<>(activity);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Context... params) {
|
||||
|
||||
try {
|
||||
DirectoryHelper.refreshDirectory(params[0], masterSecret, true);
|
||||
DirectoryHelper.refreshDirectory(params[0], true);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
@ -221,7 +220,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new ContactsCursorLoader(getActivity(), KeyCachingService.getMasterSecret(getContext()),
|
||||
return new ContactsCursorLoader(getActivity(),
|
||||
getActivity().getIntent().getIntExtra(DISPLAY_MODE, DISPLAY_MODE_ALL),
|
||||
cursorFilter, getActivity().getIntent().getBooleanExtra(RECENTS, false));
|
||||
}
|
||||
@ -263,7 +262,7 @@ public class ContactSelectionListFragment extends Fragment
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
try {
|
||||
DirectoryHelper.refreshDirectory(getContext(), null, false);
|
||||
DirectoryHelper.refreshDirectory(getContext(), false);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
|
@ -97,7 +97,6 @@ import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
@ -214,7 +213,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private static final int PICK_GIF = 9;
|
||||
private static final int SMS_DEFAULT = 10;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private GlideRequests glideRequests;
|
||||
protected ComposeText composeText;
|
||||
private AnimatingToggle buttonToggle;
|
||||
@ -263,7 +261,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) {
|
||||
Log.w(TAG, "onCreate()");
|
||||
this.masterSecret = masterSecret;
|
||||
|
||||
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
|
||||
setContentView(R.layout.conversation_activity);
|
||||
@ -440,7 +437,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
break;
|
||||
case PICK_LOCATION:
|
||||
SignalPlace place = new SignalPlace(PlacePicker.getPlace(data, this));
|
||||
attachmentManager.setLocation(masterSecret, place, getCurrentMediaConstraints());
|
||||
attachmentManager.setLocation(place, getCurrentMediaConstraints());
|
||||
break;
|
||||
case PICK_GIF:
|
||||
setMedia(data.getData(), MediaType.GIF);
|
||||
@ -595,7 +592,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime);
|
||||
OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(getRecipient(), System.currentTimeMillis(), expirationTime * 1000);
|
||||
MessageSender.send(ConversationActivity.this, masterSecret, outgoingMessage, threadId, false, null);
|
||||
MessageSender.send(ConversationActivity.this, outgoingMessage, threadId, false, null);
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -681,7 +678,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private void handleRegisterForSignal() {
|
||||
Intent intent = new Intent(this, RegistrationActivity.class);
|
||||
intent.putExtra("cancel_button", true);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@ -723,7 +719,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
new AsyncTask<OutgoingEndSessionMessage, Void, Long>() {
|
||||
@Override
|
||||
protected Long doInBackground(OutgoingEndSessionMessage... messages) {
|
||||
return MessageSender.send(context, masterSecret, messages[0], threadId, false, null);
|
||||
return MessageSender.send(context, messages[0], threadId, false, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -768,7 +764,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
.build();
|
||||
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipient(), context, null, System.currentTimeMillis(), 0);
|
||||
MessageSender.send(self, masterSecret, outgoingMessage, threadId, false, null);
|
||||
MessageSender.send(self, outgoingMessage, threadId, false, null);
|
||||
DatabaseFactory.getGroupDatabase(self).remove(groupId, Address.fromSerialized(TextSecurePreferences.getLocalNumber(self)));
|
||||
initializeEnabledCheck();
|
||||
} catch (IOException e) {
|
||||
@ -1006,9 +1002,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
new AsyncTask<Void, Void, List<Draft>>() {
|
||||
@Override
|
||||
protected List<Draft> doInBackground(Void... params) {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
DraftDatabase draftDatabase = DatabaseFactory.getDraftDatabase(ConversationActivity.this);
|
||||
List<Draft> results = draftDatabase.getDrafts(masterCipher, threadId);
|
||||
List<Draft> results = draftDatabase.getDrafts(threadId);
|
||||
|
||||
draftDatabase.clearDrafts(threadId);
|
||||
|
||||
@ -1024,7 +1019,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
composeText.setText(draft.getValue());
|
||||
break;
|
||||
case Draft.LOCATION:
|
||||
attachmentManager.setLocation(masterSecret, SignalPlace.deserialize(draft.getValue()), getCurrentMediaConstraints());
|
||||
attachmentManager.setLocation(SignalPlace.deserialize(draft.getValue()), getCurrentMediaConstraints());
|
||||
break;
|
||||
case Draft.IMAGE:
|
||||
setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE);
|
||||
@ -1078,7 +1073,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (registeredState == RegisteredState.UNKNOWN) {
|
||||
try {
|
||||
Log.w(TAG, "Refreshing directory for user: " + recipient.getAddress().serialize());
|
||||
registeredState = DirectoryHelper.refreshDirectoryFor(context, masterSecret, recipient);
|
||||
registeredState = DirectoryHelper.refreshDirectoryFor(context, recipient);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
@ -1246,7 +1241,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
attachmentTypeSelector = null;
|
||||
attachmentManager = new AttachmentManager(this, this);
|
||||
audioRecorder = new AudioRecorder(this, masterSecret);
|
||||
audioRecorder = new AudioRecorder(this);
|
||||
|
||||
SendButtonListener sendButtonListener = new SendButtonListener();
|
||||
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
|
||||
@ -1385,7 +1380,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private void setMedia(@Nullable Uri uri, @NonNull MediaType mediaType) {
|
||||
if (uri == null) return;
|
||||
attachmentManager.setMedia(masterSecret, glideRequests, uri, mediaType, getCurrentMediaConstraints());
|
||||
attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints());
|
||||
}
|
||||
|
||||
private void addAttachmentContactInfo(Uri contactUri) {
|
||||
@ -1440,7 +1435,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
final Drafts drafts = getDraftsForCurrentState();
|
||||
final long thisThreadId = this.threadId;
|
||||
final MasterSecret thisMasterSecret = this.masterSecret.parcelClone();
|
||||
final int thisDistributionType = this.distributionType;
|
||||
|
||||
new AsyncTask<Long, Void, Long>() {
|
||||
@ -1453,9 +1447,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (drafts.size() > 0) {
|
||||
if (threadId == -1) threadId = threadDatabase.getThreadIdFor(getRecipient(), thisDistributionType);
|
||||
|
||||
draftDatabase.insertDrafts(new MasterCipher(thisMasterSecret), threadId, drafts);
|
||||
draftDatabase.insertDrafts(threadId, drafts);
|
||||
threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this),
|
||||
drafts.getUriSnippet(ConversationActivity.this),
|
||||
drafts.getUriSnippet(),
|
||||
System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true);
|
||||
} else if (threadId > 0) {
|
||||
threadDatabase.update(threadId, false);
|
||||
@ -1588,7 +1582,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
Context context = ConversationActivity.this;
|
||||
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(params[0], false);
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret);
|
||||
MessageNotifier.updateNotification(context);
|
||||
MarkReadReceiver.process(context, messageIds);
|
||||
|
||||
return null;
|
||||
@ -1701,7 +1695,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
|
||||
}
|
||||
|
||||
return MessageSender.send(context, masterSecret, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
|
||||
return MessageSender.send(context, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1746,7 +1740,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
|
||||
}
|
||||
|
||||
return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
|
||||
return MessageSender.send(context, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1799,7 +1793,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public void onImageCapture(@NonNull final byte[] imageBytes) {
|
||||
setMedia(PersistentBlobProvider.getInstance(this)
|
||||
.create(masterSecret, imageBytes, MediaUtil.IMAGE_JPEG, null),
|
||||
.create(this, imageBytes, MediaUtil.IMAGE_JPEG, null),
|
||||
MediaType.IMAGE);
|
||||
quickAttachmentDrawer.hide(false);
|
||||
}
|
||||
@ -1862,7 +1856,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first);
|
||||
PersistentBlobProvider.getInstance(ConversationActivity.this).delete(ConversationActivity.this, result.first);
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
@ -1891,7 +1885,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first);
|
||||
PersistentBlobProvider.getInstance(ConversationActivity.this).delete(ConversationActivity.this, result.first);
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
@ -26,13 +26,10 @@ import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.FastCursorRecyclerViewAdapter;
|
||||
@ -92,7 +89,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
private final Set<MessageRecord> batchSelected = Collections.synchronizedSet(new HashSet<MessageRecord>());
|
||||
|
||||
private final @Nullable ItemClickListener clickListener;
|
||||
private final @NonNull MasterSecret masterSecret;
|
||||
private final @NonNull GlideRequests glideRequests;
|
||||
private final @NonNull Locale locale;
|
||||
private final @NonNull Recipient recipient;
|
||||
@ -142,7 +138,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
ConversationAdapter(Context context, Cursor cursor) {
|
||||
super(context, cursor);
|
||||
try {
|
||||
this.masterSecret = null;
|
||||
this.glideRequests = null;
|
||||
this.locale = null;
|
||||
this.clickListener = null;
|
||||
@ -157,7 +152,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
}
|
||||
|
||||
public ConversationAdapter(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@Nullable ItemClickListener clickListener,
|
||||
@ -167,7 +161,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
super(context, cursor);
|
||||
|
||||
try {
|
||||
this.masterSecret = masterSecret;
|
||||
this.glideRequests = glideRequests;
|
||||
this.locale = locale;
|
||||
this.clickListener = clickListener;
|
||||
@ -193,7 +186,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
@Override
|
||||
protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord) {
|
||||
long start = System.currentTimeMillis();
|
||||
viewHolder.getView().bind(masterSecret, messageRecord, glideRequests, locale, batchSelected, recipient);
|
||||
viewHolder.getView().bind(messageRecord, glideRequests, locale, batchSelected, recipient);
|
||||
Log.w(TAG, "Bind time: " + (System.currentTimeMillis() - start));
|
||||
}
|
||||
|
||||
@ -302,7 +295,7 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
if (record != null) return record;
|
||||
}
|
||||
|
||||
final MessageRecord messageRecord = db.readerFor(cursor, masterSecret).getCurrent();
|
||||
final MessageRecord messageRecord = db.readerFor(cursor).getCurrent();
|
||||
messageRecordCache.put(type + messageId, new SoftReference<>(messageRecord));
|
||||
|
||||
return messageRecord;
|
||||
|
@ -52,7 +52,6 @@ import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
@ -92,7 +91,6 @@ public class ConversationFragment extends Fragment
|
||||
|
||||
private ConversationFragmentListener listener;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private Recipient recipient;
|
||||
private long threadId;
|
||||
private long lastSeen;
|
||||
@ -110,12 +108,11 @@ public class ConversationFragment extends Fragment
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
this.masterSecret = getArguments().getParcelable("master_secret");
|
||||
this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA);
|
||||
this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
final View view = inflater.inflate(R.layout.conversation_fragment, container, false);
|
||||
list = ViewUtil.findById(view, android.R.id.list);
|
||||
composeDivider = ViewUtil.findById(view, R.id.compose_divider);
|
||||
@ -192,7 +189,7 @@ public class ConversationFragment extends Fragment
|
||||
|
||||
private void initializeListAdapter() {
|
||||
if (this.recipient != null && this.threadId != -1) {
|
||||
ConversationAdapter adapter = new ConversationAdapter(getActivity(), masterSecret, GlideApp.with(this), locale, selectionClickListener, null, this.recipient);
|
||||
ConversationAdapter adapter = new ConversationAdapter(getActivity(), GlideApp.with(this), locale, selectionClickListener, null, this.recipient);
|
||||
list.setAdapter(adapter);
|
||||
list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false));
|
||||
|
||||
@ -352,7 +349,6 @@ public class ConversationFragment extends Fragment
|
||||
|
||||
private void handleDisplayDetails(MessageRecord message) {
|
||||
Intent intent = new Intent(getActivity(), MessageDetailsActivity.class);
|
||||
intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret);
|
||||
intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, message.getId());
|
||||
intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, threadId);
|
||||
intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, message.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT);
|
||||
@ -380,7 +376,7 @@ public class ConversationFragment extends Fragment
|
||||
new AsyncTask<MessageRecord, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(MessageRecord... messageRecords) {
|
||||
MessageSender.resend(context, masterSecret, messageRecords[0]);
|
||||
MessageSender.resend(context, messageRecords[0]);
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
|
||||
@ -391,7 +387,7 @@ public class ConversationFragment extends Fragment
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
for (Slide slide : message.getSlideDeck().getSlides()) {
|
||||
if ((slide.hasImage() || slide.hasVideo() || slide.hasAudio() || slide.hasDocument()) && slide.getUri() != null) {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity(), masterSecret);
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity());
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(slide.getUri(), slide.getContentType(), message.getDateReceived(), slide.getFileName().orNull()));
|
||||
return;
|
||||
}
|
||||
|
@ -184,8 +184,7 @@ public class ConversationItem extends LinearLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull MasterSecret masterSecret,
|
||||
@NonNull MessageRecord messageRecord,
|
||||
public void bind(@NonNull MessageRecord messageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@ -366,7 +365,7 @@ public class ConversationItem extends LinearLayout
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
audioViewStub.get().setAudio(masterSecret, ((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls);
|
||||
audioViewStub.get().setAudio(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls);
|
||||
audioViewStub.get().setDownloadClickListener(downloadClickListener);
|
||||
audioViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
@ -389,7 +388,7 @@ public class ConversationItem extends LinearLayout
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
mediaThumbnailStub.get().setImageResource(masterSecret, glideRequests,
|
||||
mediaThumbnailStub.get().setImageResource(glideRequests,
|
||||
((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide(),
|
||||
showControls, false);
|
||||
mediaThumbnailStub.get().setThumbnailClickListener(new ThumbnailClickListener());
|
||||
@ -564,7 +563,7 @@ public class ConversationItem extends LinearLayout
|
||||
throw new AssertionError("Identity mismatch count: " + mismatches.size());
|
||||
}
|
||||
|
||||
new ConfirmIdentityDialog(context, masterSecret, messageRecord, mismatches.get(0)).show();
|
||||
new ConfirmIdentityDialog(context, messageRecord, mismatches.get(0)).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -660,7 +659,6 @@ public class ConversationItem extends LinearLayout
|
||||
parent.onClick(v);
|
||||
} else if (messageRecord.isFailed()) {
|
||||
Intent intent = new Intent(context, MessageDetailsActivity.class);
|
||||
intent.putExtra(MessageDetailsActivity.MASTER_SECRET_EXTRA, masterSecret);
|
||||
intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, messageRecord.getId());
|
||||
intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, messageRecord.getThreadId());
|
||||
intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, messageRecord.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT);
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.*;
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
@ -60,7 +59,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private ConversationListFragment fragment;
|
||||
private MasterSecret masterSecret;
|
||||
private SearchToolbar searchToolbar;
|
||||
private ImageView searchAction;
|
||||
|
||||
@ -72,8 +70,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
|
||||
setContentView(R.layout.conversation_list_activity);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
@ -211,7 +207,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
||||
Context context = ConversationListActivity.this;
|
||||
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setAllThreadsRead();
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret);
|
||||
MessageNotifier.updateNotification(context);
|
||||
MarkReadReceiver.process(context, messageIds);
|
||||
|
||||
return null;
|
||||
|
@ -25,8 +25,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
@ -53,8 +51,6 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
||||
private static final int MESSAGE_TYPE_INBOX_ZERO = 3;
|
||||
|
||||
private final @NonNull ThreadDatabase threadDatabase;
|
||||
private final @NonNull MasterSecret masterSecret;
|
||||
private final @NonNull MasterCipher masterCipher;
|
||||
private final @NonNull GlideRequests glideRequests;
|
||||
private final @NonNull Locale locale;
|
||||
private final @NonNull LayoutInflater inflater;
|
||||
@ -83,7 +79,6 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
||||
}
|
||||
|
||||
ConversationListAdapter(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@Nullable Cursor cursor,
|
||||
@ -91,8 +86,6 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
||||
{
|
||||
super(context, cursor);
|
||||
try {
|
||||
this.masterSecret = masterSecret;
|
||||
this.masterCipher = new MasterCipher(masterSecret);
|
||||
this.glideRequests = glideRequests;
|
||||
this.threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
this.locale = locale;
|
||||
@ -142,7 +135,7 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
viewHolder.getItem().bind(masterSecret, getThreadRecord(cursor), glideRequests, locale, batchSet, batchMode);
|
||||
viewHolder.getItem().bind(getThreadRecord(cursor), glideRequests, locale, batchSet, batchMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -159,10 +152,10 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
||||
}
|
||||
|
||||
private ThreadRecord getThreadRecord(@NonNull Cursor cursor) {
|
||||
return threadDatabase.readerFor(cursor, masterCipher).getCurrent();
|
||||
return threadDatabase.readerFor(cursor).getCurrent();
|
||||
}
|
||||
|
||||
public void toggleThreadInBatchSet(long threadId) {
|
||||
void toggleThreadInBatchSet(long threadId) {
|
||||
if (batchSet.contains(threadId)) {
|
||||
batchSet.remove(threadId);
|
||||
} else if (threadId != -1) {
|
||||
@ -170,21 +163,21 @@ class ConversationListAdapter extends CursorRecyclerViewAdapter<ConversationList
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Long> getBatchSelections() {
|
||||
Set<Long> getBatchSelections() {
|
||||
return batchSet;
|
||||
}
|
||||
|
||||
public void initializeBatchMode(boolean toggle) {
|
||||
void initializeBatchMode(boolean toggle) {
|
||||
this.batchMode = toggle;
|
||||
unselectAllThreads();
|
||||
}
|
||||
|
||||
public void unselectAllThreads() {
|
||||
private void unselectAllThreads() {
|
||||
this.batchSet.clear();
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void selectAllThreads() {
|
||||
void selectAllThreads() {
|
||||
for (int i = 0; i < getItemCount(); i++) {
|
||||
long threadId = getThreadRecord(getCursorAtPositionOrThrow(i)).getThreadId();
|
||||
if (threadId != -1) batchSet.add(threadId);
|
||||
|
@ -221,7 +221,7 @@ public class ConversationListFragment extends Fragment
|
||||
}
|
||||
|
||||
private void initializeListAdapter() {
|
||||
list.setAdapter(new ConversationListAdapter(getActivity(), masterSecret, GlideApp.with(this), locale, null, this));
|
||||
list.setAdapter(new ConversationListAdapter(getActivity(), GlideApp.with(this), locale, null, this));
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@ -302,7 +302,7 @@ public class ConversationListFragment extends Fragment
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getThreadDatabase(getActivity()).deleteConversations(selectedConversations);
|
||||
MessageNotifier.updateNotification(getActivity(), masterSecret);
|
||||
MessageNotifier.updateNotification(getActivity());
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -519,7 +519,7 @@ public class ConversationListFragment extends Fragment
|
||||
|
||||
if (unreadCount > 0) {
|
||||
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(getActivity()).setRead(threadId, false);
|
||||
MessageNotifier.updateNotification(getActivity(), masterSecret);
|
||||
MessageNotifier.updateNotification(getActivity());
|
||||
MarkReadReceiver.process(getActivity(), messageIds);
|
||||
}
|
||||
}
|
||||
@ -530,7 +530,7 @@ public class ConversationListFragment extends Fragment
|
||||
|
||||
if (unreadCount > 0) {
|
||||
DatabaseFactory.getThreadDatabase(getActivity()).incrementUnread(threadId, unreadCount);
|
||||
MessageNotifier.updateNotification(getActivity(), masterSecret);
|
||||
MessageNotifier.updateNotification(getActivity());
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId);
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
@ -38,7 +37,6 @@ import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.DeliveryStatusView;
|
||||
import org.thoughtcrime.securesms.components.FromTextView;
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@ -108,7 +106,7 @@ public class ConversationListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread,
|
||||
public void bind(@NonNull ThreadRecord thread,
|
||||
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
||||
@NonNull Set<Long> selectedThreads, boolean batchMode)
|
||||
{
|
||||
@ -139,7 +137,7 @@ public class ConversationListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
setStatusIcons(thread);
|
||||
setThumbnailSnippet(masterSecret, thread);
|
||||
setThumbnailSnippet(thread);
|
||||
setBatchState(batchMode);
|
||||
setRippleColor(recipient);
|
||||
setUnreadIndicator(thread);
|
||||
@ -175,10 +173,10 @@ public class ConversationListItem extends RelativeLayout
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
private void setThumbnailSnippet(MasterSecret masterSecret, ThreadRecord thread) {
|
||||
private void setThumbnailSnippet(ThreadRecord thread) {
|
||||
if (thread.getSnippetUri() != null) {
|
||||
this.thumbnailView.setVisibility(View.VISIBLE);
|
||||
this.thumbnailView.setImageResource(masterSecret, glideRequests, thread.getSnippetUri());
|
||||
this.thumbnailView.setImageResource(glideRequests, thread.getSnippetUri());
|
||||
|
||||
LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectView.getLayoutParams();
|
||||
subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.thumbnail);
|
||||
|
@ -8,7 +8,6 @@ import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
@ -40,7 +39,7 @@ public class ConversationListItemAction extends LinearLayout implements Bindable
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set<Long> selectedThreads, boolean batchMode) {
|
||||
public void bind(@NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set<Long> selectedThreads, boolean batchMode) {
|
||||
this.description.setText(getContext().getString(R.string.ConversationListItemAction_archived_conversations_d, thread.getCount()));
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import android.support.annotation.RequiresApi;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
|
||||
@ -40,7 +39,7 @@ public class ConversationListItemInboxZero extends LinearLayout implements Binda
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull MasterSecret masterSecret, @NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set<Long> selectedThreads, boolean batchMode) {
|
||||
public void bind(@NonNull ThreadRecord thread, @NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull Set<Long> selectedThreads, boolean batchMode) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
@ -38,7 +37,6 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
{
|
||||
private static final String TAG = ConversationUpdateItem.class.getSimpleName();
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private Set<MessageRecord> batchSelected;
|
||||
|
||||
private ImageView icon;
|
||||
@ -60,22 +58,20 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
this.icon = (ImageView)findViewById(R.id.conversation_update_icon);
|
||||
this.body = (TextView)findViewById(R.id.conversation_update_body);
|
||||
this.date = (TextView)findViewById(R.id.conversation_update_date);
|
||||
this.icon = findViewById(R.id.conversation_update_icon);
|
||||
this.body = findViewById(R.id.conversation_update_body);
|
||||
this.date = findViewById(R.id.conversation_update_date);
|
||||
|
||||
this.setOnClickListener(new InternalClickListener(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull MasterSecret masterSecret,
|
||||
@NonNull MessageRecord messageRecord,
|
||||
public void bind(@NonNull MessageRecord messageRecord,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@NonNull Locale locale,
|
||||
@NonNull Set<MessageRecord> batchSelected,
|
||||
@NonNull Recipient conversationRecipient)
|
||||
{
|
||||
this.masterSecret = masterSecret;
|
||||
this.batchSelected = batchSelected;
|
||||
|
||||
bind(messageRecord, locale);
|
||||
@ -172,12 +168,7 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bind(messageRecord, locale);
|
||||
}
|
||||
});
|
||||
Util.runOnMain(() -> bind(messageRecord, locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -196,7 +187,7 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
|
||||
@Nullable private final View.OnClickListener parent;
|
||||
|
||||
public InternalClickListener(@Nullable View.OnClickListener parent) {
|
||||
InternalClickListener(@Nullable View.OnClickListener parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
|
@ -76,6 +76,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
public static final int SCREENSHOTS = 300;
|
||||
public static final int PERSISTENT_BLOBS = 317;
|
||||
public static final int INTERNALIZE_CONTACTS = 317;
|
||||
public static final int SQLCIPHER = 334;
|
||||
|
||||
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
|
||||
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
|
||||
@ -93,6 +94,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
add(SCREENSHOTS);
|
||||
add(INTERNALIZE_CONTACTS);
|
||||
add(PERSISTENT_BLOBS);
|
||||
add(SQLCIPHER);
|
||||
}};
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
@ -106,14 +108,14 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
Log.w("DatabaseUpgradeActivity", "Upgrading...");
|
||||
setContentView(R.layout.database_upgrade_activity);
|
||||
|
||||
ProgressBar indeterminateProgress = (ProgressBar)findViewById(R.id.indeterminate_progress);
|
||||
ProgressBar determinateProgress = (ProgressBar)findViewById(R.id.determinate_progress);
|
||||
ProgressBar indeterminateProgress = findViewById(R.id.indeterminate_progress);
|
||||
ProgressBar determinateProgress = findViewById(R.id.determinate_progress);
|
||||
|
||||
new DatabaseUpgradeTask(indeterminateProgress, determinateProgress)
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, VersionTracker.getLastSeenVersion(this));
|
||||
} else {
|
||||
VersionTracker.updateLastSeenVersion(this);
|
||||
updateNotifications(this, masterSecret);
|
||||
updateNotifications(this);
|
||||
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
||||
finish();
|
||||
}
|
||||
@ -149,11 +151,11 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void updateNotifications(final Context context, final MasterSecret masterSecret) {
|
||||
private void updateNotifications(final Context context) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
MessageNotifier.updateNotification(context, masterSecret);
|
||||
MessageNotifier.updateNotification(context);
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
@ -171,7 +173,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
private final ProgressBar indeterminateProgress;
|
||||
private final ProgressBar determinateProgress;
|
||||
|
||||
public DatabaseUpgradeTask(ProgressBar indeterminateProgress, ProgressBar determinateProgress) {
|
||||
DatabaseUpgradeTask(ProgressBar indeterminateProgress, ProgressBar determinateProgress) {
|
||||
this.indeterminateProgress = indeterminateProgress;
|
||||
this.determinateProgress = determinateProgress;
|
||||
}
|
||||
@ -278,11 +280,11 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
private void schedulePendingIncomingParts(Context context) {
|
||||
final AttachmentDatabase attachmentDb = DatabaseFactory.getAttachmentDatabase(context);
|
||||
final MmsDatabase mmsDb = DatabaseFactory.getMmsDatabase(context);
|
||||
final List<DatabaseAttachment> pendingAttachments = DatabaseFactory.getAttachmentDatabase(context).getPendingAttachments(masterSecret);
|
||||
final List<DatabaseAttachment> pendingAttachments = DatabaseFactory.getAttachmentDatabase(context).getPendingAttachments();
|
||||
|
||||
Log.w(TAG, pendingAttachments.size() + " pending parts.");
|
||||
for (DatabaseAttachment attachment : pendingAttachments) {
|
||||
final Reader reader = mmsDb.readerFor(masterSecret, mmsDb.getMessage(attachment.getMmsId()));
|
||||
final Reader reader = mmsDb.readerFor(mmsDb.getMessage(attachment.getMmsId()));
|
||||
final MessageRecord record = reader.getNext();
|
||||
|
||||
if (attachment.hasData()) {
|
||||
@ -329,7 +331,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
VersionTracker.updateLastSeenVersion(DatabaseUpgradeActivity.this);
|
||||
updateNotifications(DatabaseUpgradeActivity.this, masterSecret);
|
||||
updateNotifications(DatabaseUpgradeActivity.this);
|
||||
|
||||
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
||||
finish();
|
||||
|
@ -18,7 +18,6 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
@ -102,7 +101,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
private ListView lv;
|
||||
private ImageView avatar;
|
||||
private TextView creatingText;
|
||||
private MasterSecret masterSecret;
|
||||
private Bitmap avatarBmp;
|
||||
|
||||
@NonNull private Optional<GroupData> groupToUpdate = Optional.absent();
|
||||
@ -115,8 +113,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
|
||||
setContentView(R.layout.group_create_activity);
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
@ -192,12 +188,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
recipientsPanel.setPanelChangeListener(this);
|
||||
findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener());
|
||||
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_group_white_24dp).asDrawable(this, ContactColors.UNKNOWN_COLOR.toConversationColor(this)));
|
||||
avatar.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Crop.pickImage(GroupCreateActivity.this);
|
||||
}
|
||||
});
|
||||
avatar.setOnClickListener(view -> Crop.pickImage(GroupCreateActivity.this));
|
||||
}
|
||||
|
||||
private void initializeExistingGroup() {
|
||||
@ -252,14 +243,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
return;
|
||||
}
|
||||
if (isSignalGroup()) {
|
||||
new CreateSignalGroupTask(this, masterSecret, avatarBmp, getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
new CreateSignalGroupTask(this, avatarBmp, getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} else {
|
||||
new CreateMmsGroupTask(this, masterSecret, getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
new CreateMmsGroupTask(this, getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGroupUpdate() {
|
||||
new UpdateSignalGroupTask(this, masterSecret, groupToUpdate.get().id, avatarBmp,
|
||||
new UpdateSignalGroupTask(this, groupToUpdate.get().id, avatarBmp,
|
||||
getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
@ -313,7 +304,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
.override(AVATAR_SIZE, AVATAR_SIZE)
|
||||
.into(new SimpleTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
|
||||
setAvatar(Crop.getOutput(data), resource);
|
||||
}
|
||||
});
|
||||
@ -332,12 +323,10 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private static class CreateMmsGroupTask extends AsyncTask<Void,Void,GroupActionResult> {
|
||||
private final GroupCreateActivity activity;
|
||||
private final MasterSecret masterSecret;
|
||||
private final Set<Recipient> members;
|
||||
|
||||
public CreateMmsGroupTask(GroupCreateActivity activity, MasterSecret masterSecret, Set<Recipient> members) {
|
||||
public CreateMmsGroupTask(GroupCreateActivity activity, Set<Recipient> members) {
|
||||
this.activity = activity;
|
||||
this.masterSecret = masterSecret;
|
||||
this.members = members;
|
||||
}
|
||||
|
||||
@ -368,20 +357,18 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private abstract static class SignalGroupTask extends AsyncTask<Void,Void,Optional<GroupActionResult>> {
|
||||
|
||||
protected GroupCreateActivity activity;
|
||||
protected MasterSecret masterSecret;
|
||||
protected Bitmap avatar;
|
||||
protected Set<Recipient> members;
|
||||
protected String name;
|
||||
|
||||
public SignalGroupTask(GroupCreateActivity activity,
|
||||
MasterSecret masterSecret,
|
||||
Bitmap avatar,
|
||||
String name,
|
||||
Set<Recipient> members)
|
||||
{
|
||||
this.activity = activity;
|
||||
this.masterSecret = masterSecret;
|
||||
this.avatar = avatar;
|
||||
this.name = name;
|
||||
this.members = members;
|
||||
@ -408,13 +395,13 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private static class CreateSignalGroupTask extends SignalGroupTask {
|
||||
public CreateSignalGroupTask(GroupCreateActivity activity, MasterSecret masterSecret, Bitmap avatar, String name, Set<Recipient> members) {
|
||||
super(activity, masterSecret, avatar, name, members);
|
||||
public CreateSignalGroupTask(GroupCreateActivity activity, Bitmap avatar, String name, Set<Recipient> members) {
|
||||
super(activity, avatar, name, members);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<GroupActionResult> doInBackground(Void... aVoid) {
|
||||
return Optional.of(GroupManager.createGroup(activity, masterSecret, members, avatar, name, false));
|
||||
return Optional.of(GroupManager.createGroup(activity, members, avatar, name, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -434,18 +421,17 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
|
||||
private static class UpdateSignalGroupTask extends SignalGroupTask {
|
||||
private String groupId;
|
||||
|
||||
public UpdateSignalGroupTask(GroupCreateActivity activity,
|
||||
MasterSecret masterSecret, String groupId, Bitmap avatar, String name,
|
||||
Set<Recipient> members)
|
||||
public UpdateSignalGroupTask(GroupCreateActivity activity, String groupId,
|
||||
Bitmap avatar, String name, Set<Recipient> members)
|
||||
{
|
||||
super(activity, masterSecret, avatar, name, members);
|
||||
super(activity, avatar, name, members);
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<GroupActionResult> doInBackground(Void... aVoid) {
|
||||
try {
|
||||
return Optional.of(GroupManager.updateGroup(activity, masterSecret, groupId, members, avatar, name));
|
||||
return Optional.of(GroupManager.updateGroup(activity, groupId, members, avatar, name));
|
||||
} catch (InvalidNumberException e) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.database.PlaintextBackupExporter;
|
||||
import org.thoughtcrime.securesms.database.PlaintextBackupImporter;
|
||||
@ -35,13 +34,11 @@ public class ImportExportFragment extends Fragment {
|
||||
private static final int NO_SD_CARD = 1;
|
||||
private static final int ERROR_IO = 2;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
this.masterSecret = getArguments().getParcelable("master_secret");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -87,7 +84,6 @@ public class ImportExportFragment extends Fragment {
|
||||
.onAllGranted(() -> {
|
||||
Intent intent = new Intent(getActivity(), ApplicationMigrationService.class);
|
||||
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
getActivity().startService(intent);
|
||||
|
||||
Intent nextIntent = new Intent(getActivity(), ConversationListActivity.class);
|
||||
@ -185,7 +181,7 @@ public class ImportExportFragment extends Fragment {
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
try {
|
||||
PlaintextBackupImporter.importPlaintextFromSd(getActivity(), masterSecret);
|
||||
PlaintextBackupImporter.importPlaintextFromSd(getActivity());
|
||||
return SUCCESS;
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w("ImportFragment", e);
|
||||
@ -212,7 +208,7 @@ public class ImportExportFragment extends Fragment {
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
try {
|
||||
PlaintextBackupExporter.exportPlaintextToSd(getActivity(), masterSecret);
|
||||
PlaintextBackupExporter.exportPlaintextToSd(getActivity());
|
||||
return SUCCESS;
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w("ExportFragment", e);
|
||||
|
@ -235,7 +235,7 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
|
||||
Recipient recipient = Recipient.from(context, Address.fromExternal(context, number), false);
|
||||
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1);
|
||||
|
||||
MessageSender.send(context, masterSecret, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null);
|
||||
MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null);
|
||||
|
||||
if (recipient.getContactUri() != null) {
|
||||
DatabaseFactory.getRecipientDatabase(context).setSeenInviteReminder(recipient, true);
|
||||
|
@ -17,7 +17,6 @@ import android.widget.Toast;
|
||||
import org.thoughtcrime.securesms.MediaDocumentsAdapter.HeaderViewHolder;
|
||||
import org.thoughtcrime.securesms.MediaDocumentsAdapter.ViewHolder;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide;
|
||||
@ -36,14 +35,12 @@ import static com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager.TAG;
|
||||
|
||||
public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter<ViewHolder> implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder> {
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
private final Calendar calendar;
|
||||
private final Locale locale;
|
||||
|
||||
public MediaDocumentsAdapter(Context context, MasterSecret masterSecret, Cursor cursor, Locale locale) {
|
||||
MediaDocumentsAdapter(Context context, Cursor cursor, Locale locale) {
|
||||
super(context, cursor);
|
||||
|
||||
this.masterSecret = masterSecret;
|
||||
this.calendar = Calendar.getInstance();
|
||||
this.locale = locale;
|
||||
}
|
||||
@ -55,7 +52,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter<ViewHolder>
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor);
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor);
|
||||
Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment());
|
||||
|
||||
if (slide != null && slide.hasDocument()) {
|
||||
@ -89,7 +86,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter<ViewHolder>
|
||||
if (position < 0) return -1;
|
||||
|
||||
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor);
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor);
|
||||
|
||||
calendar.setTime(new Date(mediaRecord.getDate()));
|
||||
return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR));
|
||||
@ -103,7 +100,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter<ViewHolder>
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
|
||||
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor);
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor);
|
||||
viewHolder.textView.setText(DateUtils.getRelativeDate(getContext(), locale, mediaRecord.getDate()));
|
||||
}
|
||||
|
||||
@ -114,8 +111,8 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter<ViewHolder>
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.documentView = (DocumentView)itemView.findViewById(R.id.document_view);
|
||||
this.date = (TextView)itemView.findViewById(R.id.date);
|
||||
this.documentView = itemView.findViewById(R.id.document_view);
|
||||
this.date = itemView.findViewById(R.id.date);
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +122,7 @@ public class MediaDocumentsAdapter extends CursorRecyclerViewAdapter<ViewHolder>
|
||||
|
||||
HeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.textView = (TextView)itemView.findViewById(R.id.text);
|
||||
this.textView = itemView.findViewById(R.id.text);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ import android.widget.TextView;
|
||||
import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter;
|
||||
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
||||
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia;
|
||||
@ -39,10 +38,10 @@ import java.util.Locale;
|
||||
|
||||
class MediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = MediaGalleryAdapter.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final GlideRequests glideRequests;
|
||||
private final Locale locale;
|
||||
private final Address address;
|
||||
@ -67,11 +66,10 @@ class MediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
MediaGalleryAdapter(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests,
|
||||
MediaGalleryAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
|
||||
BucketedThreadMedia media, Locale locale, Address address)
|
||||
{
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.glideRequests = glideRequests;
|
||||
this.locale = locale;
|
||||
this.media = media;
|
||||
@ -105,7 +103,7 @@ class MediaGalleryAdapter extends StickyHeaderGridAdapter {
|
||||
Slide slide = MediaUtil.getSlideForAttachment(context, mediaRecord.getAttachment());
|
||||
|
||||
if (slide != null) {
|
||||
thumbnailView.setImageResource(masterSecret, glideRequests, slide, false, false);
|
||||
thumbnailView.setImageResource(glideRequests, slide, false, false);
|
||||
}
|
||||
|
||||
thumbnailView.setOnClickListener(new OnMediaClickListener(mediaRecord));
|
||||
|
@ -207,7 +207,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity
|
||||
this.noMedia = ViewUtil.findById(view, R.id.no_images);
|
||||
this.gridManager = new StickyHeaderGridLayoutManager(getResources().getInteger(R.integer.media_overview_cols));
|
||||
|
||||
this.recyclerView.setAdapter(new MediaGalleryAdapter(getContext(), masterSecret, GlideApp.with(this), new BucketedThreadMedia(getContext()), locale, recipient.getAddress()));
|
||||
this.recyclerView.setAdapter(new MediaGalleryAdapter(getContext(), GlideApp.with(this), new BucketedThreadMedia(getContext()), locale, recipient.getAddress()));
|
||||
this.recyclerView.setLayoutManager(gridManager);
|
||||
this.recyclerView.setHasFixedSize(true);
|
||||
|
||||
@ -225,7 +225,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public Loader<BucketedThreadMedia> onCreateLoader(int i, Bundle bundle) {
|
||||
return new BucketedThreadMediaLoader(getContext(), masterSecret, recipient.getAddress());
|
||||
return new BucketedThreadMediaLoader(getContext(), recipient.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -248,7 +248,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.media_overview_documents_fragment, container, false);
|
||||
MediaDocumentsAdapter adapter = new MediaDocumentsAdapter(getContext(), masterSecret, null, locale);
|
||||
MediaDocumentsAdapter adapter = new MediaDocumentsAdapter(getContext(), null, locale);
|
||||
|
||||
this.recyclerView = ViewUtil.findById(view, R.id.recycler_view);
|
||||
this.noMedia = ViewUtil.findById(view, R.id.no_documents);
|
||||
|
@ -81,8 +81,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
|
||||
private ViewPager mediaPager;
|
||||
private Uri initialMediaUri;
|
||||
private String initialMediaType;
|
||||
@ -95,7 +93,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
this.setTheme(R.style.TextSecure_DarkTheme);
|
||||
dynamicLanguage.onCreate(this);
|
||||
|
||||
@ -203,7 +200,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
if (conversationRecipient != null) {
|
||||
getSupportLoaderManager().restartLoader(0, null, this);
|
||||
} else {
|
||||
mediaPager.setAdapter(new SingleItemPagerAdapter(this, masterSecret, GlideApp.with(this), getWindow(), initialMediaUri, initialMediaType, initialMediaSize));
|
||||
mediaPager.setAdapter(new SingleItemPagerAdapter(this, GlideApp.with(this), getWindow(), initialMediaUri, initialMediaType, initialMediaSize));
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,7 +243,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||
.onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
|
||||
.onAllGranted(() -> {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this, masterSecret);
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this);
|
||||
long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis();
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null));
|
||||
})
|
||||
@ -304,7 +301,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
public void onLoadFinished(Loader<Pair<Cursor, Integer>> loader, @Nullable Pair<Cursor, Integer> data) {
|
||||
if (data != null) {
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
CursorPagerAdapter adapter = new CursorPagerAdapter(this, masterSecret, GlideApp.with(this), getWindow(), data.first, data.second, leftIsRecent);
|
||||
CursorPagerAdapter adapter = new CursorPagerAdapter(this, GlideApp.with(this), getWindow(), data.first, data.second, leftIsRecent);
|
||||
mediaPager.setAdapter(adapter);
|
||||
adapter.setActive(true);
|
||||
|
||||
@ -350,7 +347,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
|
||||
private static class SingleItemPagerAdapter extends PagerAdapter implements MediaItemAdapter {
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
private final GlideRequests glideRequests;
|
||||
private final Window window;
|
||||
private final Uri uri;
|
||||
@ -359,11 +355,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
|
||||
private final LayoutInflater inflater;
|
||||
|
||||
SingleItemPagerAdapter(@NonNull Context context, @NonNull MasterSecret masterSecret,
|
||||
@NonNull GlideRequests glideRequests, @NonNull Window window,
|
||||
@NonNull Uri uri, @NonNull String mediaType, long size)
|
||||
SingleItemPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
|
||||
@NonNull Window window, @NonNull Uri uri, @NonNull String mediaType,
|
||||
long size)
|
||||
{
|
||||
this.masterSecret = masterSecret;
|
||||
this.glideRequests = glideRequests;
|
||||
this.window = window;
|
||||
this.uri = uri;
|
||||
@ -388,7 +383,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
MediaView mediaView = itemView.findViewById(R.id.media_view);
|
||||
|
||||
try {
|
||||
mediaView.set(masterSecret, glideRequests, window, uri, mediaType, size, true);
|
||||
mediaView.set(glideRequests, window, uri, mediaType, size, true);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
@ -422,7 +417,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
private final WeakHashMap<Integer, MediaView> mediaViews = new WeakHashMap<>();
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final GlideRequests glideRequests;
|
||||
private final Window window;
|
||||
private final Cursor cursor;
|
||||
@ -431,12 +425,11 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
private boolean active;
|
||||
private int autoPlayPosition;
|
||||
|
||||
CursorPagerAdapter(@NonNull Context context, @NonNull MasterSecret masterSecret,
|
||||
@NonNull GlideRequests glideRequests, @NonNull Window window,
|
||||
@NonNull Cursor cursor, int autoPlayPosition, boolean leftIsRecent)
|
||||
CursorPagerAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
|
||||
@NonNull Window window, @NonNull Cursor cursor, int autoPlayPosition,
|
||||
boolean leftIsRecent)
|
||||
{
|
||||
this.context = context.getApplicationContext();
|
||||
this.masterSecret = masterSecret;
|
||||
this.glideRequests = glideRequests;
|
||||
this.window = window;
|
||||
this.cursor = cursor;
|
||||
@ -471,11 +464,11 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
|
||||
cursor.moveToPosition(cursorPosition);
|
||||
|
||||
MediaRecord mediaRecord = MediaRecord.from(context, masterSecret, cursor);
|
||||
MediaRecord mediaRecord = MediaRecord.from(context, cursor);
|
||||
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
mediaView.set(masterSecret, glideRequests, window, mediaRecord.getAttachment().getDataUri(), mediaRecord.getAttachment().getContentType(), mediaRecord.getAttachment().getSize(), autoplay);
|
||||
mediaView.set(glideRequests, window, mediaRecord.getAttachment().getDataUri(), mediaRecord.getAttachment().getContentType(), mediaRecord.getAttachment().getSize(), autoplay);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
@ -497,7 +490,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
|
||||
public MediaItem getMediaItemFor(int position) {
|
||||
cursor.moveToPosition(getCursorPosition(position));
|
||||
MediaRecord mediaRecord = MediaRecord.from(context, masterSecret, cursor);
|
||||
MediaRecord mediaRecord = MediaRecord.from(context, cursor);
|
||||
Address address = mediaRecord.getAddress();
|
||||
|
||||
if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError();
|
||||
|
@ -39,7 +39,6 @@ import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
@ -72,14 +71,12 @@ import java.util.Locale;
|
||||
public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity implements LoaderCallbacks<Cursor>, RecipientModifiedListener {
|
||||
private final static String TAG = MessageDetailsActivity.class.getSimpleName();
|
||||
|
||||
public final static String MASTER_SECRET_EXTRA = "master_secret";
|
||||
public final static String MESSAGE_ID_EXTRA = "message_id";
|
||||
public final static String THREAD_ID_EXTRA = "thread_id";
|
||||
public final static String IS_PUSH_GROUP_EXTRA = "is_push_group";
|
||||
public final static String TYPE_EXTRA = "type";
|
||||
public final static String ADDRESS_EXTRA = "address";
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private GlideRequests glideRequests;
|
||||
private long threadId;
|
||||
private boolean isPushGroup;
|
||||
@ -166,21 +163,20 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
inflater = LayoutInflater.from(this);
|
||||
View header = inflater.inflate(R.layout.message_details_header, recipientsList, false);
|
||||
|
||||
masterSecret = getIntent().getParcelableExtra(MASTER_SECRET_EXTRA);
|
||||
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
|
||||
isPushGroup = getIntent().getBooleanExtra(IS_PUSH_GROUP_EXTRA, false);
|
||||
glideRequests = GlideApp.with(this);
|
||||
itemParent = (ViewGroup) header.findViewById(R.id.item_container);
|
||||
recipientsList = (ListView ) findViewById(R.id.recipients_list);
|
||||
metadataContainer = header.findViewById(R.id.metadata_container);
|
||||
errorText = (TextView ) header.findViewById(R.id.error_text);
|
||||
sentDate = (TextView ) header.findViewById(R.id.sent_time);
|
||||
receivedContainer = header.findViewById(R.id.received_container);
|
||||
receivedDate = (TextView ) header.findViewById(R.id.received_time);
|
||||
transport = (TextView ) header.findViewById(R.id.transport);
|
||||
toFrom = (TextView ) header.findViewById(R.id.tofrom);
|
||||
expiresContainer = header.findViewById(R.id.expires_container);
|
||||
expiresInText = (TextView) header.findViewById(R.id.expires_in);
|
||||
itemParent = header.findViewById(R.id.item_container);
|
||||
recipientsList = findViewById(R.id.recipients_list);
|
||||
metadataContainer = header.findViewById(R.id.metadata_container);
|
||||
errorText = header.findViewById(R.id.error_text);
|
||||
sentDate = header.findViewById(R.id.sent_time);
|
||||
receivedContainer = header.findViewById(R.id.received_container);
|
||||
receivedDate = header.findViewById(R.id.received_time);
|
||||
transport = header.findViewById(R.id.transport);
|
||||
toFrom = header.findViewById(R.id.tofrom);
|
||||
expiresContainer = header.findViewById(R.id.expires_container);
|
||||
expiresInText = header.findViewById(R.id.expires_in);
|
||||
recipientsList.setHeaderDividersEnabled(false);
|
||||
recipientsList.addHeaderView(header, null, false);
|
||||
}
|
||||
@ -253,8 +249,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
toFromRes = R.string.message_details_header__from;
|
||||
}
|
||||
toFrom.setText(toFromRes);
|
||||
conversationItem.bind(masterSecret, messageRecord, glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient);
|
||||
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, masterSecret, glideRequests, messageRecord, recipients, isPushGroup));
|
||||
conversationItem.bind(messageRecord, glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient);
|
||||
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup));
|
||||
}
|
||||
|
||||
private void inflateMessageViewIfAbsent(MessageRecord messageRecord) {
|
||||
@ -273,12 +269,12 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
private @Nullable MessageRecord getMessageRecord(Context context, Cursor cursor, String type) {
|
||||
switch (type) {
|
||||
case MmsSmsDatabase.SMS_TRANSPORT:
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsDatabase.Reader reader = smsDatabase.readerFor(masterSecret, cursor);
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
SmsDatabase.Reader reader = smsDatabase.readerFor(cursor);
|
||||
return reader.getNext();
|
||||
case MmsSmsDatabase.MMS_TRANSPORT:
|
||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||
MmsDatabase.Reader mmsReader = mmsDatabase.readerFor(masterSecret, cursor);
|
||||
MmsDatabase.Reader mmsReader = mmsDatabase.readerFor(cursor);
|
||||
return mmsReader.getNext();
|
||||
default:
|
||||
throw new AssertionError("no valid message type specified");
|
||||
|
@ -8,7 +8,6 @@ import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@ -21,18 +20,16 @@ import java.util.List;
|
||||
class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener {
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final GlideRequests glideRequests;
|
||||
private final MessageRecord record;
|
||||
private final List<RecipientDeliveryStatus> members;
|
||||
private final boolean isPushGroup;
|
||||
|
||||
MessageDetailsRecipientAdapter(@NonNull Context context, @NonNull MasterSecret masterSecret,
|
||||
@NonNull GlideRequests glideRequests, @NonNull MessageRecord record,
|
||||
@NonNull List<RecipientDeliveryStatus> members, boolean isPushGroup)
|
||||
MessageDetailsRecipientAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
|
||||
@NonNull MessageRecord record, @NonNull List<RecipientDeliveryStatus> members,
|
||||
boolean isPushGroup)
|
||||
{
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.glideRequests = glideRequests;
|
||||
this.record = record;
|
||||
this.isPushGroup = isPushGroup;
|
||||
@ -66,7 +63,7 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.
|
||||
|
||||
RecipientDeliveryStatus member = members.get(position);
|
||||
|
||||
((MessageRecipientListItem)convertView).set(masterSecret, glideRequests, record, member, isPushGroup);
|
||||
((MessageRecipientListItem)convertView).set(glideRequests, record, member, isPushGroup);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
@ -29,7 +30,6 @@ 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.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.util.Util;
|
||||
public class MessageRecipientListItem extends RelativeLayout
|
||||
implements RecipientModifiedListener
|
||||
{
|
||||
@SuppressWarnings("unused")
|
||||
private final static String TAG = MessageRecipientListItem.class.getSimpleName();
|
||||
|
||||
private RecipientDeliveryStatus member;
|
||||
@ -81,8 +82,7 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
this.deliveryStatusView = findViewById(R.id.delivery_status);
|
||||
}
|
||||
|
||||
public void set(final MasterSecret masterSecret,
|
||||
final GlideRequests glideRequests,
|
||||
public void set(final GlideRequests glideRequests,
|
||||
final MessageRecord record,
|
||||
final RecipientDeliveryStatus member,
|
||||
final boolean isPushGroup)
|
||||
@ -93,11 +93,10 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
member.getRecipient().addListener(this);
|
||||
fromView.setText(member.getRecipient());
|
||||
contactPhotoImage.setAvatar(glideRequests, member.getRecipient(), false);
|
||||
setIssueIndicators(masterSecret, record, isPushGroup);
|
||||
setIssueIndicators(record, isPushGroup);
|
||||
}
|
||||
|
||||
private void setIssueIndicators(final MasterSecret masterSecret,
|
||||
final MessageRecord record,
|
||||
private void setIssueIndicators(final MessageRecord record,
|
||||
final boolean isPushGroup)
|
||||
{
|
||||
final NetworkFailure networkFailure = getNetworkFailure(record);
|
||||
@ -110,7 +109,7 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
conflictButton.setVisibility(View.VISIBLE);
|
||||
|
||||
errorText = getContext().getString(R.string.MessageDetailsRecipient_new_safety_number);
|
||||
conflictButton.setOnClickListener(v -> new ConfirmIdentityDialog(getContext(), masterSecret, record, keyMismatch).show());
|
||||
conflictButton.setOnClickListener(v -> new ConfirmIdentityDialog(getContext(), record, keyMismatch).show());
|
||||
} else if (networkFailure != null || (!isPushGroup && record.isFailed())) {
|
||||
resendButton.setVisibility(View.VISIBLE);
|
||||
resendButton.setEnabled(true);
|
||||
@ -123,7 +122,7 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
errorDescription.setVisibility(View.GONE);
|
||||
actionDescription.setVisibility(View.VISIBLE);
|
||||
actionDescription.setText(R.string.message_recipients_list_item__resending);
|
||||
new ResendAsyncTask(masterSecret, record, networkFailure).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
new ResendAsyncTask(record, networkFailure).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
});
|
||||
} else {
|
||||
if (record.isOutgoing()) {
|
||||
@ -185,15 +184,14 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private class ResendAsyncTask extends AsyncTask<Void,Void,Void> {
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final MessageRecord record;
|
||||
private final NetworkFailure failure;
|
||||
|
||||
ResendAsyncTask(MasterSecret masterSecret, MessageRecord record, NetworkFailure failure) {
|
||||
ResendAsyncTask(MessageRecord record, NetworkFailure failure) {
|
||||
this.context = getContext().getApplicationContext();
|
||||
this.masterSecret = masterSecret;
|
||||
this.record = record;
|
||||
this.failure = failure;
|
||||
}
|
||||
@ -206,7 +204,7 @@ public class MessageRecipientListItem extends RelativeLayout
|
||||
if (record.getRecipient().isPushGroupRecipient()) {
|
||||
MessageSender.resendGroupMessage(context, record, failure.getAddress());
|
||||
} else {
|
||||
MessageSender.resend(context, masterSecret, record);
|
||||
MessageSender.resend(context, record);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
@ -66,6 +65,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
|
||||
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
||||
IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this);
|
||||
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
||||
|
||||
TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCurrentApkReleaseVersion(PassphraseCreateActivity.this));
|
||||
TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true);
|
||||
TextSecurePreferences.setReadReceiptsEnabled(PassphraseCreateActivity.this, true);
|
||||
|
@ -131,7 +131,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (!isPassingAlongMedia && resolvedExtra != null) {
|
||||
PersistentBlobProvider.getInstance(this).delete(resolvedExtra);
|
||||
PersistentBlobProvider.getInstance(this).delete(this, resolvedExtra);
|
||||
}
|
||||
if (!isFinishing()) {
|
||||
finish();
|
||||
@ -334,7 +334,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
|
||||
return PersistentBlobProvider.getInstance(context).create(masterSecret, inputStream, mimeType, fileName, fileSize);
|
||||
return PersistentBlobProvider.getInstance(context).create(context, inputStream, mimeType, fileName, fileSize);
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
return null;
|
||||
|
@ -618,7 +618,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
isChecked ? VerifiedStatus.VERIFIED :
|
||||
VerifiedStatus.DEFAULT));
|
||||
|
||||
IdentityUtil.markIdentityVerified(getActivity(), new MasterSecretUnion(masterSecret), recipient, isChecked, false);
|
||||
IdentityUtil.markIdentityVerified(getActivity(), recipient, isChecked, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
@ -38,7 +37,6 @@ public class AttachmentServer implements Runnable {
|
||||
private static final String TAG = AttachmentServer.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final Attachment attachment;
|
||||
private final ServerSocket socket;
|
||||
private final int port;
|
||||
@ -46,12 +44,11 @@ public class AttachmentServer implements Runnable {
|
||||
|
||||
private volatile boolean isRunning;
|
||||
|
||||
public AttachmentServer(Context context, MasterSecret masterSecret, Attachment attachment)
|
||||
public AttachmentServer(Context context, Attachment attachment)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.attachment = attachment;
|
||||
this.socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[]{127, 0, 0, 1}));
|
||||
this.port = socket.getLocalPort();
|
||||
@ -189,7 +186,7 @@ public class AttachmentServer implements Runnable {
|
||||
}
|
||||
|
||||
protected void execute() throws IOException {
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, attachment.getDataUri());
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.getDataUri());
|
||||
long fileSize = attachment.getSize();
|
||||
|
||||
String headers = "";
|
||||
|
@ -4,9 +4,8 @@ import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MediaKey;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
|
||||
@ -15,7 +14,7 @@ import java.util.List;
|
||||
|
||||
public class PointerAttachment extends Attachment {
|
||||
|
||||
public PointerAttachment(@NonNull String contentType, int transferState, long size,
|
||||
private PointerAttachment(@NonNull String contentType, int transferState, long size,
|
||||
@Nullable String fileName, @NonNull String location,
|
||||
@NonNull String key, @NonNull String relay,
|
||||
@Nullable byte[] digest, boolean voiceNote)
|
||||
@ -36,19 +35,20 @@ public class PointerAttachment extends Attachment {
|
||||
}
|
||||
|
||||
|
||||
public static List<Attachment> forPointers(@NonNull MasterSecretUnion masterSecret, Optional<List<SignalServiceAttachment>> pointers) {
|
||||
public static List<Attachment> forPointers(Optional<List<SignalServiceAttachment>> pointers) {
|
||||
List<Attachment> results = new LinkedList<>();
|
||||
|
||||
if (pointers.isPresent()) {
|
||||
for (SignalServiceAttachment pointer : pointers.get()) {
|
||||
if (pointer.isPointer()) {
|
||||
String encryptedKey = MediaKey.getEncrypted(masterSecret, pointer.asPointer().getKey());
|
||||
String encodedKey = Base64.encodeBytes(pointer.asPointer().getKey());
|
||||
|
||||
results.add(new PointerAttachment(pointer.getContentType(),
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
|
||||
pointer.asPointer().getSize().or(0),
|
||||
pointer.asPointer().getFileName().orNull(),
|
||||
String.valueOf(pointer.asPointer().getId()),
|
||||
encryptedKey, pointer.asPointer().getRelay().orNull(),
|
||||
encodedKey, pointer.asPointer().getRelay().orNull(),
|
||||
pointer.asPointer().getDigest().orNull(),
|
||||
pointer.asPointer().getVoiceNote()));
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ThreadUtil;
|
||||
@ -28,41 +27,35 @@ public class AudioRecorder {
|
||||
private static final ExecutorService executor = ThreadUtil.newDynamicSingleThreadedExecutor();
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final PersistentBlobProvider blobProvider;
|
||||
|
||||
private AudioCodec audioCodec;
|
||||
private Uri captureUri;
|
||||
|
||||
public AudioRecorder(@NonNull Context context, @NonNull MasterSecret masterSecret) {
|
||||
public AudioRecorder(@NonNull Context context) {
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.blobProvider = PersistentBlobProvider.getInstance(context.getApplicationContext());
|
||||
}
|
||||
|
||||
public void startRecording() {
|
||||
Log.w(TAG, "startRecording()");
|
||||
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.w(TAG, "Running startRecording() + " + Thread.currentThread().getId());
|
||||
try {
|
||||
if (audioCodec != null) {
|
||||
throw new AssertionError("We can only record once at a time.");
|
||||
}
|
||||
|
||||
ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe();
|
||||
|
||||
captureUri = blobProvider.create(masterSecret,
|
||||
new ParcelFileDescriptor.AutoCloseInputStream(fds[0]),
|
||||
MediaUtil.AUDIO_AAC, null, null);
|
||||
audioCodec = new AudioCodec();
|
||||
|
||||
audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
executor.execute(() -> {
|
||||
Log.w(TAG, "Running startRecording() + " + Thread.currentThread().getId());
|
||||
try {
|
||||
if (audioCodec != null) {
|
||||
throw new AssertionError("We can only record once at a time.");
|
||||
}
|
||||
|
||||
ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe();
|
||||
|
||||
captureUri = blobProvider.create(context, new ParcelFileDescriptor.AutoCloseInputStream(fds[0]),
|
||||
MediaUtil.AUDIO_AAC, null, null);
|
||||
audioCodec = new AudioCodec();
|
||||
|
||||
audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -72,47 +65,34 @@ public class AudioRecorder {
|
||||
|
||||
final SettableFuture<Pair<Uri, Long>> future = new SettableFuture<>();
|
||||
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (audioCodec == null) {
|
||||
sendToFuture(future, new IOException("MediaRecorder was never initialized successfully!"));
|
||||
return;
|
||||
}
|
||||
|
||||
audioCodec.stop();
|
||||
|
||||
try {
|
||||
long size = MediaUtil.getMediaSize(context, masterSecret, captureUri);
|
||||
sendToFuture(future, new Pair<>(captureUri, size));
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
sendToFuture(future, ioe);
|
||||
}
|
||||
|
||||
audioCodec = null;
|
||||
captureUri = null;
|
||||
executor.execute(() -> {
|
||||
if (audioCodec == null) {
|
||||
sendToFuture(future, new IOException("MediaRecorder was never initialized successfully!"));
|
||||
return;
|
||||
}
|
||||
|
||||
audioCodec.stop();
|
||||
|
||||
try {
|
||||
long size = MediaUtil.getMediaSize(context, captureUri);
|
||||
sendToFuture(future, new Pair<>(captureUri, size));
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
sendToFuture(future, ioe);
|
||||
}
|
||||
|
||||
audioCodec = null;
|
||||
captureUri = null;
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private <T> void sendToFuture(final SettableFuture<T> future, final Exception exception) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
future.setException(exception);
|
||||
}
|
||||
});
|
||||
Util.runOnMain(() -> future.setException(exception));
|
||||
}
|
||||
|
||||
private <T> void sendToFuture(final SettableFuture<T> future, final T result) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
future.set(result);
|
||||
}
|
||||
});
|
||||
Util.runOnMain(() -> future.set(result));
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
private static @NonNull Optional<AudioSlidePlayer> playing = Optional.absent();
|
||||
|
||||
private final @NonNull Context context;
|
||||
private final @NonNull MasterSecret masterSecret;
|
||||
private final @NonNull AudioSlide slide;
|
||||
private final @NonNull Handler progressEventHandler;
|
||||
private final @NonNull AudioManager audioManager;
|
||||
@ -50,7 +49,6 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
private long startTime;
|
||||
|
||||
public synchronized static AudioSlidePlayer createFor(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@NonNull AudioSlide slide,
|
||||
@NonNull Listener listener)
|
||||
{
|
||||
@ -58,17 +56,15 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
playing.get().setListener(listener);
|
||||
return playing.get();
|
||||
} else {
|
||||
return new AudioSlidePlayer(context, masterSecret, slide, listener);
|
||||
return new AudioSlidePlayer(context, slide, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private AudioSlidePlayer(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@NonNull AudioSlide slide,
|
||||
@NonNull Listener listener)
|
||||
{
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.slide = slide;
|
||||
this.listener = new WeakReference<>(listener);
|
||||
this.progressEventHandler = new ProgressEventHandler(this);
|
||||
@ -91,7 +87,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
if (this.mediaPlayer != null) return;
|
||||
|
||||
this.mediaPlayer = new MediaPlayerWrapper();
|
||||
this.audioAttachmentServer = new AttachmentServer(context, masterSecret, slide.asAttachment());
|
||||
this.audioAttachmentServer = new AttachmentServer(context, slide.asAttachment());
|
||||
this.startTime = System.currentTimeMillis();
|
||||
|
||||
audioAttachmentServer.start();
|
||||
|
@ -27,7 +27,6 @@ import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
@ -108,8 +107,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
public void setAudio(final @NonNull MasterSecret masterSecret,
|
||||
final @NonNull AudioSlide audio,
|
||||
public void setAudio(final @NonNull AudioSlide audio,
|
||||
final boolean showControls)
|
||||
{
|
||||
|
||||
@ -128,7 +126,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener
|
||||
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
|
||||
}
|
||||
|
||||
this.audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), masterSecret, audio, this);
|
||||
this.audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, this);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
|
@ -13,7 +13,6 @@ import android.view.Window;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
@ -54,8 +53,7 @@ public class MediaView extends FrameLayout {
|
||||
this.videoView = new Stub<>(findViewById(R.id.video_player_stub));
|
||||
}
|
||||
|
||||
public void set(@NonNull MasterSecret masterSecret,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
public void set(@NonNull GlideRequests glideRequests,
|
||||
@NonNull Window window,
|
||||
@NonNull Uri source,
|
||||
@NonNull String mediaType,
|
||||
@ -66,12 +64,12 @@ public class MediaView extends FrameLayout {
|
||||
if (mediaType.startsWith("image/")) {
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
if (videoView.resolved()) videoView.get().setVisibility(View.GONE);
|
||||
imageView.setImageUri(masterSecret, glideRequests, source, mediaType);
|
||||
imageView.setImageUri(glideRequests, source, mediaType);
|
||||
} else if (mediaType.startsWith("video/")) {
|
||||
imageView.setVisibility(View.GONE);
|
||||
videoView.get().setVisibility(View.VISIBLE);
|
||||
videoView.get().setWindow(window);
|
||||
videoView.get().setVideoSource(masterSecret, new VideoSlide(getContext(), source, size), autoplay);
|
||||
videoView.get().setVideoSource(new VideoSlide(getContext(), source, size), autoplay);
|
||||
} else {
|
||||
throw new IOException("Unsupported media type: " + mediaType);
|
||||
}
|
||||
|
@ -91,11 +91,11 @@ public class ThreadPhotoRailView extends FrameLayout {
|
||||
@Override
|
||||
public void onBindItemViewHolder(ThreadPhotoViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
ThumbnailView imageView = viewHolder.imageView;
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor);
|
||||
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor);
|
||||
Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment());
|
||||
|
||||
if (slide != null) {
|
||||
imageView.setImageResource(masterSecret, glideRequests, slide, false, false);
|
||||
imageView.setImageResource(glideRequests, slide, false, false);
|
||||
}
|
||||
|
||||
imageView.setOnClickListener(v -> {
|
||||
|
@ -17,7 +17,6 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
@ -58,8 +57,8 @@ public class ThumbnailView extends FrameLayout {
|
||||
inflate(context, R.layout.thumbnail_view, this);
|
||||
|
||||
this.radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius);
|
||||
this.image = (ImageView) findViewById(R.id.thumbnail_image);
|
||||
this.playOverlay = (ImageView) findViewById(R.id.play_overlay);
|
||||
this.image = findViewById(R.id.thumbnail_image);
|
||||
this.playOverlay = findViewById(R.id.play_overlay);
|
||||
super.setOnClickListener(new ThumbnailClickDispatcher());
|
||||
|
||||
if (attrs != null) {
|
||||
@ -88,7 +87,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
private TransferControlView getTransferControls() {
|
||||
if (!transferControls.isPresent()) {
|
||||
transferControls = Optional.of((TransferControlView)ViewUtil.inflateStub(this, R.id.transfer_controls_stub));
|
||||
transferControls = Optional.of(ViewUtil.inflateStub(this, R.id.transfer_controls_stub));
|
||||
}
|
||||
return transferControls.get();
|
||||
}
|
||||
@ -97,8 +96,8 @@ public class ThumbnailView extends FrameLayout {
|
||||
this.backgroundColorHint = color;
|
||||
}
|
||||
|
||||
public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests,
|
||||
@NonNull Slide slide, boolean showControls, boolean isPreview)
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
|
||||
boolean showControls, boolean isPreview)
|
||||
{
|
||||
if (showControls) {
|
||||
getTransferControls().setSlide(slide);
|
||||
@ -134,15 +133,15 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
this.slide = slide;
|
||||
|
||||
if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(masterSecret, glideRequests, slide).into(image);
|
||||
if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(glideRequests, slide).into(image);
|
||||
else if (slide.hasPlaceholder()) buildPlaceholderGlideRequest(glideRequests, slide).into(image);
|
||||
else glideRequests.clear(image);
|
||||
}
|
||||
|
||||
public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
|
||||
|
||||
glideRequests.load(new DecryptableUri(masterSecret, uri))
|
||||
glideRequests.load(new DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transform(new RoundedCorners(radius))
|
||||
.transition(withCrossFade())
|
||||
@ -172,8 +171,8 @@ public class ThumbnailView extends FrameLayout {
|
||||
getTransferControls().showProgressSpinner();
|
||||
}
|
||||
|
||||
private RequestBuilder buildThumbnailGlideRequest(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||
RequestBuilder builder = glideRequests.load(new DecryptableUri(masterSecret, slide.getThumbnailUri()))
|
||||
private RequestBuilder buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||
RequestBuilder builder = glideRequests.load(new DecryptableUri(slide.getThumbnailUri()))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transform(new RoundedCorners(radius))
|
||||
.centerCrop()
|
||||
|
@ -22,7 +22,6 @@ import com.github.chrisbanes.photoview.PhotoView;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.subsampling.AttachmentBitmapDecoder;
|
||||
import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
@ -61,8 +60,7 @@ public class ZoomingImageView extends FrameLayout {
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public void setImageUri(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests,
|
||||
@NonNull Uri uri, @NonNull String contentType)
|
||||
public void setImageUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri, @NonNull String contentType)
|
||||
{
|
||||
final Context context = getContext();
|
||||
final int maxTextureSize = BitmapUtil.getMaxTextureSize();
|
||||
@ -75,7 +73,7 @@ public class ZoomingImageView extends FrameLayout {
|
||||
if (MediaUtil.isGif(contentType)) return null;
|
||||
|
||||
try {
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri);
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
|
||||
return BitmapUtil.getDimensions(inputStream);
|
||||
} catch (IOException | BitmapDecodingException e) {
|
||||
Log.w(TAG, e);
|
||||
@ -88,29 +86,29 @@ public class ZoomingImageView extends FrameLayout {
|
||||
|
||||
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
|
||||
Log.w(TAG, "Loading in standard image view...");
|
||||
setImageViewUri(masterSecret, glideRequests, uri);
|
||||
setImageViewUri(glideRequests, uri);
|
||||
} else {
|
||||
Log.w(TAG, "Loading in subsampling image view...");
|
||||
setSubsamplingImageViewUri(masterSecret, uri);
|
||||
setSubsamplingImageViewUri(uri);
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void setImageViewUri(@NonNull MasterSecret masterSecret, @NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
private void setImageViewUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
photoView.setVisibility(View.VISIBLE);
|
||||
subsamplingImageView.setVisibility(View.GONE);
|
||||
|
||||
glideRequests.load(new DecryptableUri(masterSecret, uri))
|
||||
glideRequests.load(new DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontTransform()
|
||||
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||
.into(photoView);
|
||||
}
|
||||
|
||||
private void setSubsamplingImageViewUri(@NonNull MasterSecret masterSecret, @NonNull Uri uri) {
|
||||
subsamplingImageView.setBitmapDecoderFactory(new AttachmentBitmapDecoderFactory(masterSecret));
|
||||
subsamplingImageView.setRegionDecoderFactory(new AttachmentRegionDecoderFactory(masterSecret));
|
||||
private void setSubsamplingImageViewUri(@NonNull Uri uri) {
|
||||
subsamplingImageView.setBitmapDecoderFactory(new AttachmentBitmapDecoderFactory());
|
||||
subsamplingImageView.setRegionDecoderFactory(new AttachmentRegionDecoderFactory());
|
||||
|
||||
subsamplingImageView.setVisibility(View.VISIBLE);
|
||||
photoView.setVisibility(View.GONE);
|
||||
@ -124,31 +122,16 @@ public class ZoomingImageView extends FrameLayout {
|
||||
}
|
||||
|
||||
private static class AttachmentBitmapDecoderFactory implements DecoderFactory<AttachmentBitmapDecoder> {
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
private AttachmentBitmapDecoderFactory(MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachmentBitmapDecoder make() throws IllegalAccessException, InstantiationException {
|
||||
return new AttachmentBitmapDecoder(masterSecret);
|
||||
return new AttachmentBitmapDecoder();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class AttachmentRegionDecoderFactory implements DecoderFactory<AttachmentRegionDecoder> {
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
private AttachmentRegionDecoderFactory(@NonNull MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachmentRegionDecoder make() throws IllegalAccessException, InstantiationException {
|
||||
return new AttachmentRegionDecoder(masterSecret);
|
||||
return new AttachmentRegionDecoder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ public class SystemSmsImportReminder extends Reminder {
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(context, ApplicationMigrationService.class);
|
||||
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
context.startService(intent);
|
||||
|
||||
Intent nextIntent = new Intent(context, ConversationListActivity.class);
|
||||
|
@ -4,26 +4,18 @@ package org.thoughtcrime.securesms.components.subsampling;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder;
|
||||
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageDecoder;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class AttachmentBitmapDecoder implements ImageDecoder{
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
public AttachmentBitmapDecoder(@NonNull MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
public AttachmentBitmapDecoder() {}
|
||||
|
||||
@Override
|
||||
public Bitmap decode(Context context, Uri uri) throws Exception {
|
||||
@ -31,7 +23,7 @@ public class AttachmentBitmapDecoder implements ImageDecoder{
|
||||
return new SkiaImageDecoder().decode(context, uri);
|
||||
}
|
||||
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri);
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
|
||||
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
|
@ -8,13 +8,11 @@ import android.graphics.BitmapRegionDecoder;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder;
|
||||
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
|
||||
import java.io.InputStream;
|
||||
@ -23,16 +21,10 @@ public class AttachmentRegionDecoder implements ImageRegionDecoder {
|
||||
|
||||
private static final String TAG = AttachmentRegionDecoder.class.getName();
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
private SkiaImageRegionDecoder passthrough;
|
||||
|
||||
private BitmapRegionDecoder bitmapRegionDecoder;
|
||||
|
||||
public AttachmentRegionDecoder(@NonNull MasterSecret masterSecret) {
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point init(Context context, Uri uri) throws Exception {
|
||||
Log.w(TAG, "Init!");
|
||||
@ -41,7 +33,7 @@ public class AttachmentRegionDecoder implements ImageRegionDecoder {
|
||||
return passthrough.init(context, uri);
|
||||
}
|
||||
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri);
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
|
||||
|
||||
this.bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
|
||||
inputStream.close();
|
||||
|
@ -28,16 +28,14 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -61,17 +59,14 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
ContactsDatabase.CONTACT_TYPE_COLUMN};
|
||||
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
private final String filter;
|
||||
private final int mode;
|
||||
private final boolean recents;
|
||||
|
||||
public ContactsCursorLoader(@NonNull Context context, @NonNull MasterSecret masterSecret,
|
||||
int mode, String filter, boolean recents)
|
||||
public ContactsCursorLoader(@NonNull Context context, int mode, String filter, boolean recents)
|
||||
{
|
||||
super(context);
|
||||
|
||||
this.masterSecret = masterSecret;
|
||||
this.filter = filter;
|
||||
this.mode = mode;
|
||||
this.recents = recents;
|
||||
@ -88,7 +83,7 @@ public class ContactsCursorLoader extends CursorLoader {
|
||||
MatrixCursor synthesizedContacts = new MatrixCursor(CONTACT_PROJECTION);
|
||||
synthesizedContacts.addRow(new Object[] {getContext().getString(R.string.ContactsCursorLoader_recent_chats), "", ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, "", ContactsDatabase.DIVIDER_TYPE});
|
||||
|
||||
ThreadDatabase.Reader reader = threadDatabase.readerFor(recentConversations, new MasterCipher(masterSecret));
|
||||
ThreadDatabase.Reader reader = threadDatabase.readerFor(recentConversations);
|
||||
|
||||
ThreadRecord threadRecord;
|
||||
|
||||
|
@ -8,7 +8,6 @@ import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
@ -30,7 +29,7 @@ public class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
||||
if (TextSecurePreferences.isPushRegistered(getContext())) {
|
||||
try {
|
||||
DirectoryHelper.refreshDirectory(getContext(), KeyCachingService.getMasterSecret(getContext()), true);
|
||||
DirectoryHelper.refreshDirectory(getContext(), true);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
115
src/org/thoughtcrime/securesms/crypto/AttachmentSecret.java
Normal file
115
src/org/thoughtcrime/securesms/crypto/AttachmentSecret.java
Normal file
@ -0,0 +1,115 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Encapsulates the key material used to encrypt attachments on disk.
|
||||
*
|
||||
* There are two logical pieces of material, a deprecated set of keys used to encrypt
|
||||
* legacy attachments, and a key that is used to encrypt attachments going forward.
|
||||
*/
|
||||
public class AttachmentSecret {
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] classicCipherKey;
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] classicMacKey;
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] modernKey;
|
||||
|
||||
public AttachmentSecret(byte[] classicCipherKey, byte[] classicMacKey, byte[] modernKey)
|
||||
{
|
||||
this.classicCipherKey = classicCipherKey;
|
||||
this.classicMacKey = classicMacKey;
|
||||
this.modernKey = modernKey;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public AttachmentSecret() {
|
||||
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
byte[] getClassicCipherKey() {
|
||||
return classicCipherKey;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
byte[] getClassicMacKey() {
|
||||
return classicMacKey;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
byte[] getModernKey() {
|
||||
return modernKey;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
void setClassicCipherKey(byte[] classicCipherKey) {
|
||||
this.classicCipherKey = classicCipherKey;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
void setClassicMacKey(byte[] classicMacKey) {
|
||||
this.classicMacKey = classicMacKey;
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
try {
|
||||
return JsonUtils.toJson(this);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
static AttachmentSecret fromString(@NonNull String value) {
|
||||
try {
|
||||
return JsonUtils.fromJson(value, AttachmentSecret.class);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
|
||||
@Override
|
||||
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeString(Base64.encodeToString(value, Base64.NO_WRAP | Base64.NO_PADDING));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* A provider that is responsible for creating or retrieving the AttachmentSecret model.
|
||||
*
|
||||
* On modern Android, the serialized secrets are themselves encrypted using a key that lives
|
||||
* in the system KeyStore, for whatever that is worth.
|
||||
*/
|
||||
public class AttachmentSecretProvider {
|
||||
|
||||
private static AttachmentSecretProvider provider;
|
||||
|
||||
public static synchronized AttachmentSecretProvider getInstance(@NonNull Context context) {
|
||||
if (provider == null) provider = new AttachmentSecretProvider(context.getApplicationContext());
|
||||
return provider;
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
|
||||
private AttachmentSecret attachmentSecret;
|
||||
|
||||
private AttachmentSecretProvider(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
public synchronized AttachmentSecret getOrCreateAttachmentSecret() {
|
||||
if (attachmentSecret != null) return attachmentSecret;
|
||||
|
||||
String unencryptedSecret = TextSecurePreferences.getAttachmentUnencryptedSecret(context);
|
||||
String encryptedSecret = TextSecurePreferences.getAttachmentEncryptedSecret(context);
|
||||
|
||||
if (unencryptedSecret != null) attachmentSecret = getUnencryptedAttachmentSecret(context, unencryptedSecret);
|
||||
else if (encryptedSecret != null) attachmentSecret = getEncryptedAttachmentSecret(encryptedSecret);
|
||||
else attachmentSecret = createAndStoreAttachmentSecret(context);
|
||||
|
||||
return attachmentSecret;
|
||||
}
|
||||
|
||||
public synchronized AttachmentSecret setClassicKey(@NonNull Context context, @NonNull byte[] classicCipherKey, @NonNull byte[] classicMacKey) {
|
||||
AttachmentSecret currentSecret = getOrCreateAttachmentSecret();
|
||||
currentSecret.setClassicCipherKey(classicCipherKey);
|
||||
currentSecret.setClassicMacKey(classicMacKey);
|
||||
|
||||
storeAttachmentSecret(context, attachmentSecret);
|
||||
|
||||
return attachmentSecret;
|
||||
}
|
||||
|
||||
private AttachmentSecret getUnencryptedAttachmentSecret(@NonNull Context context, @NonNull String unencryptedSecret)
|
||||
{
|
||||
AttachmentSecret attachmentSecret = AttachmentSecret.fromString(unencryptedSecret);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return attachmentSecret;
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes());
|
||||
|
||||
TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize());
|
||||
TextSecurePreferences.setAttachmentUnencryptedSecret(context, null);
|
||||
|
||||
return attachmentSecret;
|
||||
}
|
||||
}
|
||||
|
||||
private AttachmentSecret getEncryptedAttachmentSecret(@NonNull String serializedEncryptedSecret) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!");
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret);
|
||||
return AttachmentSecret.fromString(new String(KeyStoreHelper.unseal(encryptedSecret)));
|
||||
}
|
||||
}
|
||||
|
||||
private AttachmentSecret createAndStoreAttachmentSecret(@NonNull Context context) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] secret = new byte[32];
|
||||
random.nextBytes(secret);
|
||||
|
||||
AttachmentSecret attachmentSecret = new AttachmentSecret(null, null, secret);
|
||||
storeAttachmentSecret(context, attachmentSecret);
|
||||
|
||||
return attachmentSecret;
|
||||
}
|
||||
|
||||
private void storeAttachmentSecret(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes());
|
||||
TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize());
|
||||
} else {
|
||||
TextSecurePreferences.setAttachmentUnencryptedSecret(context, attachmentSecret.serialize());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.LimitedInputStream;
|
||||
@ -37,14 +38,14 @@ import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class DecryptingPartInputStream {
|
||||
public class ClassicDecryptingPartInputStream {
|
||||
|
||||
private static final String TAG = DecryptingPartInputStream.class.getSimpleName();
|
||||
private static final String TAG = ClassicDecryptingPartInputStream.class.getSimpleName();
|
||||
|
||||
private static final int IV_LENGTH = 16;
|
||||
private static final int MAC_LENGTH = 20;
|
||||
|
||||
public static InputStream createFor(MasterSecret masterSecret, File file)
|
||||
public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
@ -52,7 +53,7 @@ public class DecryptingPartInputStream {
|
||||
throw new IOException("File too short");
|
||||
}
|
||||
|
||||
verifyMac(masterSecret, file);
|
||||
verifyMac(attachmentSecret, file);
|
||||
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
byte[] ivBytes = new byte[IV_LENGTH];
|
||||
@ -60,7 +61,7 @@ public class DecryptingPartInputStream {
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||
cipher.init(Cipher.DECRYPT_MODE, masterSecret.getEncryptionKey(), iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(attachmentSecret.getClassicCipherKey(), "AES"), iv);
|
||||
|
||||
return new CipherInputStreamWrapper(new LimitedInputStream(fileStream, file.length() - MAC_LENGTH - IV_LENGTH), cipher);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
@ -68,8 +69,8 @@ public class DecryptingPartInputStream {
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyMac(MasterSecret masterSecret, File file) throws IOException {
|
||||
Mac mac = initializeMac(masterSecret.getMacKey());
|
||||
private static void verifyMac(AttachmentSecret attachmentSecret, File file) throws IOException {
|
||||
Mac mac = initializeMac(new SecretKeySpec(attachmentSecret.getClassicMacKey(), "HmacSHA1"));
|
||||
FileInputStream macStream = new FileInputStream(file);
|
||||
InputStream dataStream = new LimitedInputStream(new FileInputStream(file), file.length() - MAC_LENGTH);
|
||||
byte[] theirMac = new byte[MAC_LENGTH];
|
32
src/org/thoughtcrime/securesms/crypto/DatabaseSecret.java
Normal file
32
src/org/thoughtcrime/securesms/crypto/DatabaseSecret.java
Normal file
@ -0,0 +1,32 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class DatabaseSecret {
|
||||
|
||||
private final byte[] key;
|
||||
private final String encoded;
|
||||
|
||||
public DatabaseSecret(@NonNull byte[] key) {
|
||||
this.key = key;
|
||||
this.encoded = Hex.toStringCondensed(key);
|
||||
}
|
||||
|
||||
public DatabaseSecret(@NonNull String encoded) throws IOException {
|
||||
this.key = Hex.fromStringCondensed(encoded);
|
||||
this.encoded = encoded;
|
||||
}
|
||||
|
||||
public String asString() {
|
||||
return encoded;
|
||||
}
|
||||
|
||||
public byte[] asBytes() {
|
||||
return key;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class DatabaseSecretProvider {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = DatabaseSecretProvider.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
|
||||
public DatabaseSecretProvider(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
public DatabaseSecret getOrCreateDatabaseSecret() {
|
||||
String unencryptedSecret = TextSecurePreferences.getDatabaseUnencryptedSecret(context);
|
||||
String encryptedSecret = TextSecurePreferences.getDatabaseEncryptedSecret(context);
|
||||
|
||||
if (unencryptedSecret != null) return getUnencryptedDatabaseSecret(context, unencryptedSecret);
|
||||
else if (encryptedSecret != null) return getEncryptedDatabaseSecret(encryptedSecret);
|
||||
else return createAndStoreDatabaseSecret(context);
|
||||
}
|
||||
|
||||
private DatabaseSecret getUnencryptedDatabaseSecret(@NonNull Context context, @NonNull String unencryptedSecret)
|
||||
{
|
||||
try {
|
||||
DatabaseSecret databaseSecret = new DatabaseSecret(unencryptedSecret);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return databaseSecret;
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
|
||||
|
||||
TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize());
|
||||
TextSecurePreferences.setDatabaseUnencryptedSecret(context, null);
|
||||
|
||||
return databaseSecret;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseSecret getEncryptedDatabaseSecret(@NonNull String serializedEncryptedSecret) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!");
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret);
|
||||
return new DatabaseSecret(KeyStoreHelper.unseal(encryptedSecret));
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] secret = new byte[32];
|
||||
random.nextBytes(secret);
|
||||
|
||||
DatabaseSecret databaseSecret = new DatabaseSecret(secret);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
|
||||
TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize());
|
||||
} else {
|
||||
TextSecurePreferences.setDatabaseUnencryptedSecret(context, databaseSecret.asString());
|
||||
}
|
||||
|
||||
return databaseSecret;
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A class for streaming an encrypted MMS "part" to disk.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class EncryptingPartOutputStream extends FileOutputStream {
|
||||
|
||||
private Cipher cipher;
|
||||
private Mac mac;
|
||||
private boolean closed;
|
||||
|
||||
public EncryptingPartOutputStream(File file, MasterSecret masterSecret) throws FileNotFoundException {
|
||||
super(file);
|
||||
|
||||
try {
|
||||
mac = initializeMac(masterSecret.getMacKey());
|
||||
cipher = initializeCipher(mac, masterSecret.getEncryptionKey());
|
||||
closed = false;
|
||||
} catch (IOException ioe) {
|
||||
Log.w("EncryptingPartOutputStream", ioe);
|
||||
throw new FileNotFoundException("Couldn't write IV");
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer) throws IOException {
|
||||
this.write(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int offset, int length) throws IOException {
|
||||
byte[] encryptedBuffer = cipher.update(buffer, offset, length);
|
||||
|
||||
if (encryptedBuffer != null) {
|
||||
mac.update(encryptedBuffer);
|
||||
super.write(encryptedBuffer, 0, encryptedBuffer.length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
if (!closed) {
|
||||
byte[] encryptedRemainder = cipher.doFinal();
|
||||
mac.update(encryptedRemainder);
|
||||
|
||||
byte[] macBytes = mac.doFinal();
|
||||
|
||||
super.write(encryptedRemainder, 0, encryptedRemainder.length);
|
||||
super.write(macBytes, 0, macBytes.length);
|
||||
|
||||
closed = true;
|
||||
}
|
||||
|
||||
super.close();
|
||||
} catch (BadPaddingException bpe) {
|
||||
throw new AssertionError(bpe);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Mac initializeMac(SecretKeySpec key) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Mac hmac = Mac.getInstance("HmacSHA1");
|
||||
hmac.init(key);
|
||||
|
||||
return hmac;
|
||||
}
|
||||
|
||||
private Cipher initializeCipher(Mac mac, SecretKeySpec key) throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
|
||||
byte[] ivBytes = cipher.getIV();
|
||||
mac.update(ivBytes);
|
||||
super.write(ivBytes, 0, ivBytes.length);
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
}
|
180
src/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java
Normal file
180
src/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java
Normal file
@ -0,0 +1,180 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.os.Build;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.UnrecoverableEntryException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
|
||||
public class KeyStoreHelper {
|
||||
|
||||
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
|
||||
private static final String KEY_ALIAS = "SignalSecret";
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
public static SealedData seal(@NonNull byte[] input) {
|
||||
SecretKey secretKey = getOrCreateKeyStoreEntry();
|
||||
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
|
||||
byte[] iv = cipher.getIV();
|
||||
byte[] data = cipher.doFinal(input);
|
||||
|
||||
return new SealedData(iv, data);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
public static byte[] unseal(@NonNull SealedData sealedData) {
|
||||
SecretKey secretKey = getKeyStoreEntry();
|
||||
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv));
|
||||
|
||||
return cipher.doFinal(sealedData.data);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private static SecretKey getOrCreateKeyStoreEntry() {
|
||||
if (hasKeyStoreEntry()) return getKeyStoreEntry();
|
||||
else return createKeyStoreEntry();
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private static SecretKey createKeyStoreEntry() {
|
||||
try {
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
|
||||
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.build();
|
||||
|
||||
keyGenerator.init(keyGenParameterSpec);
|
||||
|
||||
return keyGenerator.generateKey();
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private static SecretKey getKeyStoreEntry() {
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
|
||||
keyStore.load(null);
|
||||
|
||||
return ((KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null)).getSecretKey();
|
||||
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableEntryException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private static boolean hasKeyStoreEntry() {
|
||||
try {
|
||||
KeyStore ks = KeyStore.getInstance(ANDROID_KEY_STORE);
|
||||
ks.load(null);
|
||||
|
||||
return ks.containsAlias(KEY_ALIAS) && ks.entryInstanceOf(KEY_ALIAS, KeyStore.SecretKeyEntry.class);
|
||||
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SealedData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = SealedData.class.getSimpleName();
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] iv;
|
||||
|
||||
@JsonProperty
|
||||
@JsonSerialize(using = ByteArraySerializer.class)
|
||||
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
||||
private byte[] data;
|
||||
|
||||
SealedData(@NonNull byte[] iv, @NonNull byte[] data) {
|
||||
this.iv = iv;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public SealedData() {}
|
||||
|
||||
public String serialize() {
|
||||
try {
|
||||
return JsonUtils.toJson(this);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
static SealedData fromString(@NonNull String value) {
|
||||
try {
|
||||
return JsonUtils.fromJson(value, SealedData.class);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
|
||||
@Override
|
||||
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeString(Base64.encodeToString(value, Base64.NO_WRAP | Base64.NO_PADDING));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
|
||||
|
||||
@Override
|
||||
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MediaKey {
|
||||
|
||||
public static String getEncrypted(@NonNull MasterSecretUnion masterSecret, @NonNull byte[] key) {
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
return Base64.encodeBytes(new MasterCipher(masterSecret.getMasterSecret().get()).encryptBytes(key));
|
||||
} else {
|
||||
return "?ASYNC-" + Base64.encodeBytes(new AsymmetricMasterCipher(masterSecret.getAsymmetricMasterSecret().get()).encryptBytes(key));
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getDecrypted(@NonNull MasterSecret masterSecret,
|
||||
@NonNull AsymmetricMasterSecret asymmetricMasterSecret,
|
||||
@NonNull String encodedKey)
|
||||
throws IOException, InvalidMessageException
|
||||
{
|
||||
if (encodedKey.startsWith("?ASYNC-")) {
|
||||
return new AsymmetricMasterCipher(asymmetricMasterSecret).decryptBytes(Base64.decode(encodedKey.substring("?ASYNC-".length())));
|
||||
} else {
|
||||
return new MasterCipher(masterSecret).decryptBytes(Base64.decode(encodedKey));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class ModernDecryptingPartInputStream {
|
||||
|
||||
public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull byte[] random, @NonNull File file)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256"));
|
||||
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
byte[] iv = new byte[16];
|
||||
byte[] key = mac.doFinal(random);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
|
||||
|
||||
return new CipherInputStream(fileInputStream, cipher);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Constructs an OutputStream that encrypts data written to it with the AttachmentSecret provided.
|
||||
*
|
||||
* The on-disk format is very simple, and intentionally no longer includes authentication.
|
||||
*/
|
||||
public class ModernEncryptingPartOutputStream {
|
||||
|
||||
public static OutputStream createFor(@NonNull AttachmentSecret attachmentSecret, byte[] random, File file)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(attachmentSecret.getModernKey(), "HmacSHA256"));
|
||||
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
byte[] iv = new byte[16];
|
||||
byte[] key = mac.doFinal(random);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
|
||||
|
||||
return new CipherOutputStream(fileOutputStream, cipher);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -15,12 +15,12 @@ import java.util.List;
|
||||
|
||||
public class SessionUtil {
|
||||
|
||||
public static boolean hasSession(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
return hasSession(context, masterSecret, recipient.getAddress());
|
||||
public static boolean hasSession(Context context, Recipient recipient) {
|
||||
return hasSession(context, recipient.getAddress());
|
||||
}
|
||||
|
||||
public static boolean hasSession(Context context, MasterSecret masterSecret, @NonNull Address address) {
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
public static boolean hasSession(Context context, @NonNull Address address) {
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, null);
|
||||
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(address.serialize(), SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||
|
||||
return sessionStore.containsSession(axolotlAddress);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -16,11 +16,10 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.net.Uri;
|
||||
@ -30,17 +29,17 @@ import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.mms.MediaStream;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
@ -48,13 +47,13 @@ import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.video.EncryptedMediaDataSource;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
@ -83,6 +82,8 @@ public class AttachmentDatabase extends Database {
|
||||
static final String DIGEST = "digest";
|
||||
static final String VOICE_NOTE = "voice_note";
|
||||
public static final String FAST_PREFLIGHT_ID = "fast_preflight_id";
|
||||
static final String DATA_RANDOM = "data_random";
|
||||
static final String THUMBNAIL_RANDOM = "thumbnail_random";
|
||||
|
||||
public static final int TRANSFER_PROGRESS_DONE = 0;
|
||||
public static final int TRANSFER_PROGRESS_STARTED = 1;
|
||||
@ -95,7 +96,8 @@ public class AttachmentDatabase extends Database {
|
||||
MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION,
|
||||
CONTENT_LOCATION, DATA, THUMBNAIL, TRANSFER_STATE,
|
||||
SIZE, FILE_NAME, THUMBNAIL, THUMBNAIL_ASPECT_RATIO,
|
||||
UNIQUE_ID, DIGEST, FAST_PREFLIGHT_ID, VOICE_NOTE};
|
||||
UNIQUE_ID, DIGEST, FAST_PREFLIGHT_ID, VOICE_NOTE,
|
||||
DATA_RANDOM, THUMBNAIL_RANDOM};
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
|
||||
MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " +
|
||||
@ -106,7 +108,7 @@ public class AttachmentDatabase extends Database {
|
||||
TRANSFER_STATE + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " +
|
||||
FILE_NAME + " TEXT, " + THUMBNAIL + " TEXT, " + THUMBNAIL_ASPECT_RATIO + " REAL, " +
|
||||
UNIQUE_ID + " INTEGER NOT NULL, " + DIGEST + " BLOB, " + FAST_PREFLIGHT_ID + " TEXT, " +
|
||||
VOICE_NOTE + " INTEGER DEFAULT 0);";
|
||||
VOICE_NOTE + " INTEGER DEFAULT 0, " + DATA_RANDOM + " BLOB, " + THUMBNAIL_RANDOM + " BLOB);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
|
||||
@ -115,31 +117,34 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
private final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor();
|
||||
|
||||
public AttachmentDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
private final AttachmentSecret attachmentSecret;
|
||||
|
||||
public AttachmentDatabase(Context context, SQLCipherOpenHelper databaseHelper, AttachmentSecret attachmentSecret) {
|
||||
super(context, databaseHelper);
|
||||
this.attachmentSecret = attachmentSecret;
|
||||
}
|
||||
|
||||
public @NonNull InputStream getAttachmentStream(MasterSecret masterSecret, AttachmentId attachmentId)
|
||||
public @NonNull InputStream getAttachmentStream(AttachmentId attachmentId)
|
||||
throws IOException
|
||||
{
|
||||
InputStream dataStream = getDataStream(masterSecret, attachmentId, DATA);
|
||||
InputStream dataStream = getDataStream(attachmentId, DATA);
|
||||
|
||||
if (dataStream == null) throw new IOException("No stream for: " + attachmentId);
|
||||
else return dataStream;
|
||||
}
|
||||
|
||||
public @NonNull InputStream getThumbnailStream(@NonNull MasterSecret masterSecret, @NonNull AttachmentId attachmentId)
|
||||
public @NonNull InputStream getThumbnailStream(@NonNull AttachmentId attachmentId)
|
||||
throws IOException
|
||||
{
|
||||
Log.w(TAG, "getThumbnailStream(" + attachmentId + ")");
|
||||
InputStream dataStream = getDataStream(masterSecret, attachmentId, THUMBNAIL);
|
||||
InputStream dataStream = getDataStream(attachmentId, THUMBNAIL);
|
||||
|
||||
if (dataStream != null) {
|
||||
return dataStream;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream generatedStream = thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, attachmentId)).get();
|
||||
InputStream generatedStream = thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId)).get();
|
||||
|
||||
if (generatedStream == null) throw new FileNotFoundException("No thumbnail stream available: " + attachmentId);
|
||||
else return generatedStream;
|
||||
@ -162,7 +167,7 @@ public class AttachmentDatabase extends Database {
|
||||
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId));
|
||||
}
|
||||
|
||||
public @Nullable DatabaseAttachment getAttachment(@Nullable MasterSecret masterSecret, AttachmentId attachmentId)
|
||||
public @Nullable DatabaseAttachment getAttachment(@NonNull AttachmentId attachmentId)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
@ -170,7 +175,7 @@ public class AttachmentDatabase extends Database {
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, PROJECTION, PART_ID_WHERE, attachmentId.toStrings(), null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) return getAttachment(masterSecret, cursor);
|
||||
if (cursor != null && cursor.moveToFirst()) return getAttachment(cursor);
|
||||
else return null;
|
||||
|
||||
} finally {
|
||||
@ -179,7 +184,7 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull List<DatabaseAttachment> getAttachmentsForMessage(@Nullable MasterSecret masterSecret, long mmsId) {
|
||||
public @NonNull List<DatabaseAttachment> getAttachmentsForMessage(long mmsId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
List<DatabaseAttachment> results = new LinkedList<>();
|
||||
Cursor cursor = null;
|
||||
@ -189,7 +194,7 @@ public class AttachmentDatabase extends Database {
|
||||
null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
results.add(getAttachment(masterSecret, cursor));
|
||||
results.add(getAttachment(cursor));
|
||||
}
|
||||
|
||||
return results;
|
||||
@ -199,7 +204,7 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull List<DatabaseAttachment> getPendingAttachments(@NonNull MasterSecret masterSecret) {
|
||||
public @NonNull List<DatabaseAttachment> getPendingAttachments() {
|
||||
final SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
final List<DatabaseAttachment> attachments = new LinkedList<>();
|
||||
|
||||
@ -207,7 +212,7 @@ public class AttachmentDatabase extends Database {
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, PROJECTION, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null);
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
attachments.add(getAttachment(masterSecret, cursor));
|
||||
attachments.add(getAttachment(cursor));
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
@ -217,7 +222,7 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public void deleteAttachmentsForMessage(long mmsId) {
|
||||
void deleteAttachmentsForMessage(long mmsId) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
@ -246,7 +251,7 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public void deleteAllAttachments() {
|
||||
void deleteAllAttachments() {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
database.delete(TABLE_NAME, null, null);
|
||||
|
||||
@ -258,17 +263,16 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public long insertAttachmentsForPlaceholder(@NonNull MasterSecret masterSecret, long mmsId,
|
||||
@NonNull AttachmentId attachmentId,
|
||||
@NonNull InputStream inputStream)
|
||||
public void insertAttachmentsForPlaceholder(long mmsId, @NonNull AttachmentId attachmentId, @NonNull InputStream inputStream)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Pair<File, Long> partData = setAttachmentData(masterSecret, inputStream);
|
||||
ContentValues values = new ContentValues();
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
DataInfo dataInfo = setAttachmentData(inputStream);
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(DATA, partData.first.getAbsolutePath());
|
||||
values.put(SIZE, partData.second);
|
||||
values.put(DATA, dataInfo.file.getAbsolutePath());
|
||||
values.put(SIZE, dataInfo.length);
|
||||
values.put(DATA_RANDOM, dataInfo.random);
|
||||
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
|
||||
values.put(CONTENT_LOCATION, (String)null);
|
||||
values.put(CONTENT_DISPOSITION, (String)null);
|
||||
@ -278,47 +282,44 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
if (database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()) == 0) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
partData.first.delete();
|
||||
dataInfo.file.delete();
|
||||
} else {
|
||||
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId));
|
||||
notifyConversationListListeners();
|
||||
}
|
||||
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, attachmentId));
|
||||
return partData.second;
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId));
|
||||
}
|
||||
|
||||
void insertAttachmentsForMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
long mmsId,
|
||||
@NonNull List<Attachment> attachments)
|
||||
void insertAttachmentsForMessage(long mmsId, @NonNull List<Attachment> attachments)
|
||||
throws MmsException
|
||||
{
|
||||
Log.w(TAG, "insertParts(" + attachments.size() + ")");
|
||||
|
||||
for (Attachment attachment : attachments) {
|
||||
AttachmentId attachmentId = insertAttachment(masterSecret, mmsId, attachment);
|
||||
AttachmentId attachmentId = insertAttachment(mmsId, attachment);
|
||||
Log.w(TAG, "Inserted attachment at ID: " + attachmentId);
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull Attachment updateAttachmentData(@NonNull MasterSecret masterSecret,
|
||||
@NonNull Attachment attachment,
|
||||
public @NonNull Attachment updateAttachmentData(@NonNull Attachment attachment,
|
||||
@NonNull MediaStream mediaStream)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
DatabaseAttachment databaseAttachment = (DatabaseAttachment) attachment;
|
||||
File dataFile = getAttachmentDataFile(databaseAttachment.getAttachmentId(), DATA);
|
||||
DataInfo dataInfo = getAttachmentDataFileInfo(databaseAttachment.getAttachmentId(), DATA);
|
||||
|
||||
if (dataFile == null) {
|
||||
if (dataInfo == null) {
|
||||
throw new MmsException("No attachment data found!");
|
||||
}
|
||||
|
||||
long dataSize = setAttachmentData(masterSecret, dataFile, mediaStream.getStream());
|
||||
dataInfo = setAttachmentData(dataInfo.file, mediaStream.getStream());
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(SIZE, dataSize);
|
||||
contentValues.put(SIZE, dataInfo.length);
|
||||
contentValues.put(CONTENT_TYPE, mediaStream.getMimeType());
|
||||
contentValues.put(DATA_RANDOM, dataInfo.random);
|
||||
|
||||
database.update(TABLE_NAME, contentValues, PART_ID_WHERE, databaseAttachment.getAttachmentId().toStrings());
|
||||
|
||||
@ -328,7 +329,7 @@ public class AttachmentDatabase extends Database {
|
||||
databaseAttachment.hasThumbnail(),
|
||||
mediaStream.getMimeType(),
|
||||
databaseAttachment.getTransferState(),
|
||||
dataSize,
|
||||
dataInfo.length,
|
||||
databaseAttachment.getFileName(),
|
||||
databaseAttachment.getLocation(),
|
||||
databaseAttachment.getKey(),
|
||||
@ -339,16 +340,11 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
|
||||
|
||||
public void updateAttachmentFileName(@NonNull MasterSecret masterSecret,
|
||||
@NonNull AttachmentId attachmentId,
|
||||
public void updateAttachmentFileName(@NonNull AttachmentId attachmentId,
|
||||
@Nullable String fileName)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
if (fileName != null) {
|
||||
fileName = new MasterCipher(masterSecret).encryptBody(fileName);
|
||||
}
|
||||
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(FILE_NAME, fileName);
|
||||
|
||||
@ -382,28 +378,43 @@ public class AttachmentDatabase extends Database {
|
||||
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
protected @Nullable InputStream getDataStream(MasterSecret masterSecret, AttachmentId attachmentId, String dataType)
|
||||
protected @Nullable InputStream getDataStream(AttachmentId attachmentId, String dataType)
|
||||
{
|
||||
File dataFile = getAttachmentDataFile(attachmentId, dataType);
|
||||
DataInfo dataInfo = getAttachmentDataFileInfo(attachmentId, dataType);
|
||||
|
||||
if (dataInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (dataFile != null) return DecryptingPartInputStream.createFor(masterSecret, dataFile);
|
||||
else return null;
|
||||
if (dataInfo.random != null && dataInfo.random.length == 32) {
|
||||
return ModernDecryptingPartInputStream.createFor(attachmentSecret, dataInfo.random, dataInfo.file);
|
||||
} else {
|
||||
return ClassicDecryptingPartInputStream.createFor(attachmentSecret, dataInfo.file);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable File getAttachmentDataFile(@NonNull AttachmentId attachmentId,
|
||||
@NonNull String dataType)
|
||||
private @Nullable DataInfo getAttachmentDataFileInfo(@NonNull AttachmentId attachmentId, @NonNull String dataType)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
String randomColumn;
|
||||
|
||||
switch (dataType) {
|
||||
case DATA: randomColumn = DATA_RANDOM; break;
|
||||
case THUMBNAIL: randomColumn = THUMBNAIL_RANDOM; break;
|
||||
default:throw new AssertionError("Unknown data type: " + dataType);
|
||||
}
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, new String[]{dataType}, PART_ID_WHERE, attachmentId.toStrings(),
|
||||
cursor = database.query(TABLE_NAME, new String[]{dataType, SIZE, randomColumn}, PART_ID_WHERE, attachmentId.toStrings(),
|
||||
null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
@ -411,7 +422,9 @@ public class AttachmentDatabase extends Database {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new File(cursor.getString(0));
|
||||
return new DataInfo(new File(cursor.getString(0)),
|
||||
cursor.getLong(1),
|
||||
cursor.getBlob(2));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -422,57 +435,46 @@ public class AttachmentDatabase extends Database {
|
||||
|
||||
}
|
||||
|
||||
private @NonNull Pair<File, Long> setAttachmentData(@NonNull MasterSecret masterSecret,
|
||||
@NonNull Uri uri)
|
||||
private @NonNull DataInfo setAttachmentData(@NonNull Uri uri)
|
||||
throws MmsException
|
||||
{
|
||||
try {
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri);
|
||||
return setAttachmentData(masterSecret, inputStream);
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
|
||||
return setAttachmentData(inputStream);
|
||||
} catch (IOException e) {
|
||||
throw new MmsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull Pair<File, Long> setAttachmentData(@NonNull MasterSecret masterSecret,
|
||||
@NonNull InputStream in)
|
||||
private @NonNull DataInfo setAttachmentData(@NonNull InputStream in)
|
||||
throws MmsException
|
||||
{
|
||||
try {
|
||||
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
|
||||
File dataFile = File.createTempFile("part", ".mms", partsDirectory);
|
||||
|
||||
return new Pair<>(dataFile, setAttachmentData(masterSecret, dataFile, in));
|
||||
return setAttachmentData(dataFile, in);
|
||||
} catch (IOException e) {
|
||||
throw new MmsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private long setAttachmentData(@NonNull MasterSecret masterSecret,
|
||||
@NonNull File destination,
|
||||
@NonNull InputStream in)
|
||||
private @NonNull DataInfo setAttachmentData(@NonNull File destination, @NonNull InputStream in)
|
||||
throws MmsException
|
||||
{
|
||||
try {
|
||||
OutputStream out = new EncryptingPartOutputStream(destination, masterSecret);
|
||||
return Util.copy(in, out);
|
||||
byte[] random = new byte[32];
|
||||
new SecureRandom().nextBytes(random);
|
||||
|
||||
OutputStream out = ModernEncryptingPartOutputStream.createFor(attachmentSecret, random, destination);
|
||||
long length = Util.copy(in, out);
|
||||
|
||||
return new DataInfo(destination, length, random);
|
||||
} catch (IOException e) {
|
||||
throw new MmsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseAttachment getAttachment(@Nullable MasterSecret masterSecret, Cursor cursor) {
|
||||
String encryptedFileName = cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME));
|
||||
String fileName = null;
|
||||
|
||||
if (masterSecret != null && !TextUtils.isEmpty(encryptedFileName)) {
|
||||
try {
|
||||
fileName = new MasterCipher(masterSecret).decryptBody(encryptedFileName);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseAttachment getAttachment(@NonNull Cursor cursor) {
|
||||
return new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ATTACHMENT_ID_ALIAS)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
|
||||
@ -481,7 +483,7 @@ public class AttachmentDatabase extends Database {
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
|
||||
fileName,
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(NAME)),
|
||||
@ -491,23 +493,18 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
|
||||
|
||||
private AttachmentId insertAttachment(MasterSecretUnion masterSecret, long mmsId, Attachment attachment)
|
||||
private AttachmentId insertAttachment(long mmsId, Attachment attachment)
|
||||
throws MmsException
|
||||
{
|
||||
Log.w(TAG, "Inserting attachment for mms id: " + mmsId);
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Pair<File, Long> partData = null;
|
||||
long uniqueId = System.currentTimeMillis();
|
||||
String fileName = null;
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
DataInfo dataInfo = null;
|
||||
long uniqueId = System.currentTimeMillis();
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent() && attachment.getDataUri() != null) {
|
||||
partData = setAttachmentData(masterSecret.getMasterSecret().get(), attachment.getDataUri());
|
||||
Log.w(TAG, "Wrote part to file: " + partData.first.getAbsolutePath());
|
||||
}
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent() && !TextUtils.isEmpty(attachment.getFileName())) {
|
||||
fileName = new MasterCipher(masterSecret.getMasterSecret().get()).encryptBody(attachment.getFileName());
|
||||
if (attachment.getDataUri() != null) {
|
||||
dataInfo = setAttachmentData(attachment.getDataUri());
|
||||
Log.w(TAG, "Wrote part to file: " + dataInfo.file.getAbsolutePath());
|
||||
}
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@ -519,33 +516,34 @@ public class AttachmentDatabase extends Database {
|
||||
contentValues.put(DIGEST, attachment.getDigest());
|
||||
contentValues.put(CONTENT_DISPOSITION, attachment.getKey());
|
||||
contentValues.put(NAME, attachment.getRelay());
|
||||
contentValues.put(FILE_NAME, fileName);
|
||||
contentValues.put(FILE_NAME, attachment.getFileName());
|
||||
contentValues.put(SIZE, attachment.getSize());
|
||||
contentValues.put(FAST_PREFLIGHT_ID, attachment.getFastPreflightId());
|
||||
contentValues.put(VOICE_NOTE, attachment.isVoiceNote() ? 1 : 0);
|
||||
|
||||
if (partData != null) {
|
||||
contentValues.put(DATA, partData.first.getAbsolutePath());
|
||||
contentValues.put(SIZE, partData.second);
|
||||
if (dataInfo != null) {
|
||||
contentValues.put(DATA, dataInfo.file.getAbsolutePath());
|
||||
contentValues.put(SIZE, dataInfo.length);
|
||||
contentValues.put(DATA_RANDOM, dataInfo.random);
|
||||
}
|
||||
|
||||
long rowId = database.insert(TABLE_NAME, null, contentValues);
|
||||
AttachmentId attachmentId = new AttachmentId(rowId, uniqueId);
|
||||
|
||||
if (partData != null) {
|
||||
if (dataInfo != null) {
|
||||
if (MediaUtil.hasVideoThumbnail(attachment.getDataUri())) {
|
||||
Bitmap bitmap = MediaUtil.getVideoThumbnail(context, attachment.getDataUri());
|
||||
|
||||
if (bitmap != null) {
|
||||
ThumbnailData thumbnailData = new ThumbnailData(bitmap);
|
||||
updateAttachmentThumbnail(masterSecret.getMasterSecret().get(), attachmentId, thumbnailData.toDataStream(), thumbnailData.getAspectRatio());
|
||||
updateAttachmentThumbnail(attachmentId, thumbnailData.toDataStream(), thumbnailData.getAspectRatio());
|
||||
} else {
|
||||
Log.w(TAG, "Retrieving video thumbnail failed, submitting thumbnail generation job...");
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId));
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId));
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Submitting thumbnail generation job...");
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId));
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(attachmentId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -553,19 +551,21 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@VisibleForTesting
|
||||
protected void updateAttachmentThumbnail(MasterSecret masterSecret, AttachmentId attachmentId, InputStream in, float aspectRatio)
|
||||
protected void updateAttachmentThumbnail(AttachmentId attachmentId, InputStream in, float aspectRatio)
|
||||
throws MmsException
|
||||
{
|
||||
Log.w(TAG, "updating part thumbnail for #" + attachmentId);
|
||||
|
||||
Pair<File, Long> thumbnailFile = setAttachmentData(masterSecret, in);
|
||||
DataInfo thumbnailFile = setAttachmentData(in);
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
ContentValues values = new ContentValues(2);
|
||||
|
||||
values.put(THUMBNAIL, thumbnailFile.first.getAbsolutePath());
|
||||
values.put(THUMBNAIL, thumbnailFile.file.getAbsolutePath());
|
||||
values.put(THUMBNAIL_ASPECT_RATIO, aspectRatio);
|
||||
values.put(THUMBNAIL_RANDOM, thumbnailFile.random);
|
||||
|
||||
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
|
||||
|
||||
@ -584,24 +584,22 @@ public class AttachmentDatabase extends Database {
|
||||
@VisibleForTesting
|
||||
class ThumbnailFetchCallable implements Callable<InputStream> {
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
private final AttachmentId attachmentId;
|
||||
|
||||
ThumbnailFetchCallable(MasterSecret masterSecret, AttachmentId attachmentId) {
|
||||
this.masterSecret = masterSecret;
|
||||
ThumbnailFetchCallable(AttachmentId attachmentId) {
|
||||
this.attachmentId = attachmentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable InputStream call() throws Exception {
|
||||
Log.w(TAG, "Executing thumbnail job...");
|
||||
final InputStream stream = getDataStream(masterSecret, attachmentId, THUMBNAIL);
|
||||
final InputStream stream = getDataStream(attachmentId, THUMBNAIL);
|
||||
|
||||
if (stream != null) {
|
||||
return stream;
|
||||
}
|
||||
|
||||
DatabaseAttachment attachment = getAttachment(masterSecret, attachmentId);
|
||||
DatabaseAttachment attachment = getAttachment(attachmentId);
|
||||
|
||||
if (attachment == null || !attachment.hasData()) {
|
||||
return null;
|
||||
@ -610,34 +608,35 @@ public class AttachmentDatabase extends Database {
|
||||
ThumbnailData data;
|
||||
|
||||
if (MediaUtil.isVideoType(attachment.getContentType())) {
|
||||
data = generateVideoThumbnail(masterSecret, attachmentId);
|
||||
data = generateVideoThumbnail(attachmentId);
|
||||
} else{
|
||||
data = MediaUtil.generateThumbnail(context, masterSecret, attachment.getContentType(), attachment.getDataUri());
|
||||
data = MediaUtil.generateThumbnail(context, attachment.getContentType(), attachment.getDataUri());
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
updateAttachmentThumbnail(masterSecret, attachmentId, data.toDataStream(), data.getAspectRatio());
|
||||
updateAttachmentThumbnail(attachmentId, data.toDataStream(), data.getAspectRatio());
|
||||
|
||||
return getDataStream(masterSecret, attachmentId, THUMBNAIL);
|
||||
return getDataStream(attachmentId, THUMBNAIL);
|
||||
}
|
||||
|
||||
private ThumbnailData generateVideoThumbnail(MasterSecret masterSecret, AttachmentId attachmentId) {
|
||||
@SuppressLint("NewApi")
|
||||
private ThumbnailData generateVideoThumbnail(AttachmentId attachmentId) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
Log.w(TAG, "Video thumbnails not supported...");
|
||||
return null;
|
||||
}
|
||||
|
||||
File mediaFile = getAttachmentDataFile(attachmentId, DATA);
|
||||
DataInfo dataInfo = getAttachmentDataFileInfo(attachmentId, DATA);
|
||||
|
||||
if (mediaFile == null) {
|
||||
if (dataInfo == null) {
|
||||
Log.w(TAG, "No data file found for video thumbnail...");
|
||||
return null;
|
||||
}
|
||||
|
||||
EncryptedMediaDataSource dataSource = new EncryptedMediaDataSource(masterSecret, mediaFile);
|
||||
EncryptedMediaDataSource dataSource = new EncryptedMediaDataSource(attachmentSecret, dataInfo.file, dataInfo.random, dataInfo.length);
|
||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
retriever.setDataSource(dataSource);
|
||||
|
||||
@ -647,4 +646,16 @@ public class AttachmentDatabase extends Database {
|
||||
return new ThumbnailData(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DataInfo {
|
||||
private final File file;
|
||||
private final long length;
|
||||
private final byte[] random;
|
||||
|
||||
private DataInfo(File file, long length, byte[] random) {
|
||||
this.file = file;
|
||||
this.length = length;
|
||||
this.random = random;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,10 @@ package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class Database {
|
||||
@ -29,10 +30,10 @@ public abstract class Database {
|
||||
private static final String CONVERSATION_URI = "content://textsecure/thread/";
|
||||
private static final String CONVERSATION_LIST_URI = "content://textsecure/conversation-list";
|
||||
|
||||
protected SQLiteOpenHelper databaseHelper;
|
||||
protected final Context context;
|
||||
protected SQLCipherOpenHelper databaseHelper;
|
||||
protected final Context context;
|
||||
|
||||
public Database(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public Database(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
this.context = context;
|
||||
this.databaseHelper = databaseHelper;
|
||||
}
|
||||
@ -58,7 +59,7 @@ public abstract class Database {
|
||||
cursor.setNotificationUri(context.getContentResolver(), Uri.parse(CONVERSATION_LIST_URI));
|
||||
}
|
||||
|
||||
public void reset(SQLiteOpenHelper databaseHelper) {
|
||||
public void reset(SQLCipherOpenHelper databaseHelper) {
|
||||
this.databaseHelper = databaseHelper;
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,15 +3,13 @@ package org.thoughtcrime.securesms.database;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -32,18 +30,18 @@ public class DraftDatabase extends Database {
|
||||
"CREATE INDEX IF NOT EXISTS draft_thread_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||
};
|
||||
|
||||
public DraftDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public DraftDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public void insertDrafts(MasterCipher masterCipher, long threadId, List<Draft> drafts) {
|
||||
public void insertDrafts(long threadId, List<Draft> drafts) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
|
||||
for (Draft draft : drafts) {
|
||||
ContentValues values = new ContentValues(3);
|
||||
values.put(THREAD_ID, threadId);
|
||||
values.put(DRAFT_TYPE, masterCipher.encryptBody(draft.getType()));
|
||||
values.put(DRAFT_VALUE, masterCipher.encryptBody(draft.getValue()));
|
||||
values.put(DRAFT_TYPE, draft.getType());
|
||||
values.put(DRAFT_VALUE, draft.getValue());
|
||||
|
||||
db.insert(TABLE_NAME, null, values);
|
||||
}
|
||||
@ -54,7 +52,7 @@ public class DraftDatabase extends Database {
|
||||
db.delete(TABLE_NAME, THREAD_ID + " = ?", new String[] {threadId+""});
|
||||
}
|
||||
|
||||
public void clearDrafts(Set<Long> threadIds) {
|
||||
void clearDrafts(Set<Long> threadIds) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
StringBuilder where = new StringBuilder();
|
||||
List<String> arguments = new LinkedList<>();
|
||||
@ -70,29 +68,24 @@ public class DraftDatabase extends Database {
|
||||
db.delete(TABLE_NAME, where.toString().substring(4), arguments.toArray(new String[0]));
|
||||
}
|
||||
|
||||
public void clearAllDrafts() {
|
||||
void clearAllDrafts() {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.delete(TABLE_NAME, null, null);
|
||||
}
|
||||
|
||||
public List<Draft> getDrafts(MasterCipher masterCipher, long threadId) {
|
||||
public List<Draft> getDrafts(long threadId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
List<Draft> results = new LinkedList<Draft>();
|
||||
List<Draft> results = new LinkedList<>();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = db.query(TABLE_NAME, null, THREAD_ID + " = ?", new String[] {threadId+""}, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
try {
|
||||
String encryptedType = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_TYPE));
|
||||
String encryptedValue = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_VALUE));
|
||||
String type = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_TYPE));
|
||||
String value = cursor.getString(cursor.getColumnIndexOrThrow(DRAFT_VALUE));
|
||||
|
||||
results.add(new Draft(masterCipher.decryptBody(encryptedType),
|
||||
masterCipher.decryptBody(encryptedValue)));
|
||||
} catch (InvalidMessageException ime) {
|
||||
Log.w("DraftDatabase", ime);
|
||||
}
|
||||
results.add(new Draft(type, value));
|
||||
}
|
||||
|
||||
return results;
|
||||
@ -125,7 +118,7 @@ public class DraftDatabase extends Database {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getSnippet(Context context) {
|
||||
String getSnippet(Context context) {
|
||||
switch (type) {
|
||||
case TEXT: return value;
|
||||
case IMAGE: return context.getString(R.string.DraftDatabase_Draft_image_snippet);
|
||||
@ -158,7 +151,7 @@ public class DraftDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Uri getUriSnippet(Context context) {
|
||||
public @Nullable Uri getUriSnippet() {
|
||||
Draft imageDraft = getDraftOfType(Draft.IMAGE);
|
||||
|
||||
if (imageDraft != null && imageDraft.getValue() != null) {
|
||||
|
@ -1,233 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.model.DisplayRecord;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class EncryptingSmsDatabase extends SmsDatabase {
|
||||
|
||||
private final PlaintextCache plaintextCache = new PlaintextCache();
|
||||
|
||||
public EncryptingSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
private String getAsymmetricEncryptedBody(AsymmetricMasterSecret masterSecret, String body) {
|
||||
AsymmetricMasterCipher bodyCipher = new AsymmetricMasterCipher(masterSecret);
|
||||
return bodyCipher.encryptBody(body);
|
||||
}
|
||||
|
||||
private String getEncryptedBody(MasterSecret masterSecret, String body) {
|
||||
MasterCipher bodyCipher = new MasterCipher(masterSecret);
|
||||
String ciphertext = bodyCipher.encryptBody(body);
|
||||
plaintextCache.put(ciphertext, body);
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public long insertMessageOutbox(MasterSecretUnion masterSecret, long threadId,
|
||||
OutgoingTextMessage message, boolean forceSms,
|
||||
long timestamp, InsertListener insertListener)
|
||||
{
|
||||
long type = Types.BASE_SENDING_TYPE;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
message = message.withBody(getEncryptedBody(masterSecret.getMasterSecret().get(), message.getMessageBody()));
|
||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
message = message.withBody(getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), message.getMessageBody()));
|
||||
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
return insertMessageOutbox(threadId, message, type, forceSms, timestamp, insertListener);
|
||||
}
|
||||
|
||||
public Optional<InsertResult> insertMessageInbox(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull IncomingTextMessage message)
|
||||
{
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
return insertMessageInbox(masterSecret.getMasterSecret().get(), message);
|
||||
} else {
|
||||
return insertMessageInbox(masterSecret.getAsymmetricMasterSecret().get(), message);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<InsertResult> insertMessageInbox(@NonNull MasterSecret masterSecret,
|
||||
@NonNull IncomingTextMessage message)
|
||||
{
|
||||
long type = Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
|
||||
message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody()));
|
||||
|
||||
return insertMessageInbox(message, type);
|
||||
}
|
||||
|
||||
private Optional<InsertResult> insertMessageInbox(@NonNull AsymmetricMasterSecret masterSecret,
|
||||
@NonNull IncomingTextMessage message)
|
||||
{
|
||||
long type = Types.BASE_INBOX_TYPE | Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
|
||||
message = message.withMessageBody(getAsymmetricEncryptedBody(masterSecret, message.getMessageBody()));
|
||||
|
||||
return insertMessageInbox(message, type);
|
||||
}
|
||||
|
||||
public Pair<Long, Long> updateBundleMessageBody(MasterSecretUnion masterSecret, long messageId, String body) {
|
||||
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT;
|
||||
String encryptedBody;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
encryptedBody = getEncryptedBody(masterSecret.getMasterSecret().get(), body);
|
||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
encryptedBody = getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), body);
|
||||
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
return updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK, type);
|
||||
}
|
||||
|
||||
public void updateMessageBody(MasterSecretUnion masterSecret, long messageId, String body) {
|
||||
long type;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
body = getEncryptedBody(masterSecret.getMasterSecret().get(), body);
|
||||
type = Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
body = getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), body);
|
||||
type = Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
|
||||
}
|
||||
|
||||
public Reader getMessages(MasterSecret masterSecret, int skip, int limit) {
|
||||
Cursor cursor = super.getMessages(skip, limit);
|
||||
return new DecryptingReader(masterSecret, cursor);
|
||||
}
|
||||
|
||||
public Reader getOutgoingMessages(MasterSecret masterSecret) {
|
||||
Cursor cursor = super.getOutgoingMessages();
|
||||
return new DecryptingReader(masterSecret, cursor);
|
||||
}
|
||||
|
||||
public SmsMessageRecord getMessage(MasterSecret masterSecret, long messageId) throws NoSuchMessageException {
|
||||
Cursor cursor = super.getMessage(messageId);
|
||||
DecryptingReader reader = new DecryptingReader(masterSecret, cursor);
|
||||
SmsMessageRecord record = reader.getNext();
|
||||
|
||||
reader.close();
|
||||
|
||||
if (record == null) throw new NoSuchMessageException("No message for ID: " + messageId);
|
||||
else return record;
|
||||
}
|
||||
|
||||
public Reader getDecryptInProgressMessages(MasterSecret masterSecret) {
|
||||
Cursor cursor = super.getDecryptInProgressMessages();
|
||||
return new DecryptingReader(masterSecret, cursor);
|
||||
}
|
||||
|
||||
public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
|
||||
return new DecryptingReader(masterSecret, cursor);
|
||||
}
|
||||
|
||||
public class DecryptingReader extends SmsDatabase.Reader {
|
||||
|
||||
private final MasterCipher masterCipher;
|
||||
|
||||
public DecryptingReader(MasterSecret masterSecret, Cursor cursor) {
|
||||
super(cursor);
|
||||
this.masterCipher = new MasterCipher(masterSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DisplayRecord.Body getBody(Cursor cursor) {
|
||||
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
|
||||
String ciphertext = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
|
||||
|
||||
if (ciphertext == null) {
|
||||
return new DisplayRecord.Body("", true);
|
||||
}
|
||||
|
||||
try {
|
||||
if (SmsDatabase.Types.isSymmetricEncryption(type)) {
|
||||
String plaintext = plaintextCache.get(ciphertext);
|
||||
|
||||
if (plaintext != null)
|
||||
return new DisplayRecord.Body(plaintext, true);
|
||||
|
||||
plaintext = masterCipher.decryptBody(ciphertext);
|
||||
|
||||
plaintextCache.put(ciphertext, plaintext);
|
||||
return new DisplayRecord.Body(plaintext, true);
|
||||
} else {
|
||||
return new DisplayRecord.Body(ciphertext, true);
|
||||
}
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("EncryptingSmsDatabase", e);
|
||||
return new DisplayRecord.Body(context.getString(R.string.EncryptingSmsDatabase_error_decrypting_message), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PlaintextCache {
|
||||
private static final int MAX_CACHE_SIZE = 2000;
|
||||
private static final Map<String, SoftReference<String>> decryptedBodyCache =
|
||||
Collections.synchronizedMap(new LRUCache<String, SoftReference<String>>(MAX_CACHE_SIZE));
|
||||
|
||||
public void put(String ciphertext, String plaintext) {
|
||||
decryptedBodyCache.put(ciphertext, new SoftReference<String>(plaintext));
|
||||
}
|
||||
|
||||
public String get(String ciphertext) {
|
||||
SoftReference<String> plaintextReference = decryptedBodyCache.get(ciphertext);
|
||||
|
||||
if (plaintextReference != null) {
|
||||
String plaintext = plaintextReference.get();
|
||||
|
||||
if (plaintext != null) {
|
||||
return plaintext;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,8 +5,6 @@ import android.annotation.SuppressLint;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@ -14,6 +12,9 @@ import android.text.TextUtils;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
@ -64,7 +65,7 @@ public class GroupDatabase extends Database {
|
||||
AVATAR_DIGEST + " BLOB, " +
|
||||
MMS + " INTEGER DEFAULT 0);";
|
||||
|
||||
static final String[] CREATE_INDEXS = {
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");",
|
||||
};
|
||||
|
||||
@ -75,7 +76,7 @@ public class GroupDatabase extends Database {
|
||||
|
||||
static final List<String> TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList();
|
||||
|
||||
public GroupDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public GroupDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,12 @@ package org.thoughtcrime.securesms.database;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@ -33,7 +35,7 @@ public class GroupReceiptDatabase extends Database {
|
||||
"CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
|
||||
};
|
||||
|
||||
public GroupReceiptDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public GroupReceiptDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,13 @@ package org.thoughtcrime.securesms.database;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
@ -72,7 +73,7 @@ public class IdentityDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
IdentityDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
IdentityDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,14 @@ package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
public class MediaDatabase extends Database {
|
||||
|
||||
@ -44,7 +44,7 @@ public class MediaDatabase extends Database {
|
||||
private static final String GALLERY_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentDatabase.CONTENT_TYPE + " LIKE 'image/%' OR " + AttachmentDatabase.CONTENT_TYPE + " LIKE 'video/%'");
|
||||
private static final String DOCUMENT_MEDIA_QUERY = String.format(BASE_MEDIA_QUERY, AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'image/%' AND " + AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'video/%' AND " + AttachmentDatabase.CONTENT_TYPE + " NOT LIKE 'audio/%'");
|
||||
|
||||
public MediaDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public MediaDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
@ -76,9 +76,9 @@ public class MediaDatabase extends Database {
|
||||
this.outgoing = outgoing;
|
||||
}
|
||||
|
||||
public static MediaRecord from(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Cursor cursor) {
|
||||
public static MediaRecord from(@NonNull Context context, @NonNull Cursor cursor) {
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
DatabaseAttachment attachment = attachmentDatabase.getAttachment(masterSecret, cursor);
|
||||
DatabaseAttachment attachment = attachmentDatabase.getAttachment(cursor);
|
||||
String serializedAddress = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
|
||||
boolean outgoing = MessagingDatabase.Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX)));
|
||||
Address address = null;
|
||||
|
@ -3,14 +3,15 @@ package org.thoughtcrime.securesms.database;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.documents.Document;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
|
||||
@ -23,7 +24,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
|
||||
|
||||
private static final String TAG = MessagingDatabase.class.getSimpleName();
|
||||
|
||||
public MessagingDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public MessagingDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,6 @@ package org.thoughtcrime.securesms.database;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@ -32,19 +30,17 @@ import com.annimon.stream.Stream;
|
||||
import com.google.android.mms.pdu_alt.NotificationInd;
|
||||
import com.google.android.mms.pdu_alt.PduHeaders;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailureList;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.DisplayRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
@ -63,7 +59,6 @@ import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobManager;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -153,7 +148,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
private final JobManager jobManager;
|
||||
|
||||
public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public MmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
}
|
||||
@ -289,14 +284,14 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public Reader getExpireStartedMessages(@Nullable MasterSecret masterSecret) {
|
||||
public Reader getExpireStartedMessages() {
|
||||
String where = EXPIRE_STARTED + " > 0";
|
||||
return readerFor(masterSecret, rawQuery(where, null));
|
||||
return readerFor(rawQuery(where, null));
|
||||
}
|
||||
|
||||
public Reader getDecryptInProgressMessages(MasterSecret masterSecret) {
|
||||
public Reader getDecryptInProgressMessages() {
|
||||
String where = MESSAGE_BOX + " & " + (Types.ENCRYPTION_ASYMMETRIC_BIT) + " != 0";
|
||||
return readerFor(masterSecret, rawQuery(where, null));
|
||||
return readerFor(rawQuery(where, null));
|
||||
}
|
||||
|
||||
private void updateMailboxBitmask(long id, long maskOff, long maskOn, Optional<Long> threadId) {
|
||||
@ -494,16 +489,8 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return expiring;
|
||||
}
|
||||
|
||||
public void updateMessageBody(MasterSecretUnion masterSecret, long messageId, String body) {
|
||||
body = getEncryptedBody(masterSecret, body);
|
||||
|
||||
long type;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
type = Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
type = Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
public void updateMessageBody(long messageId, String body) {
|
||||
long type = 0;
|
||||
|
||||
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
|
||||
}
|
||||
@ -544,7 +531,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public OutgoingMediaMessage getOutgoingMessage(MasterSecret masterSecret, long messageId)
|
||||
public OutgoingMediaMessage getOutgoingMessage(long messageId)
|
||||
throws MmsException, NoSuchMessageException
|
||||
{
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
@ -555,13 +542,12 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
|
||||
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
|
||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
|
||||
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID));
|
||||
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
|
||||
List<Attachment> attachments = new LinkedList<Attachment>(attachmentDatabase.getAttachmentsForMessage(masterSecret, messageId));
|
||||
List<Attachment> attachments = new LinkedList<>(attachmentDatabase.getAttachmentsForMessage(messageId));
|
||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
|
||||
String body = getDecryptedBody(masterSecret, messageText, outboxType);
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
||||
int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId);
|
||||
|
||||
@ -591,9 +577,9 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public long copyMessageInbox(MasterSecret masterSecret, long messageId) throws MmsException {
|
||||
public long copyMessageInbox(long messageId) throws MmsException {
|
||||
try {
|
||||
OutgoingMediaMessage request = getOutgoingMessage(masterSecret, messageId);
|
||||
OutgoingMediaMessage request = getOutgoingMessage(messageId);
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(ADDRESS, request.getRecipient().getAddress().serialize());
|
||||
contentValues.put(DATE_SENT, request.getSentTimeMillis());
|
||||
@ -623,8 +609,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
databaseAttachment.isVoiceNote()));
|
||||
}
|
||||
|
||||
return insertMediaMessage(new MasterSecretUnion(masterSecret),
|
||||
request.getBody(),
|
||||
return insertMediaMessage(request.getBody(),
|
||||
attachments,
|
||||
contentValues,
|
||||
null);
|
||||
@ -633,8 +618,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<InsertResult> insertMessageInbox(MasterSecretUnion masterSecret,
|
||||
IncomingMediaMessage retrieved,
|
||||
private Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved,
|
||||
String contentLocation,
|
||||
long threadId, long mailbox)
|
||||
throws MmsException
|
||||
@ -674,7 +658,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
long messageId = insertMediaMessage(masterSecret, retrieved.getBody(), retrieved.getAttachments(), contentValues, null);
|
||||
long messageId = insertMediaMessage(retrieved.getBody(), retrieved.getAttachments(), contentValues, null);
|
||||
|
||||
if (!Types.isExpirationTimerUpdate(mailbox)) {
|
||||
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
|
||||
@ -687,19 +671,12 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return Optional.of(new InsertResult(messageId, threadId));
|
||||
}
|
||||
|
||||
public Optional<InsertResult> insertMessageInbox(MasterSecretUnion masterSecret,
|
||||
IncomingMediaMessage retrieved,
|
||||
public Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved,
|
||||
String contentLocation, long threadId)
|
||||
throws MmsException
|
||||
{
|
||||
long type = Types.BASE_INBOX_TYPE;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
if (retrieved.isPushMessage()) {
|
||||
type |= Types.PUSH_MESSAGE_BIT;
|
||||
}
|
||||
@ -708,22 +685,14 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
|
||||
}
|
||||
|
||||
return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId, type);
|
||||
return insertMessageInbox(retrieved, contentLocation, threadId, type);
|
||||
}
|
||||
|
||||
public Optional<InsertResult> insertSecureDecryptedMessageInbox(MasterSecretUnion masterSecret,
|
||||
IncomingMediaMessage retrieved,
|
||||
long threadId)
|
||||
public Optional<InsertResult> insertSecureDecryptedMessageInbox(IncomingMediaMessage retrieved, long threadId)
|
||||
throws MmsException
|
||||
{
|
||||
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
if (retrieved.isPushMessage()) {
|
||||
type |= Types.PUSH_MESSAGE_BIT;
|
||||
}
|
||||
@ -732,7 +701,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
|
||||
}
|
||||
|
||||
return insertMessageInbox(masterSecret, retrieved, "", threadId, type);
|
||||
return insertMessageInbox(retrieved, "", threadId, type);
|
||||
}
|
||||
|
||||
public Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification, int subscriptionId) {
|
||||
@ -781,17 +750,13 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
jobManager.add(new TrimThreadJob(context, threadId));
|
||||
}
|
||||
|
||||
public long insertMessageOutbox(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull OutgoingMediaMessage message,
|
||||
public long insertMessageOutbox(@NonNull OutgoingMediaMessage message,
|
||||
long threadId, boolean forceSms,
|
||||
@Nullable SmsDatabase.InsertListener insertListener)
|
||||
throws MmsException
|
||||
{
|
||||
long type = Types.BASE_SENDING_TYPE;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
else type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
|
||||
if (message.isSecure()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
|
||||
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
|
||||
|
||||
@ -821,7 +786,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
|
||||
contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum());
|
||||
|
||||
long messageId = insertMediaMessage(masterSecret, message.getBody(), message.getAttachments(), contentValues, insertListener);
|
||||
long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), contentValues, insertListener);
|
||||
|
||||
if (message.getRecipient().getAddress().isGroup()) {
|
||||
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().getAddress().toGroupString(), false);
|
||||
@ -841,56 +806,23 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
private String getEncryptedBody(MasterSecretUnion masterSecret, String body) {
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
return new MasterCipher(masterSecret.getMasterSecret().get()).encryptBody(body);
|
||||
} else {
|
||||
return new AsymmetricMasterCipher(masterSecret.getAsymmetricMasterSecret().get()).encryptBody(body);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String getDecryptedBody(@NonNull MasterSecret masterSecret,
|
||||
@Nullable String body, long outboxType)
|
||||
{
|
||||
try {
|
||||
if (!TextUtils.isEmpty(body) && Types.isSymmetricEncryption(outboxType)) {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
return masterCipher.decryptBody(body);
|
||||
} else {
|
||||
return body;
|
||||
}
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private long insertMediaMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@Nullable String body,
|
||||
private long insertMediaMessage(@Nullable String body,
|
||||
@NonNull List<Attachment> attachments,
|
||||
@NonNull ContentValues contentValues,
|
||||
@Nullable SmsDatabase.InsertListener insertListener)
|
||||
throws MmsException
|
||||
{
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
AttachmentDatabase partsDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
|
||||
if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX)) ||
|
||||
Types.isAsymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX)))
|
||||
{
|
||||
if (!TextUtils.isEmpty(body)) {
|
||||
contentValues.put(BODY, getEncryptedBody(masterSecret, body));
|
||||
}
|
||||
}
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
AttachmentDatabase partsDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
|
||||
contentValues.put(BODY, body);
|
||||
contentValues.put(PART_COUNT, attachments.size());
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
long messageId = db.insert(TABLE_NAME, null, contentValues);
|
||||
|
||||
partsDatabase.insertAttachmentsForMessage(masterSecret, messageId, attachments);
|
||||
partsDatabase.insertAttachmentsForMessage(messageId, attachments);
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
return messageId;
|
||||
@ -1016,8 +948,8 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
|
||||
return new Reader(masterSecret, cursor);
|
||||
public Reader readerFor(Cursor cursor) {
|
||||
return new Reader(cursor);
|
||||
}
|
||||
|
||||
public OutgoingMessageReader readerFor(OutgoingMediaMessage message, long threadId) {
|
||||
@ -1097,16 +1029,10 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
public class Reader {
|
||||
|
||||
private final Cursor cursor;
|
||||
private final MasterSecret masterSecret;
|
||||
private final MasterCipher masterCipher;
|
||||
private final Cursor cursor;
|
||||
|
||||
public Reader(MasterSecret masterSecret, Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
this.masterSecret = masterSecret;
|
||||
|
||||
if (masterSecret != null) masterCipher = new MasterCipher(masterSecret);
|
||||
else masterCipher = null;
|
||||
public Reader(Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
public MessageRecord getNext() {
|
||||
@ -1239,27 +1165,12 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
|
||||
private DisplayRecord.Body getBody(Cursor cursor) {
|
||||
try {
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.BODY));
|
||||
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
|
||||
|
||||
if (!TextUtils.isEmpty(body) && masterCipher != null && Types.isSymmetricEncryption(box)) {
|
||||
return new DisplayRecord.Body(masterCipher.decryptBody(body), true);
|
||||
} else if (!TextUtils.isEmpty(body) && masterCipher == null && Types.isSymmetricEncryption(box)) {
|
||||
return new DisplayRecord.Body(body, false);
|
||||
} else if (!TextUtils.isEmpty(body) && Types.isAsymmetricEncryption(box)) {
|
||||
return new DisplayRecord.Body(body, false);
|
||||
} else {
|
||||
return new DisplayRecord.Body(body == null ? "" : body, true);
|
||||
}
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("MmsDatabase", e);
|
||||
return new DisplayRecord.Body(context.getString(R.string.MmsDatabase_error_decrypting_message), true);
|
||||
}
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.BODY));
|
||||
return new DisplayRecord.Body(body == null ? "" : body, true);
|
||||
}
|
||||
|
||||
private SlideDeck getSlideDeck(@NonNull Cursor cursor) {
|
||||
Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(masterSecret, cursor);
|
||||
Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(cursor);
|
||||
return new SlideDeck(context, attachment);
|
||||
}
|
||||
|
||||
|
@ -71,9 +71,9 @@ public interface MmsSmsColumns {
|
||||
protected static final long GROUP_QUIT_BIT = 0x20000;
|
||||
protected static final long EXPIRATION_TIMER_UPDATE_BIT = 0x40000;
|
||||
|
||||
// Encrypted Storage Information
|
||||
protected static final long ENCRYPTION_MASK = 0xFF000000;
|
||||
protected static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
|
||||
// Encrypted Storage Information XXX
|
||||
public static final long ENCRYPTION_MASK = 0xFF000000;
|
||||
public static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
|
||||
protected static final long ENCRYPTION_ASYMMETRIC_BIT = 0x40000000;
|
||||
protected static final long ENCRYPTION_REMOTE_BIT = 0x20000000;
|
||||
protected static final long ENCRYPTION_REMOTE_FAILED_BIT = 0x10000000;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -18,23 +18,22 @@ package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteQueryBuilder;
|
||||
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class MmsSmsDatabase extends Database {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = MmsSmsDatabase.class.getSimpleName();
|
||||
|
||||
public static final String TRANSPORT = "transport_type";
|
||||
@ -77,7 +76,7 @@ public class MmsSmsDatabase extends Database {
|
||||
AttachmentDatabase.NAME,
|
||||
AttachmentDatabase.TRANSFER_STATE};
|
||||
|
||||
public MmsSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
@ -309,34 +308,23 @@ public class MmsSmsDatabase extends Database {
|
||||
return db.rawQuery(query, null);
|
||||
}
|
||||
|
||||
public Reader readerFor(@NonNull Cursor cursor, @Nullable MasterSecret masterSecret) {
|
||||
return new Reader(cursor, masterSecret);
|
||||
}
|
||||
|
||||
public Reader readerFor(@NonNull Cursor cursor) {
|
||||
return new Reader(cursor);
|
||||
}
|
||||
|
||||
public class Reader {
|
||||
|
||||
private final Cursor cursor;
|
||||
private final Optional<MasterSecret> masterSecret;
|
||||
private EncryptingSmsDatabase.Reader smsReader;
|
||||
private MmsDatabase.Reader mmsReader;
|
||||
|
||||
public Reader(Cursor cursor, @Nullable MasterSecret masterSecret) {
|
||||
this.cursor = cursor;
|
||||
this.masterSecret = Optional.fromNullable(masterSecret);
|
||||
}
|
||||
private final Cursor cursor;
|
||||
private SmsDatabase.Reader smsReader;
|
||||
private MmsDatabase.Reader mmsReader;
|
||||
|
||||
public Reader(Cursor cursor) {
|
||||
this(cursor, null);
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
private EncryptingSmsDatabase.Reader getSmsReader() {
|
||||
private SmsDatabase.Reader getSmsReader() {
|
||||
if (smsReader == null) {
|
||||
if (masterSecret.isPresent()) smsReader = DatabaseFactory.getEncryptingSmsDatabase(context).readerFor(masterSecret.get(), cursor);
|
||||
else smsReader = DatabaseFactory.getSmsDatabase(context).readerFor(cursor);
|
||||
smsReader = DatabaseFactory.getSmsDatabase(context).readerFor(cursor);
|
||||
}
|
||||
|
||||
return smsReader;
|
||||
@ -344,7 +332,7 @@ public class MmsSmsDatabase extends Database {
|
||||
|
||||
private MmsDatabase.Reader getMmsReader() {
|
||||
if (mmsReader == null) {
|
||||
mmsReader = DatabaseFactory.getMmsDatabase(context).readerFor(masterSecret.orNull(), cursor);
|
||||
mmsReader = DatabaseFactory.getMmsDatabase(context).readerFor(cursor);
|
||||
}
|
||||
|
||||
return mmsReader;
|
||||
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
|
||||
@ -14,33 +13,35 @@ public class PlaintextBackupExporter {
|
||||
|
||||
private static final String FILENAME = "SignalPlaintextBackup.xml";
|
||||
|
||||
public static void exportPlaintextToSd(Context context, MasterSecret masterSecret)
|
||||
public static void exportPlaintextToSd(Context context)
|
||||
throws NoExternalStorageException, IOException
|
||||
{
|
||||
exportPlaintext(context, masterSecret);
|
||||
exportPlaintext(context);
|
||||
}
|
||||
|
||||
public static File getPlaintextExportFile() throws NoExternalStorageException {
|
||||
return new File(StorageUtil.getBackupDir(), FILENAME);
|
||||
}
|
||||
|
||||
private static void exportPlaintext(Context context, MasterSecret masterSecret)
|
||||
private static void exportPlaintext(Context context)
|
||||
throws NoExternalStorageException, IOException
|
||||
{
|
||||
int count = DatabaseFactory.getSmsDatabase(context).getMessageCount();
|
||||
XmlBackup.Writer writer = new XmlBackup.Writer(getPlaintextExportFile().getAbsolutePath(), count);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
int count = database.getMessageCount();
|
||||
XmlBackup.Writer writer = new XmlBackup.Writer(getPlaintextExportFile().getAbsolutePath(), count);
|
||||
|
||||
|
||||
SmsMessageRecord record;
|
||||
EncryptingSmsDatabase.Reader reader = null;
|
||||
int skip = 0;
|
||||
int ROW_LIMIT = 500;
|
||||
|
||||
SmsDatabase.Reader reader = null;
|
||||
int skip = 0;
|
||||
int ROW_LIMIT = 500;
|
||||
|
||||
do {
|
||||
if (reader != null)
|
||||
reader.close();
|
||||
|
||||
reader = DatabaseFactory.getEncryptingSmsDatabase(context).getMessages(masterSecret, skip, ROW_LIMIT);
|
||||
reader = database.readerFor(database.getMessages(skip, ROW_LIMIT));
|
||||
|
||||
while ((record = reader.getNext()) != null) {
|
||||
XmlBackup.XmlBackupItem item =
|
||||
|
@ -1,13 +1,12 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteStatement;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
@ -18,7 +17,7 @@ import java.util.Set;
|
||||
|
||||
public class PlaintextBackupImporter {
|
||||
|
||||
public static void importPlaintextFromSd(Context context, MasterSecret masterSecret)
|
||||
public static void importPlaintextFromSd(Context context)
|
||||
throws NoExternalStorageException, IOException
|
||||
{
|
||||
Log.w("PlaintextBackupImporter", "importPlaintext()");
|
||||
@ -28,7 +27,6 @@ public class PlaintextBackupImporter {
|
||||
try {
|
||||
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
|
||||
XmlBackup backup = new XmlBackup(getPlaintextExportFile().getAbsolutePath());
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
Set<Long> modifiedThreads = new HashSet<>();
|
||||
XmlBackup.XmlBackupItem item;
|
||||
|
||||
@ -53,7 +51,7 @@ public class PlaintextBackupImporter {
|
||||
addTranslatedTypeToStatement(statement, 8, item.getType());
|
||||
addNullToStatement(statement, 9);
|
||||
addStringToStatement(statement, 10, item.getSubject());
|
||||
addEncryptedStringToStatement(masterCipher, statement, 11, item.getBody());
|
||||
addStringToStatement(statement, 11, item.getBody());
|
||||
addStringToStatement(statement, 12, item.getServiceCenter());
|
||||
addLongToStatement(statement, 13, threadId);
|
||||
modifiedThreads.add(threadId);
|
||||
@ -80,14 +78,7 @@ public class PlaintextBackupImporter {
|
||||
return !backup.exists() && oldBackup.exists() ? oldBackup : backup;
|
||||
}
|
||||
|
||||
private static void addEncryptedStringToStatement(MasterCipher masterCipher, SQLiteStatement statement, int index, String value) {
|
||||
if (value == null || value.equals("null")) {
|
||||
statement.bindNull(index);
|
||||
} else {
|
||||
statement.bindString(index, masterCipher.encryptBody(value));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static void addTranslatedTypeToStatement(SQLiteStatement statement, int index, int type) {
|
||||
statement.bindLong(index, SmsDatabase.Types.translateFromSystemBaseType(type) | SmsDatabase.Types.ENCRYPTION_SYMMETRIC_BIT);
|
||||
}
|
||||
|
@ -3,11 +3,12 @@ package org.thoughtcrime.securesms.database;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
@ -31,7 +32,7 @@ public class PushDatabase extends Database {
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
TYPE + " INTEGER, " + SOURCE + " TEXT, " + DEVICE_ID + " INTEGER, " + LEGACY_MSG + " TEXT, " + CONTENT + " TEXT, " + TIMESTAMP + " INTEGER);";
|
||||
|
||||
public PushDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public PushDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@ package org.thoughtcrime.securesms.database;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@ -12,7 +10,10 @@ import android.util.Log;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@ -119,7 +120,7 @@ public class RecipientDatabase extends Database {
|
||||
SIGNAL_PROFILE_AVATAR + " TEXT DEFAULT NULL, " +
|
||||
PROFILE_SHARING + " INTEGER DEFAULT 0);";
|
||||
|
||||
public RecipientDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public RecipientDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,6 @@ package org.thoughtcrime.securesms.database;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
@ -30,9 +27,13 @@ import android.util.Pair;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteStatement;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.DisplayRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
@ -107,7 +108,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
|
||||
private final JobManager jobManager;
|
||||
|
||||
public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public SmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
}
|
||||
@ -410,7 +411,17 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
return results;
|
||||
}
|
||||
|
||||
protected Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
|
||||
public Pair<Long, Long> updateBundleMessageBody(long messageId, String body) {
|
||||
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT;
|
||||
return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type);
|
||||
}
|
||||
|
||||
public void updateMessageBody(long messageId, String body) {
|
||||
long type = 0;
|
||||
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
|
||||
}
|
||||
|
||||
private Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
|
||||
TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
|
||||
@ -427,31 +438,33 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
|
||||
public Pair<Long, Long> copyMessageInbox(long messageId) {
|
||||
Reader reader = readerFor(getMessage(messageId));
|
||||
SmsMessageRecord record = reader.getNext();
|
||||
try {
|
||||
SmsMessageRecord record = getMessage(messageId);
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(TYPE, (record.getType() & ~Types.BASE_TYPE_MASK) | Types.BASE_INBOX_TYPE);
|
||||
contentValues.put(ADDRESS, record.getIndividualRecipient().getAddress().serialize());
|
||||
contentValues.put(ADDRESS_DEVICE_ID, record.getRecipientDeviceId());
|
||||
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
|
||||
contentValues.put(DATE_SENT, record.getDateSent());
|
||||
contentValues.put(PROTOCOL, 31337);
|
||||
contentValues.put(READ, 0);
|
||||
contentValues.put(BODY, record.getBody().getBody());
|
||||
contentValues.put(THREAD_ID, record.getThreadId());
|
||||
contentValues.put(EXPIRES_IN, record.getExpiresIn());
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(TYPE, (record.getType() & ~Types.BASE_TYPE_MASK) | Types.BASE_INBOX_TYPE);
|
||||
contentValues.put(ADDRESS, record.getIndividualRecipient().getAddress().serialize());
|
||||
contentValues.put(ADDRESS_DEVICE_ID, record.getRecipientDeviceId());
|
||||
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
|
||||
contentValues.put(DATE_SENT, record.getDateSent());
|
||||
contentValues.put(PROTOCOL, 31337);
|
||||
contentValues.put(READ, 0);
|
||||
contentValues.put(BODY, record.getBody().getBody());
|
||||
contentValues.put(THREAD_ID, record.getThreadId());
|
||||
contentValues.put(EXPIRES_IN, record.getExpiresIn());
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
long newMessageId = db.insert(TABLE_NAME, null, contentValues);
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
long newMessageId = db.insert(TABLE_NAME, null, contentValues);
|
||||
|
||||
DatabaseFactory.getThreadDatabase(context).update(record.getThreadId(), true);
|
||||
notifyConversationListeners(record.getThreadId());
|
||||
DatabaseFactory.getThreadDatabase(context).update(record.getThreadId(), true);
|
||||
notifyConversationListeners(record.getThreadId());
|
||||
|
||||
jobManager.add(new TrimThreadJob(context, record.getThreadId()));
|
||||
reader.close();
|
||||
|
||||
return new Pair<>(newMessageId, record.getThreadId());
|
||||
jobManager.add(new TrimThreadJob(context, record.getThreadId()));
|
||||
|
||||
return new Pair<>(newMessageId, record.getThreadId());
|
||||
} catch (NoSuchMessageException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull Pair<Long, Long> insertReceivedCall(@NonNull Address address) {
|
||||
@ -587,10 +600,11 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
return insertMessageInbox(message, Types.BASE_INBOX_TYPE);
|
||||
}
|
||||
|
||||
protected long insertMessageOutbox(long threadId, OutgoingTextMessage message,
|
||||
long type, boolean forceSms, long date,
|
||||
InsertListener insertListener)
|
||||
public long insertMessageOutbox(long threadId, OutgoingTextMessage message,
|
||||
boolean forceSms, long date, InsertListener insertListener)
|
||||
{
|
||||
long type = Types.BASE_SENDING_TYPE;
|
||||
|
||||
if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT;
|
||||
else if (message.isSecureMessage()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
|
||||
else if (message.isEndSession()) type |= Types.END_SESSION_BIT;
|
||||
@ -670,12 +684,21 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
return db.query(TABLE_NAME, MESSAGE_PROJECTION, where, null, null, null, null);
|
||||
}
|
||||
|
||||
public Cursor getMessage(long messageId) {
|
||||
public SmsMessageRecord getMessage(long messageId) throws NoSuchMessageException {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[]{messageId + ""},
|
||||
null, null, null);
|
||||
setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId));
|
||||
return cursor;
|
||||
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[]{messageId + ""}, null, null, null);
|
||||
Reader reader = new Reader(cursor);
|
||||
SmsMessageRecord record = reader.getNext();
|
||||
|
||||
reader.close();
|
||||
|
||||
if (record == null) throw new NoSuchMessageException("No message for ID: " + messageId);
|
||||
else return record;
|
||||
}
|
||||
|
||||
public Cursor getMessageCursor(long messageId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
return db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null);
|
||||
}
|
||||
|
||||
public boolean deleteMessage(long messageId) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -18,15 +18,14 @@ package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteStatement;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
@ -40,19 +39,6 @@ public class SmsMigrator {
|
||||
|
||||
private static final String TAG = SmsMigrator.class.getSimpleName();
|
||||
|
||||
private static void addEncryptedStringToStatement(Context context, SQLiteStatement statement,
|
||||
Cursor cursor, MasterSecret masterSecret,
|
||||
int index, String key)
|
||||
{
|
||||
int columnIndex = cursor.getColumnIndexOrThrow(key);
|
||||
|
||||
if (cursor.isNull(columnIndex)) {
|
||||
statement.bindNull(index);
|
||||
} else {
|
||||
statement.bindString(index, encrypt(masterSecret, cursor.getString(columnIndex)));
|
||||
}
|
||||
}
|
||||
|
||||
private static void addStringToStatement(SQLiteStatement statement, Cursor cursor,
|
||||
int index, String key)
|
||||
{
|
||||
@ -77,8 +63,8 @@ public class SmsMigrator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addTranslatedTypeToStatement(SQLiteStatement statement, Cursor cursor,
|
||||
int index, String key)
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static void addTranslatedTypeToStatement(SQLiteStatement statement, Cursor cursor, int index, String key)
|
||||
{
|
||||
int columnIndex = cursor.getColumnIndexOrThrow(key);
|
||||
|
||||
@ -99,9 +85,8 @@ public class SmsMigrator {
|
||||
ourType == MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE;
|
||||
}
|
||||
|
||||
private static void getContentValuesForRow(Context context, MasterSecret masterSecret,
|
||||
Cursor cursor, long threadId,
|
||||
SQLiteStatement statement)
|
||||
private static void getContentValuesForRow(Context context, Cursor cursor,
|
||||
long threadId, SQLiteStatement statement)
|
||||
{
|
||||
String theirAddress = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
|
||||
statement.bindString(1, Address.fromExternal(context, theirAddress).serialize());
|
||||
@ -115,7 +100,7 @@ public class SmsMigrator {
|
||||
addTranslatedTypeToStatement(statement, cursor, 8, SmsDatabase.TYPE);
|
||||
addIntToStatement(statement, cursor, 9, SmsDatabase.REPLY_PATH_PRESENT);
|
||||
addStringToStatement(statement, cursor, 10, SmsDatabase.SUBJECT);
|
||||
addEncryptedStringToStatement(context, statement, cursor, masterSecret, 11, SmsDatabase.BODY);
|
||||
addStringToStatement(statement, cursor, 11, SmsDatabase.BODY);
|
||||
addStringToStatement(statement, cursor, 12, SmsDatabase.SERVICE_CENTER);
|
||||
|
||||
statement.bindLong(13, threadId);
|
||||
@ -159,14 +144,7 @@ public class SmsMigrator {
|
||||
else return recipientList;
|
||||
}
|
||||
|
||||
private static String encrypt(MasterSecret masterSecret, String body)
|
||||
{
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
return masterCipher.encryptBody(body);
|
||||
}
|
||||
|
||||
private static void migrateConversation(Context context, MasterSecret masterSecret,
|
||||
SmsMigrationProgressListener listener,
|
||||
private static void migrateConversation(Context context, SmsMigrationProgressListener listener,
|
||||
ProgressDescription progress,
|
||||
long theirThreadId, long ourThreadId)
|
||||
{
|
||||
@ -191,7 +169,7 @@ public class SmsMigrator {
|
||||
int typeColumn = cursor.getColumnIndex(SmsDatabase.TYPE);
|
||||
|
||||
if (cursor.isNull(typeColumn) || isAppropriateTypeForMigration(cursor, typeColumn)) {
|
||||
getContentValuesForRow(context, masterSecret, cursor, ourThreadId, statement);
|
||||
getContentValuesForRow(context, cursor, ourThreadId, statement);
|
||||
statement.execute();
|
||||
}
|
||||
|
||||
@ -208,9 +186,7 @@ public class SmsMigrator {
|
||||
}
|
||||
}
|
||||
|
||||
public static void migrateDatabase(Context context,
|
||||
MasterSecret masterSecret,
|
||||
SmsMigrationProgressListener listener)
|
||||
public static void migrateDatabase(Context context, SmsMigrationProgressListener listener)
|
||||
{
|
||||
// if (context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).getBoolean("migrated", false))
|
||||
// return;
|
||||
@ -231,7 +207,7 @@ public class SmsMigrator {
|
||||
if (ourRecipients != null) {
|
||||
if (ourRecipients.size() == 1) {
|
||||
long ourThreadId = threadDatabase.getThreadIdFor(ourRecipients.iterator().next());
|
||||
migrateConversation(context, masterSecret, listener, progress, theirThreadId, ourThreadId);
|
||||
migrateConversation(context, listener, progress, theirThreadId, ourThreadId);
|
||||
} else if (ourRecipients.size() > 1) {
|
||||
ourRecipients.add(Recipient.from(context, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), true));
|
||||
|
||||
@ -245,7 +221,7 @@ public class SmsMigrator {
|
||||
Recipient ourGroupRecipient = Recipient.from(context, Address.fromSerialized(ourGroupId), true);
|
||||
long ourThreadId = threadDatabase.getThreadIdFor(ourGroupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
|
||||
|
||||
migrateConversation(context, masterSecret, listener, progress, theirThreadId, ourThreadId);
|
||||
migrateConversation(context, listener, progress, theirThreadId, ourThreadId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,7 +238,7 @@ public class SmsMigrator {
|
||||
}
|
||||
|
||||
public interface SmsMigrationProgressListener {
|
||||
public void progressUpdate(ProgressDescription description);
|
||||
void progressUpdate(ProgressDescription description);
|
||||
}
|
||||
|
||||
public static class ProgressDescription {
|
||||
@ -271,8 +247,8 @@ public class SmsMigrator {
|
||||
public final int secondaryTotal;
|
||||
public final int secondaryComplete;
|
||||
|
||||
public ProgressDescription(int primaryTotal, int primaryComplete,
|
||||
int secondaryTotal, int secondaryComplete)
|
||||
ProgressDescription(int primaryTotal, int primaryComplete,
|
||||
int secondaryTotal, int secondaryComplete)
|
||||
{
|
||||
this.primaryTotal = primaryTotal;
|
||||
this.primaryComplete = primaryComplete;
|
||||
@ -280,14 +256,14 @@ public class SmsMigrator {
|
||||
this.secondaryComplete = secondaryComplete;
|
||||
}
|
||||
|
||||
public ProgressDescription(ProgressDescription that, int secondaryTotal, int secondaryComplete) {
|
||||
ProgressDescription(ProgressDescription that, int secondaryTotal, int secondaryComplete) {
|
||||
this.primaryComplete = that.primaryComplete;
|
||||
this.primaryTotal = that.primaryTotal;
|
||||
this.secondaryComplete = secondaryComplete;
|
||||
this.secondaryTotal = secondaryTotal;
|
||||
}
|
||||
|
||||
public void incrementPrimaryComplete() {
|
||||
void incrementPrimaryComplete() {
|
||||
primaryComplete += 1;
|
||||
}
|
||||
}
|
||||
|
@ -21,21 +21,19 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MergeCursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.DisplayRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
@ -46,7 +44,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.DelimiterUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@ -90,7 +87,7 @@ public class ThreadDatabase extends Database {
|
||||
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0, " +
|
||||
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " + UNREAD_COUNT + " INTEGER DEFAULT 0);";
|
||||
|
||||
static final String[] CREATE_INDEXS = {
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");",
|
||||
"CREATE INDEX IF NOT EXISTS archived_count_index ON " + TABLE_NAME + " (" + ARCHIVED + ", " + MESSAGE_COUNT + ");",
|
||||
};
|
||||
@ -109,7 +106,7 @@ public class ThreadDatabase extends Database {
|
||||
Stream.of(GroupDatabase.TYPED_GROUP_PROJECTION))
|
||||
.toList();
|
||||
|
||||
public ThreadDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
@ -617,8 +614,8 @@ public class ThreadDatabase extends Database {
|
||||
void onProgress(int complete, int total);
|
||||
}
|
||||
|
||||
public Reader readerFor(Cursor cursor, MasterCipher masterCipher) {
|
||||
return new Reader(cursor, masterCipher);
|
||||
public Reader readerFor(Cursor cursor) {
|
||||
return new Reader(cursor);
|
||||
}
|
||||
|
||||
public static class DistributionTypes {
|
||||
@ -631,12 +628,10 @@ public class ThreadDatabase extends Database {
|
||||
|
||||
public class Reader {
|
||||
|
||||
private final Cursor cursor;
|
||||
private final MasterCipher masterCipher;
|
||||
private final Cursor cursor;
|
||||
|
||||
public Reader(Cursor cursor, MasterCipher masterCipher) {
|
||||
this.cursor = cursor;
|
||||
this.masterCipher = masterCipher;
|
||||
public Reader(Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
public ThreadRecord getNext() {
|
||||
@ -686,21 +681,7 @@ public class ThreadDatabase extends Database {
|
||||
}
|
||||
|
||||
private DisplayRecord.Body getPlaintextBody(Cursor cursor) {
|
||||
try {
|
||||
long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE));
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET));
|
||||
|
||||
if (!TextUtils.isEmpty(body) && masterCipher != null && MmsSmsColumns.Types.isSymmetricEncryption(type)) {
|
||||
return new DisplayRecord.Body(masterCipher.decryptBody(body), true);
|
||||
} else if (!TextUtils.isEmpty(body) && masterCipher == null && MmsSmsColumns.Types.isSymmetricEncryption(type)) {
|
||||
return new DisplayRecord.Body(body, false);
|
||||
} else {
|
||||
return new DisplayRecord.Body(body, true);
|
||||
}
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("ThreadDatabase", e);
|
||||
return new DisplayRecord.Body(context.getString(R.string.ThreadDatabase_error_decrypting_message), true);
|
||||
}
|
||||
return new DisplayRecord.Body(cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET)), true);
|
||||
}
|
||||
|
||||
private @Nullable Uri getSnippetUri(Cursor cursor) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,197 @@
|
||||
package org.thoughtcrime.securesms.database.helpers;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.annimon.stream.function.Function;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SQLCipherMigrationHelper {
|
||||
|
||||
private static final String TAG = SQLCipherMigrationHelper.class.getSimpleName();
|
||||
|
||||
private static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
|
||||
private static final long ENCRYPTION_ASYMMETRIC_BIT = 0x40000000;
|
||||
|
||||
static void migratePlaintext(@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
|
||||
{
|
||||
modernDb.beginTransaction();
|
||||
try {
|
||||
copyTable("identities", legacyDb, modernDb, null);
|
||||
copyTable("push", legacyDb, modernDb, null);
|
||||
copyTable("groups", legacyDb, modernDb, null);
|
||||
copyTable("recipient_preferences", legacyDb, modernDb, null);
|
||||
copyTable("group_receipts", legacyDb, modernDb, null);
|
||||
modernDb.setTransactionSuccessful();
|
||||
} finally {
|
||||
modernDb.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public static void migrateCiphertext(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
|
||||
{
|
||||
MasterCipher legacyCipher = new MasterCipher(masterSecret);
|
||||
AsymmetricMasterCipher legacyAsymmetricCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
|
||||
|
||||
modernDb.beginTransaction();
|
||||
|
||||
try {
|
||||
copyTable("sms", legacyDb, modernDb, (row) -> {
|
||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
||||
row.getAsLong("type"),
|
||||
row.getAsString("body"));
|
||||
|
||||
row.put("body", plaintext.second);
|
||||
row.put("type", plaintext.first);
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
copyTable("mms", legacyDb, modernDb, (row) -> {
|
||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
||||
row.getAsLong("msg_box"),
|
||||
row.getAsString("body"));
|
||||
|
||||
row.put("body", plaintext.second);
|
||||
row.put("msg_box", plaintext.first);
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
copyTable("part", legacyDb, modernDb, (row) -> {
|
||||
String fileName = row.getAsString("file_name");
|
||||
String mediaKey = row.getAsString("cd");
|
||||
|
||||
try {
|
||||
if (!TextUtils.isEmpty(fileName)) {
|
||||
row.put("file_name", legacyCipher.decryptBody(fileName));
|
||||
}
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!TextUtils.isEmpty(mediaKey)) {
|
||||
byte[] plaintext;
|
||||
|
||||
if (mediaKey.startsWith("?ASYNC-")) {
|
||||
plaintext = legacyAsymmetricCipher.decryptBytes(Base64.decode(mediaKey.substring("?ASYNC-".length())));
|
||||
} else {
|
||||
plaintext = legacyCipher.decryptBytes(Base64.decode(mediaKey));
|
||||
}
|
||||
|
||||
row.put("cd", Base64.encodeBytes(plaintext));
|
||||
}
|
||||
} catch (IOException | InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
copyTable("thread", legacyDb, modernDb, (row) -> {
|
||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
||||
row.getAsLong("snippet_type"),
|
||||
row.getAsString("snippet"));
|
||||
|
||||
row.put("snippet", plaintext.second);
|
||||
row.put("snippet_type", plaintext.first);
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
|
||||
copyTable("drafts", legacyDb, modernDb, (row) -> {
|
||||
String draftType = row.getAsString("type");
|
||||
String draft = row.getAsString("value");
|
||||
|
||||
try {
|
||||
if (!TextUtils.isEmpty(draftType)) row.put("type", legacyCipher.decryptBody(draftType));
|
||||
if (!TextUtils.isEmpty(draft)) row.put("value", legacyCipher.decryptBody(draft));
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
|
||||
modernDb.setTransactionSuccessful();
|
||||
} finally {
|
||||
modernDb.endTransaction();
|
||||
}
|
||||
|
||||
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
|
||||
}
|
||||
|
||||
private static void copyTable(@NonNull String tableName,
|
||||
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb,
|
||||
@Nullable Function<ContentValues, ContentValues> transformer)
|
||||
{
|
||||
try (Cursor cursor = legacyDb.query(tableName, null, null, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
ContentValues row = new ContentValues();
|
||||
|
||||
for (int i=0;i<cursor.getColumnCount();i++) {
|
||||
String columnName = cursor.getColumnName(i);
|
||||
|
||||
switch (cursor.getType(i)) {
|
||||
case Cursor.FIELD_TYPE_STRING: row.put(columnName, cursor.getString(i)); break;
|
||||
case Cursor.FIELD_TYPE_FLOAT: row.put(columnName, cursor.getFloat(i)); break;
|
||||
case Cursor.FIELD_TYPE_INTEGER: row.put(columnName, cursor.getLong(i)); break;
|
||||
case Cursor.FIELD_TYPE_BLOB: row.put(columnName, cursor.getBlob(i)); break;
|
||||
}
|
||||
}
|
||||
|
||||
if (transformer != null) {
|
||||
row = transformer.apply(row);
|
||||
}
|
||||
|
||||
modernDb.insert(tableName, null, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Pair<Long, String> getPlaintextBody(@NonNull MasterCipher legacyCipher,
|
||||
@NonNull AsymmetricMasterCipher legacyAsymmetricCipher,
|
||||
long type,
|
||||
@Nullable String body)
|
||||
{
|
||||
try {
|
||||
if (!TextUtils.isEmpty(body)) {
|
||||
if ((type & ENCRYPTION_SYMMETRIC_BIT) != 0) body = legacyCipher.decryptBody(body);
|
||||
else if ((type & ENCRYPTION_ASYMMETRIC_BIT) != 0) body = legacyAsymmetricCipher.decryptBody(body);
|
||||
}
|
||||
} catch (InvalidMessageException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
type &= ~(ENCRYPTION_SYMMETRIC_BIT);
|
||||
type &= ~(ENCRYPTION_ASYMMETRIC_BIT);
|
||||
|
||||
return new Pair<>(type, body);
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package org.thoughtcrime.securesms.database.helpers;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
|
||||
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
private final DatabaseSecret databaseSecret;
|
||||
|
||||
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() {
|
||||
@Override
|
||||
public void preKey(SQLiteDatabase db) {
|
||||
db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;");
|
||||
db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postKey(SQLiteDatabase db) {
|
||||
db.rawExecSQL("PRAGMA kdf_iter = '1';");
|
||||
db.rawExecSQL("PRAGMA cipher_page_size = 4096;");
|
||||
}
|
||||
});
|
||||
|
||||
this.context = context.getApplicationContext();
|
||||
this.databaseSecret = databaseSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(SmsDatabase.CREATE_TABLE);
|
||||
db.execSQL(MmsDatabase.CREATE_TABLE);
|
||||
db.execSQL(AttachmentDatabase.CREATE_TABLE);
|
||||
db.execSQL(ThreadDatabase.CREATE_TABLE);
|
||||
db.execSQL(IdentityDatabase.CREATE_TABLE);
|
||||
db.execSQL(DraftDatabase.CREATE_TABLE);
|
||||
db.execSQL(PushDatabase.CREATE_TABLE);
|
||||
db.execSQL(GroupDatabase.CREATE_TABLE);
|
||||
db.execSQL(RecipientDatabase.CREATE_TABLE);
|
||||
db.execSQL(GroupReceiptDatabase.CREATE_TABLE);
|
||||
|
||||
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, AttachmentDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, DraftDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, GroupDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
|
||||
|
||||
if (context.getDatabasePath(ClassicOpenHelper.NAME).exists()) {
|
||||
ClassicOpenHelper legacyHelper = new ClassicOpenHelper(context);
|
||||
android.database.sqlite.SQLiteDatabase legacyDb = legacyHelper.getWritableDatabase();
|
||||
|
||||
SQLCipherMigrationHelper.migratePlaintext(legacyDb, db);
|
||||
|
||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
||||
|
||||
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db);
|
||||
else TextSecurePreferences.setNeedsSqlCipherMigration(context, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
|
||||
}
|
||||
|
||||
public SQLiteDatabase getReadableDatabase() {
|
||||
return getReadableDatabase(databaseSecret.asString());
|
||||
}
|
||||
|
||||
public SQLiteDatabase getWritableDatabase() {
|
||||
return getWritableDatabase(databaseSecret.asString());
|
||||
}
|
||||
|
||||
private void executeStatements(SQLiteDatabase db, String[] statements) {
|
||||
for (String statement : statements)
|
||||
db.execSQL(statement);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -9,7 +9,6 @@ import android.support.v4.content.AsyncTaskLoader;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||
@ -28,15 +27,14 @@ import java.util.Map;
|
||||
|
||||
public class BucketedThreadMediaLoader extends AsyncTaskLoader<BucketedThreadMediaLoader.BucketedThreadMedia> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = BucketedThreadMediaLoader.class.getSimpleName();
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
private final Address address;
|
||||
|
||||
public BucketedThreadMediaLoader(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Address address) {
|
||||
public BucketedThreadMediaLoader(@NonNull Context context, @NonNull Address address) {
|
||||
super(context);
|
||||
this.masterSecret = masterSecret;
|
||||
this.address = address;
|
||||
this.address = address;
|
||||
|
||||
onContentChanged();
|
||||
}
|
||||
@ -60,7 +58,7 @@ public class BucketedThreadMediaLoader extends AsyncTaskLoader<BucketedThreadMed
|
||||
|
||||
try (Cursor cursor = DatabaseFactory.getMediaDatabase(getContext()).getGalleryMediaForThread(threadId)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
result.add(MediaDatabase.MediaRecord.from(getContext(), masterSecret, cursor));
|
||||
result.add(MediaDatabase.MediaRecord.from(getContext(), cursor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ public class MessageDetailsLoader extends AbstractCursorLoader {
|
||||
public Cursor getCursor() {
|
||||
switch (type) {
|
||||
case MmsSmsDatabase.SMS_TRANSPORT:
|
||||
return DatabaseFactory.getEncryptingSmsDatabase(context).getMessage(messageId);
|
||||
return DatabaseFactory.getSmsDatabase(context).getMessageCursor(messageId);
|
||||
case MmsSmsDatabase.MMS_TRANSPORT:
|
||||
return DatabaseFactory.getMmsDatabase(context).getMessage(messageId);
|
||||
default:
|
||||
|
@ -10,7 +10,6 @@ import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@ -37,7 +36,6 @@ import java.util.Set;
|
||||
public class GroupManager {
|
||||
|
||||
public static @NonNull GroupActionResult createGroup(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@NonNull Set<Recipient> members,
|
||||
@Nullable Bitmap avatar,
|
||||
@Nullable String name,
|
||||
@ -55,7 +53,7 @@ public class GroupManager {
|
||||
if (!mms) {
|
||||
groupDatabase.updateAvatar(groupId, avatarBytes);
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true);
|
||||
return sendGroupUpdate(context, masterSecret, groupId, memberAddresses, name, avatarBytes);
|
||||
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes);
|
||||
} else {
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
|
||||
return new GroupActionResult(groupRecipient, threadId);
|
||||
@ -63,7 +61,6 @@ public class GroupManager {
|
||||
}
|
||||
|
||||
public static GroupActionResult updateGroup(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@NonNull String groupId,
|
||||
@NonNull Set<Recipient> members,
|
||||
@Nullable Bitmap avatar,
|
||||
@ -80,7 +77,7 @@ public class GroupManager {
|
||||
groupDatabase.updateAvatar(groupId, avatarBytes);
|
||||
|
||||
if (!GroupUtil.isMmsGroup(groupId)) {
|
||||
return sendGroupUpdate(context, masterSecret, groupId, memberAddresses, name, avatarBytes);
|
||||
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes);
|
||||
} else {
|
||||
Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), true);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
@ -89,7 +86,6 @@ public class GroupManager {
|
||||
}
|
||||
|
||||
private static GroupActionResult sendGroupUpdate(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@NonNull String groupId,
|
||||
@NonNull Set<Address> members,
|
||||
@Nullable String groupName,
|
||||
@ -119,7 +115,7 @@ public class GroupManager {
|
||||
}
|
||||
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0);
|
||||
long threadId = MessageSender.send(context, masterSecret, outgoingMessage, -1, false, null);
|
||||
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
|
||||
|
||||
return new GroupActionResult(groupRecipient, threadId);
|
||||
} catch (IOException e) {
|
||||
|
@ -9,13 +9,12 @@ import android.util.Log;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
@ -47,7 +46,6 @@ public class GroupMessageProcessor {
|
||||
private static final String TAG = GroupMessageProcessor.class.getSimpleName();
|
||||
|
||||
public static @Nullable Long process(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
boolean outgoing)
|
||||
@ -63,11 +61,11 @@ public class GroupMessageProcessor {
|
||||
Optional<GroupRecord> record = database.getGroup(id);
|
||||
|
||||
if (record.isPresent() && group.getType() == Type.UPDATE) {
|
||||
return handleGroupUpdate(context, masterSecret, envelope, group, record.get(), outgoing);
|
||||
return handleGroupUpdate(context, envelope, group, record.get(), outgoing);
|
||||
} else if (!record.isPresent() && group.getType() == Type.UPDATE) {
|
||||
return handleGroupCreate(context, masterSecret, envelope, group, outgoing);
|
||||
return handleGroupCreate(context, envelope, group, outgoing);
|
||||
} else if (record.isPresent() && group.getType() == Type.QUIT) {
|
||||
return handleGroupLeave(context, masterSecret, envelope, group, record.get(), outgoing);
|
||||
return handleGroupLeave(context, envelope, group, record.get(), outgoing);
|
||||
} else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) {
|
||||
return handleGroupInfoRequest(context, envelope, group, record.get());
|
||||
} else {
|
||||
@ -77,7 +75,6 @@ public class GroupMessageProcessor {
|
||||
}
|
||||
|
||||
private static @Nullable Long handleGroupCreate(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceGroup group,
|
||||
boolean outgoing)
|
||||
@ -100,11 +97,10 @@ public class GroupMessageProcessor {
|
||||
avatar != null && avatar.isPointer() ? avatar.asPointer() : null,
|
||||
envelope.getRelay());
|
||||
|
||||
return storeMessage(context, masterSecret, envelope, group, builder.build(), outgoing);
|
||||
return storeMessage(context, envelope, group, builder.build(), outgoing);
|
||||
}
|
||||
|
||||
private static @Nullable Long handleGroupUpdate(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceGroup group,
|
||||
@NonNull GroupRecord groupRecord,
|
||||
@ -159,7 +155,7 @@ public class GroupMessageProcessor {
|
||||
|
||||
if (!groupRecord.isActive()) database.setActive(id, true);
|
||||
|
||||
return storeMessage(context, masterSecret, envelope, group, builder.build(), outgoing);
|
||||
return storeMessage(context, envelope, group, builder.build(), outgoing);
|
||||
}
|
||||
|
||||
private static Long handleGroupInfoRequest(@NonNull Context context,
|
||||
@ -177,7 +173,6 @@ public class GroupMessageProcessor {
|
||||
}
|
||||
|
||||
private static Long handleGroupLeave(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceGroup group,
|
||||
@NonNull GroupRecord record,
|
||||
@ -194,7 +189,7 @@ public class GroupMessageProcessor {
|
||||
database.remove(id, Address.fromExternal(context, envelope.getSource()));
|
||||
if (outgoing) database.setActive(id, false);
|
||||
|
||||
return storeMessage(context, masterSecret, envelope, group, builder.build(), outgoing);
|
||||
return storeMessage(context, envelope, group, builder.build(), outgoing);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -202,7 +197,6 @@ public class GroupMessageProcessor {
|
||||
|
||||
|
||||
private static @Nullable Long storeMessage(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceGroup group,
|
||||
@NonNull GroupContext storage,
|
||||
@ -220,21 +214,21 @@ public class GroupMessageProcessor {
|
||||
Recipient recipient = Recipient.from(context, addres, false);
|
||||
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, envelope.getTimestamp(), 0);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||
long messageId = mmsDatabase.insertMessageOutbox(masterSecret, outgoingMessage, threadId, false, null);
|
||||
long messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
|
||||
|
||||
mmsDatabase.markAsSent(messageId, true);
|
||||
|
||||
return threadId;
|
||||
} else {
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String body = Base64.encodeBytes(storage.toByteArray());
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group), 0);
|
||||
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
String body = Base64.encodeBytes(storage.toByteArray());
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()), envelope.getSourceDevice(), envelope.getTimestamp(), body, Optional.of(group), 0);
|
||||
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
|
||||
|
||||
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(masterSecret, groupMessage);
|
||||
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(groupMessage);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId());
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
return insertResult.get().getThreadId();
|
||||
} else {
|
||||
return null;
|
||||
|
@ -8,10 +8,7 @@ import android.util.Log;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MediaKey;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
@ -20,6 +17,7 @@ import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
@ -27,7 +25,6 @@ import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
@ -72,7 +69,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
public void onRun(MasterSecret masterSecret) throws IOException {
|
||||
final AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
|
||||
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
|
||||
final Attachment attachment = database.getAttachment(masterSecret, attachmentId);
|
||||
final Attachment attachment = database.getAttachment(attachmentId);
|
||||
|
||||
if (attachment == null) {
|
||||
Log.w(TAG, "attachment no longer exists.");
|
||||
@ -92,8 +89,8 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
Log.w(TAG, "Downloading push part " + attachmentId);
|
||||
database.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
|
||||
|
||||
retrieveAttachment(masterSecret, messageId, attachmentId, attachment);
|
||||
MessageNotifier.updateNotification(context, masterSecret);
|
||||
retrieveAttachment(messageId, attachmentId, attachment);
|
||||
MessageNotifier.updateNotification(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -107,8 +104,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
return (exception instanceof PushNetworkException);
|
||||
}
|
||||
|
||||
private void retrieveAttachment(MasterSecret masterSecret,
|
||||
long messageId,
|
||||
private void retrieveAttachment(long messageId,
|
||||
final AttachmentId attachmentId,
|
||||
final Attachment attachment)
|
||||
throws IOException
|
||||
@ -120,26 +116,23 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
try {
|
||||
attachmentFile = createTempFile();
|
||||
|
||||
SignalServiceAttachmentPointer pointer = createAttachmentPointer(masterSecret, attachment);
|
||||
InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, new ProgressListener() {
|
||||
@Override
|
||||
public void onAttachmentProgress(long total, long progress) {
|
||||
EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress));
|
||||
}
|
||||
});
|
||||
SignalServiceAttachmentPointer pointer = createAttachmentPointer(attachment);
|
||||
InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress)));
|
||||
|
||||
database.insertAttachmentsForPlaceholder(masterSecret, messageId, attachmentId, stream);
|
||||
database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream);
|
||||
} catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
markFailed(messageId, attachmentId);
|
||||
} finally {
|
||||
if (attachmentFile != null)
|
||||
if (attachmentFile != null) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
attachmentFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SignalServiceAttachmentPointer createAttachmentPointer(MasterSecret masterSecret, Attachment attachment)
|
||||
SignalServiceAttachmentPointer createAttachmentPointer(Attachment attachment)
|
||||
throws InvalidPartException
|
||||
{
|
||||
if (TextUtils.isEmpty(attachment.getLocation())) {
|
||||
@ -151,10 +144,9 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
}
|
||||
|
||||
try {
|
||||
AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret);
|
||||
long id = Long.parseLong(attachment.getLocation());
|
||||
byte[] key = MediaKey.getDecrypted(masterSecret, asymmetricMasterSecret, attachment.getKey());
|
||||
String relay = null;
|
||||
long id = Long.parseLong(attachment.getLocation());
|
||||
byte[] key = Base64.decode(attachment.getKey());
|
||||
String relay = null;
|
||||
|
||||
if (TextUtils.isEmpty(attachment.getRelay())) {
|
||||
relay = attachment.getRelay();
|
||||
@ -173,7 +165,7 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
Optional.fromNullable(attachment.getDigest()),
|
||||
Optional.fromNullable(attachment.getFileName()),
|
||||
attachment.isVoiceNote());
|
||||
} catch (InvalidMessageException | IOException | ArithmeticException e) {
|
||||
} catch (IOException | ArithmeticException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new InvalidPartException(e);
|
||||
}
|
||||
@ -200,8 +192,8 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
}
|
||||
|
||||
@VisibleForTesting static class InvalidPartException extends Exception {
|
||||
public InvalidPartException(String s) {super(s);}
|
||||
public InvalidPartException(Exception e) {super(e);}
|
||||
InvalidPartException(String s) {super(s);}
|
||||
InvalidPartException(Exception e) {super(e);}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ public class AttachmentFileNameJob extends MasterSecretJob {
|
||||
AttachmentId attachmentId = new AttachmentId(attachmentRowId, attachmentUniqueId);
|
||||
String plaintextFileName = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret)).decryptBody(encryptedFileName);
|
||||
|
||||
DatabaseFactory.getAttachmentDatabase(context).updateAttachmentFileName(masterSecret, attachmentId, plaintextFileName);
|
||||
DatabaseFactory.getAttachmentDatabase(context).updateAttachmentFileName(attachmentId, plaintextFileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -6,9 +6,7 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
@ -19,15 +17,13 @@ import java.io.IOException;
|
||||
public class DirectoryRefreshJob extends ContextJob {
|
||||
|
||||
@Nullable private transient Recipient recipient;
|
||||
@Nullable private transient MasterSecret masterSecret;
|
||||
private transient boolean notifyOfNewUsers;
|
||||
|
||||
public DirectoryRefreshJob(@NonNull Context context, boolean notifyOfNewUsers) {
|
||||
this(context, null, null, notifyOfNewUsers);
|
||||
this(context, null, notifyOfNewUsers);
|
||||
}
|
||||
|
||||
public DirectoryRefreshJob(@NonNull Context context,
|
||||
@Nullable MasterSecret masterSecret,
|
||||
@Nullable Recipient recipient,
|
||||
boolean notifyOfNewUsers)
|
||||
{
|
||||
@ -37,7 +33,6 @@ public class DirectoryRefreshJob extends ContextJob {
|
||||
.create());
|
||||
|
||||
this.recipient = recipient;
|
||||
this.masterSecret = masterSecret;
|
||||
this.notifyOfNewUsers = notifyOfNewUsers;
|
||||
}
|
||||
|
||||
@ -53,9 +48,9 @@ public class DirectoryRefreshJob extends ContextJob {
|
||||
try {
|
||||
wakeLock.acquire();
|
||||
if (recipient == null) {
|
||||
DirectoryHelper.refreshDirectory(context, KeyCachingService.getMasterSecret(context), notifyOfNewUsers);
|
||||
DirectoryHelper.refreshDirectory(context, notifyOfNewUsers);
|
||||
} else {
|
||||
DirectoryHelper.refreshDirectoryFor(context, masterSecret, recipient);
|
||||
DirectoryHelper.refreshDirectoryFor(context, recipient);
|
||||
}
|
||||
} finally {
|
||||
if (wakeLock.isHeld()) wakeLock.release();
|
||||
|
@ -1,103 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MasterSecretDecryptJob extends MasterSecretJob {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TAG = MasterSecretDecryptJob.class.getSimpleName();
|
||||
|
||||
public MasterSecretDecryptJob(Context context) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withRequirement(new MasterSecretRequirement(context))
|
||||
.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) {
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsDatabase.Reader smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret);
|
||||
|
||||
SmsMessageRecord smsRecord;
|
||||
|
||||
while ((smsRecord = smsReader.getNext()) != null) {
|
||||
try {
|
||||
String body = getAsymmetricDecryptedBody(masterSecret, smsRecord.getBody().getBody());
|
||||
smsDatabase.updateMessageBody(new MasterSecretUnion(masterSecret), smsRecord.getId(), body);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||
MmsDatabase.Reader mmsReader = mmsDatabase.getDecryptInProgressMessages(masterSecret);
|
||||
|
||||
MessageRecord mmsRecord;
|
||||
|
||||
while ((mmsRecord = mmsReader.getNext()) != null) {
|
||||
try {
|
||||
String body = getAsymmetricDecryptedBody(masterSecret, mmsRecord.getBody().getBody());
|
||||
mmsDatabase.updateMessageBody(new MasterSecretUnion(masterSecret), mmsRecord.getId(), body);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
smsReader.close();
|
||||
mmsReader.close();
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetryThrowable(Exception exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
|
||||
}
|
||||
|
||||
private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret);
|
||||
AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(asymmetricMasterSecret);
|
||||
|
||||
if (TextUtils.isEmpty(body)) return "";
|
||||
else return asymmetricMasterCipher.decryptBody(body);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -14,7 +14,6 @@ import com.google.android.mms.pdu_alt.RetrieveConf;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@ -50,6 +49,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MmsDownloadJob extends MasterSecretJob {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String TAG = MmsDownloadJob.class.getSimpleName();
|
||||
|
||||
private final long messageId;
|
||||
@ -74,7 +75,7 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
public void onAdded() {
|
||||
if (automatic && KeyCachingService.getMasterSecret(context) == null) {
|
||||
DatabaseFactory.getMmsDatabase(context).markIncomingNotificationReceived(threadId);
|
||||
MessageNotifier.updateNotification(context, null);
|
||||
MessageNotifier.updateNotification(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,19 +121,19 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
throw new MmsException("RetrieveConf was null");
|
||||
}
|
||||
|
||||
storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, retrieveConf, notification.get().getSubscriptionId(), notification.get().getFrom());
|
||||
storeRetrievedMms(contentLocation, messageId, threadId, retrieveConf, notification.get().getSubscriptionId(), notification.get().getFrom());
|
||||
} catch (ApnUnavailableException e) {
|
||||
Log.w(TAG, e);
|
||||
handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
|
||||
handleDownloadError(messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
|
||||
automatic);
|
||||
} catch (MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
handleDownloadError(masterSecret, messageId, threadId,
|
||||
handleDownloadError(messageId, threadId,
|
||||
MmsDatabase.Status.DOWNLOAD_HARD_FAILURE,
|
||||
automatic);
|
||||
} catch (MmsRadioException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
handleDownloadError(masterSecret, messageId, threadId,
|
||||
handleDownloadError(messageId, threadId,
|
||||
MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
|
||||
automatic);
|
||||
} catch (DuplicateMessageException e) {
|
||||
@ -157,7 +158,7 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
|
||||
if (automatic) {
|
||||
database.markIncomingNotificationReceived(threadId);
|
||||
MessageNotifier.updateNotification(context, null, threadId);
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,7 +167,7 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void storeRetrievedMms(MasterSecret masterSecret, String contentLocation,
|
||||
private void storeRetrievedMms(String contentLocation,
|
||||
long messageId, long threadId, RetrieveConf retrieved,
|
||||
int subscriptionId, @Nullable Address notificationFrom)
|
||||
throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException,
|
||||
@ -229,17 +230,15 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
}
|
||||
|
||||
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false);
|
||||
Optional<InsertResult> insertResult = database.insertMessageInbox(new MasterSecretUnion(masterSecret),
|
||||
message, contentLocation, threadId);
|
||||
Optional<InsertResult> insertResult = database.insertMessageInbox(message, contentLocation, threadId);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
database.delete(messageId);
|
||||
MessageNotifier.updateNotification(context, masterSecret, insertResult.get().getThreadId());
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDownloadError(MasterSecret masterSecret, long messageId, long threadId,
|
||||
int downloadStatus, boolean automatic)
|
||||
private void handleDownloadError(long messageId, long threadId, int downloadStatus, boolean automatic)
|
||||
{
|
||||
MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
|
||||
|
||||
@ -247,7 +246,7 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
|
||||
if (automatic) {
|
||||
db.markIncomingNotificationReceived(threadId);
|
||||
MessageNotifier.updateNotification(context, masterSecret, threadId);
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,10 +76,10 @@ public class MmsSendJob extends SendJob {
|
||||
@Override
|
||||
public void onSend(MasterSecret masterSecret) throws MmsException, NoSuchMessageException, IOException {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId);
|
||||
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
|
||||
|
||||
try {
|
||||
SendReq pdu = constructSendPdu(masterSecret, message);
|
||||
SendReq pdu = constructSendPdu(message);
|
||||
|
||||
validateDestinations(message, pdu);
|
||||
|
||||
@ -168,14 +168,14 @@ public class MmsSendJob extends SendJob {
|
||||
}
|
||||
}
|
||||
|
||||
private SendReq constructSendPdu(MasterSecret masterSecret, OutgoingMediaMessage message)
|
||||
private SendReq constructSendPdu(OutgoingMediaMessage message)
|
||||
throws UndeliverableMessageException
|
||||
{
|
||||
SendReq req = new SendReq();
|
||||
String lineNumber = getMyNumber(context);
|
||||
Address destination = message.getRecipient().getAddress();
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(message.getSubscriptionId());
|
||||
List<Attachment> scaledAttachments = scaleAttachments(masterSecret, mediaConstraints, message.getAttachments());
|
||||
List<Attachment> scaledAttachments = scaleAttachments(mediaConstraints, message.getAttachments());
|
||||
|
||||
if (!TextUtils.isEmpty(lineNumber)) {
|
||||
req.setFrom(new EncodedStringValue(lineNumber));
|
||||
@ -241,7 +241,7 @@ public class MmsSendJob extends SendJob {
|
||||
int index = fileName.lastIndexOf(".");
|
||||
String contentId = (index == -1) ? fileName : fileName.substring(0, index);
|
||||
part.setContentId(contentId.getBytes());
|
||||
part.setData(Util.readFully(PartAuthority.getAttachmentStream(context, masterSecret, attachment.getDataUri())));
|
||||
part.setData(Util.readFully(PartAuthority.getAttachmentStream(context, attachment.getDataUri())));
|
||||
|
||||
body.addPart(part);
|
||||
size += getPartSize(part);
|
||||
|
@ -11,15 +11,11 @@ import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
||||
@ -39,7 +35,6 @@ import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
|
||||
@ -67,7 +62,6 @@ import org.whispersystems.libsignal.state.SessionStore;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
@ -124,18 +118,11 @@ public class PushDecryptJob extends ContextJob {
|
||||
return;
|
||||
}
|
||||
|
||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
||||
PushDatabase database = DatabaseFactory.getPushDatabase(context);
|
||||
SignalServiceEnvelope envelope = database.get(messageId);
|
||||
Optional<Long> optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) :
|
||||
Optional.<Long>absent();
|
||||
Optional<Long> optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.absent();
|
||||
|
||||
MasterSecretUnion masterSecretUnion;
|
||||
|
||||
if (masterSecret == null) masterSecretUnion = new MasterSecretUnion(MasterSecretUtil.getAsymmetricMasterSecret(context, null));
|
||||
else masterSecretUnion = new MasterSecretUnion(masterSecret);
|
||||
|
||||
handleMessage(masterSecretUnion, envelope, optionalSmsMessageId);
|
||||
handleMessage(envelope, optionalSmsMessageId);
|
||||
database.delete(messageId);
|
||||
}
|
||||
|
||||
@ -149,7 +136,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
|
||||
}
|
||||
|
||||
private void handleMessage(MasterSecretUnion masterSecret, SignalServiceEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
private void handleMessage(SignalServiceEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
try {
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
||||
@ -161,11 +148,11 @@ public class PushDecryptJob extends ContextJob {
|
||||
if (content.getDataMessage().isPresent()) {
|
||||
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||
|
||||
if (message.isEndSession()) handleEndSessionMessage(masterSecret, envelope, message, smsMessageId);
|
||||
else if (message.isGroupUpdate()) handleGroupMessage(masterSecret, envelope, message, smsMessageId);
|
||||
else if (message.isExpirationUpdate()) handleExpirationUpdate(masterSecret, envelope, message, smsMessageId);
|
||||
else if (message.getAttachments().isPresent()) handleMediaMessage(masterSecret, envelope, message, smsMessageId);
|
||||
else if (message.getBody().isPresent()) handleTextMessage(masterSecret, envelope, message, smsMessageId);
|
||||
if (message.isEndSession()) handleEndSessionMessage(envelope, message, smsMessageId);
|
||||
else if (message.isGroupUpdate()) handleGroupMessage(envelope, message, smsMessageId);
|
||||
else if (message.isExpirationUpdate()) handleExpirationUpdate(envelope, message, smsMessageId);
|
||||
else if (message.getAttachments().isPresent()) handleMediaMessage(envelope, message, smsMessageId);
|
||||
else if (message.getBody().isPresent()) handleTextMessage(envelope, message, smsMessageId);
|
||||
|
||||
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) {
|
||||
handleUnknownGroupMessage(envelope, message.getGroupInfo().get());
|
||||
@ -177,10 +164,10 @@ public class PushDecryptJob extends ContextJob {
|
||||
} else if (content.getSyncMessage().isPresent()) {
|
||||
SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
|
||||
|
||||
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(masterSecret, envelope, syncMessage.getSent().get());
|
||||
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(masterSecret, syncMessage.getRequest().get());
|
||||
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(masterSecret, syncMessage.getRead().get(), envelope.getTimestamp());
|
||||
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(masterSecret, syncMessage.getVerified().get());
|
||||
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(envelope, syncMessage.getSent().get());
|
||||
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get());
|
||||
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), envelope.getTimestamp());
|
||||
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
|
||||
else Log.w(TAG, "Contains no known sync types...");
|
||||
} else if (content.getCallMessage().isPresent()) {
|
||||
Log.w(TAG, "Got call message...");
|
||||
@ -205,22 +192,22 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w(TAG, e);
|
||||
handleInvalidVersionMessage(masterSecret, envelope, smsMessageId);
|
||||
handleInvalidVersionMessage(envelope, smsMessageId);
|
||||
} catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException e) {
|
||||
Log.w(TAG, e);
|
||||
handleCorruptMessage(masterSecret, envelope, smsMessageId);
|
||||
handleCorruptMessage(envelope, smsMessageId);
|
||||
} catch (NoSessionException e) {
|
||||
Log.w(TAG, e);
|
||||
handleNoSessionMessage(masterSecret, envelope, smsMessageId);
|
||||
handleNoSessionMessage(envelope, smsMessageId);
|
||||
} catch (LegacyMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
handleLegacyMessage(masterSecret, envelope, smsMessageId);
|
||||
handleLegacyMessage(envelope, smsMessageId);
|
||||
} catch (DuplicateMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
handleDuplicateMessage(masterSecret, envelope, smsMessageId);
|
||||
handleDuplicateMessage(envelope, smsMessageId);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w(TAG, e);
|
||||
handleUntrustedIdentityMessage(masterSecret, envelope, smsMessageId);
|
||||
handleUntrustedIdentityMessage(envelope, smsMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,22 +291,21 @@ public class PushDecryptJob extends ContextJob {
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
private void handleEndSessionMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleEndSessionMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
envelope.getSourceDevice(),
|
||||
message.getTimestamp(),
|
||||
"", Optional.<SignalServiceGroup>absent(), 0);
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
envelope.getSourceDevice(),
|
||||
message.getTimestamp(),
|
||||
"", Optional.absent(), 0);
|
||||
|
||||
Long threadId;
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage);
|
||||
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(masterSecret, incomingEndSessionMessage);
|
||||
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(incomingEndSessionMessage);
|
||||
|
||||
if (insertResult.isPresent()) threadId = insertResult.get().getThreadId();
|
||||
else threadId = null;
|
||||
@ -333,14 +319,13 @@ public class PushDecryptJob extends ContextJob {
|
||||
sessionStore.deleteAllSessions(envelope.getSource());
|
||||
|
||||
SecurityEvent.broadcastSecurityUpdateEvent(context);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), threadId);
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
private long handleSynchronizeSentEndSessionMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message)
|
||||
private long handleSynchronizeSentEndSessionMessage(@NonNull SentTranscriptMessage message)
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
Recipient recipient = getSyncMessageDestination(message);
|
||||
OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipient, "", -1);
|
||||
OutgoingEndSessionMessage outgoingEndSessionMessage = new OutgoingEndSessionMessage(outgoingTextMessage);
|
||||
@ -353,20 +338,20 @@ public class PushDecryptJob extends ContextJob {
|
||||
|
||||
SecurityEvent.broadcastSecurityUpdateEvent(context);
|
||||
|
||||
long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingEndSessionMessage,
|
||||
false, message.getTimestamp(), null);
|
||||
long messageId = database.insertMessageOutbox(threadId, outgoingEndSessionMessage,
|
||||
false, message.getTimestamp(),
|
||||
null);
|
||||
database.markAsSent(messageId, true);
|
||||
}
|
||||
|
||||
return threadId;
|
||||
}
|
||||
|
||||
private void handleGroupMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleGroupMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
GroupMessageProcessor.process(context, masterSecret, envelope, message, false);
|
||||
GroupMessageProcessor.process(context, envelope, message, false);
|
||||
|
||||
if (smsMessageId.isPresent()) {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
|
||||
@ -381,25 +366,23 @@ public class PushDecryptJob extends ContextJob {
|
||||
.add(new RequestGroupInfoJob(context, envelope.getSource(), group.getGroupId()));
|
||||
}
|
||||
|
||||
private void handleExpirationUpdate(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleExpirationUpdate(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Recipient recipient = getMessageDestination(envelope, message);
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret,
|
||||
Address.fromExternal(context, envelope.getSource()),
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000, true,
|
||||
Optional.fromNullable(envelope.getRelay()),
|
||||
Optional.<String>absent(), message.getGroupInfo(),
|
||||
Optional.<List<SignalServiceAttachment>>absent());
|
||||
Optional.absent(), message.getGroupInfo(),
|
||||
Optional.absent());
|
||||
|
||||
|
||||
|
||||
database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1);
|
||||
database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
|
||||
DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, message.getExpiresInSeconds());
|
||||
|
||||
@ -408,14 +391,11 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeVerifiedMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull VerifiedMessage verifiedMessage)
|
||||
{
|
||||
IdentityUtil.processVerifiedMessage(context, masterSecret, verifiedMessage);
|
||||
private void handleSynchronizeVerifiedMessage(@NonNull VerifiedMessage verifiedMessage) {
|
||||
IdentityUtil.processVerifiedMessage(context, verifiedMessage);
|
||||
}
|
||||
|
||||
private void handleSynchronizeSentMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleSynchronizeSentMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SentTranscriptMessage message)
|
||||
throws MmsException
|
||||
{
|
||||
@ -424,15 +404,15 @@ public class PushDecryptJob extends ContextJob {
|
||||
Long threadId;
|
||||
|
||||
if (message.getMessage().isEndSession()) {
|
||||
threadId = handleSynchronizeSentEndSessionMessage(masterSecret, message);
|
||||
threadId = handleSynchronizeSentEndSessionMessage(message);
|
||||
} else if (message.getMessage().isGroupUpdate()) {
|
||||
threadId = GroupMessageProcessor.process(context, masterSecret, envelope, message.getMessage(), true);
|
||||
threadId = GroupMessageProcessor.process(context, envelope, message.getMessage(), true);
|
||||
} else if (message.getMessage().isExpirationUpdate()) {
|
||||
threadId = handleSynchronizeSentExpirationUpdate(masterSecret, message);
|
||||
threadId = handleSynchronizeSentExpirationUpdate(message);
|
||||
} else if (message.getMessage().getAttachments().isPresent()) {
|
||||
threadId = handleSynchronizeSentMediaMessage(masterSecret, message);
|
||||
threadId = handleSynchronizeSentMediaMessage(message);
|
||||
} else {
|
||||
threadId = handleSynchronizeSentTextMessage(masterSecret, message);
|
||||
threadId = handleSynchronizeSentTextMessage(message);
|
||||
}
|
||||
|
||||
if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false))) {
|
||||
@ -453,14 +433,13 @@ public class PushDecryptJob extends ContextJob {
|
||||
|
||||
if (threadId != null) {
|
||||
DatabaseFactory.getThreadDatabase(getContext()).setRead(threadId, true);
|
||||
MessageNotifier.updateNotification(getContext(), masterSecret.getMasterSecret().orNull());
|
||||
MessageNotifier.updateNotification(getContext());
|
||||
}
|
||||
|
||||
MessageNotifier.setLastDesktopActivityTimestamp(message.getTimestamp());
|
||||
}
|
||||
|
||||
private void handleSynchronizeRequestMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull RequestMessage message)
|
||||
private void handleSynchronizeRequestMessage(@NonNull RequestMessage message)
|
||||
{
|
||||
if (message.isContactsRequest()) {
|
||||
ApplicationContext.getInstance(context)
|
||||
@ -487,9 +466,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeReadMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull List<ReadMessage> readMessages,
|
||||
long envelopeTimestamp)
|
||||
private void handleSynchronizeReadMessage(@NonNull List<ReadMessage> readMessages, long envelopeTimestamp)
|
||||
{
|
||||
for (ReadMessage readMessage : readMessages) {
|
||||
List<Pair<Long, Long>> expiringText = DatabaseFactory.getSmsDatabase(context).setTimestampRead(new SyncMessageId(Address.fromExternal(context, readMessage.getSender()), readMessage.getTimestamp()), envelopeTimestamp);
|
||||
@ -510,19 +487,17 @@ public class PushDecryptJob extends ContextJob {
|
||||
|
||||
MessageNotifier.setLastDesktopActivityTimestamp(envelopeTimestamp);
|
||||
MessageNotifier.cancelDelayedNotifications();
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull());
|
||||
MessageNotifier.updateNotification(context);
|
||||
}
|
||||
|
||||
private void handleMediaMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleMediaMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Recipient recipient = getMessageDestination(envelope, message);
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret,
|
||||
Address.fromExternal(context, envelope.getSource()),
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000, false,
|
||||
Optional.fromNullable(envelope.getRelay()),
|
||||
@ -531,36 +506,29 @@ public class PushDecryptJob extends ContextJob {
|
||||
message.getAttachments());
|
||||
|
||||
if (message.getExpiresInSeconds() != recipient.getExpireMessages()) {
|
||||
handleExpirationUpdate(masterSecret, envelope, message, Optional.<Long>absent());
|
||||
handleExpirationUpdate(envelope, message, Optional.absent());
|
||||
}
|
||||
|
||||
Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1);
|
||||
Optional<InsertResult> insertResult = database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
List<DatabaseAttachment> attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(null, insertResult.get().getMessageId());
|
||||
List<DatabaseAttachment> attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId());
|
||||
|
||||
for (DatabaseAttachment attachment : attachments) {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new AttachmentDownloadJob(context, insertResult.get().getMessageId(), attachment.getAttachmentId(), false));
|
||||
|
||||
if (!masterSecret.getMasterSecret().isPresent()) {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new AttachmentFileNameJob(context, masterSecret.getAsymmetricMasterSecret().get(), attachment, mediaMessage));
|
||||
}
|
||||
}
|
||||
|
||||
if (smsMessageId.isPresent()) {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
|
||||
}
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId());
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
}
|
||||
|
||||
private long handleSynchronizeSentExpirationUpdate(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message)
|
||||
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
@ -571,7 +539,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
message.getMessage().getExpiresInSeconds() * 1000);
|
||||
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||
long messageId = database.insertMessageOutbox(masterSecret, expirationUpdateMessage, threadId, false, null);
|
||||
long messageId = database.insertMessageOutbox(expirationUpdateMessage, threadId, false, null);
|
||||
|
||||
database.markAsSent(messageId, true);
|
||||
|
||||
@ -580,14 +548,13 @@ public class PushDecryptJob extends ContextJob {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
private long handleSynchronizeSentMediaMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message)
|
||||
private long handleSynchronizeSentMediaMessage(@NonNull SentTranscriptMessage message)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Recipient recipients = getSyncMessageDestination(message);
|
||||
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipients, message.getMessage().getBody().orNull(),
|
||||
PointerAttachment.forPointers(masterSecret, message.getMessage().getAttachments()),
|
||||
PointerAttachment.forPointers(message.getMessage().getAttachments()),
|
||||
message.getTimestamp(), -1,
|
||||
message.getMessage().getExpiresInSeconds() * 1000,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT);
|
||||
@ -595,15 +562,15 @@ public class PushDecryptJob extends ContextJob {
|
||||
mediaMessage = new OutgoingSecureMediaMessage(mediaMessage);
|
||||
|
||||
if (recipients.getExpireMessages() != message.getMessage().getExpiresInSeconds()) {
|
||||
handleSynchronizeSentExpirationUpdate(masterSecret, message);
|
||||
handleSynchronizeSentExpirationUpdate(message);
|
||||
}
|
||||
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
long messageId = database.insertMessageOutbox(masterSecret, mediaMessage, threadId, false, null);
|
||||
long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, null);
|
||||
|
||||
database.markAsSent(messageId, true);
|
||||
|
||||
for (DatabaseAttachment attachment : DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(null, messageId)) {
|
||||
for (DatabaseAttachment attachment : DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageId)) {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new AttachmentDownloadJob(context, messageId, attachment.getAttachmentId(), false));
|
||||
@ -621,24 +588,23 @@ public class PushDecryptJob extends ContextJob {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
private void handleTextMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleTextMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String body = message.getBody().isPresent() ? message.getBody().get() : "";
|
||||
Recipient recipient = getMessageDestination(envelope, message);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
String body = message.getBody().isPresent() ? message.getBody().get() : "";
|
||||
Recipient recipient = getMessageDestination(envelope, message);
|
||||
|
||||
if (message.getExpiresInSeconds() != recipient.getExpireMessages()) {
|
||||
handleExpirationUpdate(masterSecret, envelope, message, Optional.<Long>absent());
|
||||
handleExpirationUpdate(envelope, message, Optional.absent());
|
||||
}
|
||||
|
||||
Long threadId;
|
||||
|
||||
if (smsMessageId.isPresent() && !message.getGroupInfo().isPresent()) {
|
||||
threadId = database.updateBundleMessageBody(masterSecret, smsMessageId.get(), body).second;
|
||||
threadId = database.updateBundleMessageBody(smsMessageId.get(), body).second;
|
||||
} else {
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
envelope.getSourceDevice(),
|
||||
@ -647,7 +613,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
message.getExpiresInSeconds() * 1000);
|
||||
|
||||
textMessage = new IncomingEncryptedMessage(textMessage, body);
|
||||
Optional<InsertResult> insertResult = database.insertMessageInbox(masterSecret, textMessage);
|
||||
Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage);
|
||||
|
||||
if (insertResult.isPresent()) threadId = insertResult.get().getThreadId();
|
||||
else threadId = null;
|
||||
@ -656,12 +622,11 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
|
||||
if (threadId != null) {
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), threadId);
|
||||
MessageNotifier.updateNotification(context, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
private long handleSynchronizeSentTextMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message)
|
||||
private long handleSynchronizeSentTextMessage(@NonNull SentTranscriptMessage message)
|
||||
throws MmsException
|
||||
{
|
||||
|
||||
@ -670,7 +635,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
long expiresInMillis = message.getMessage().getExpiresInSeconds() * 1000;
|
||||
|
||||
if (recipient.getExpireMessages() != message.getMessage().getExpiresInSeconds()) {
|
||||
handleSynchronizeSentExpirationUpdate(masterSecret, message);
|
||||
handleSynchronizeSentExpirationUpdate(message);
|
||||
}
|
||||
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
||||
@ -682,12 +647,12 @@ public class PushDecryptJob extends ContextJob {
|
||||
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT);
|
||||
outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage);
|
||||
|
||||
messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(masterSecret, outgoingMediaMessage, threadId, false, null);
|
||||
messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null);
|
||||
database = DatabaseFactory.getMmsDatabase(context);
|
||||
} else {
|
||||
OutgoingTextMessage outgoingTextMessage = new OutgoingEncryptedMessage(recipient, body, expiresInMillis);
|
||||
|
||||
messageId = DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageOutbox(masterSecret, threadId, outgoingTextMessage, false, message.getTimestamp(), null);
|
||||
messageId = DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoingTextMessage, false, message.getTimestamp(), null);
|
||||
database = DatabaseFactory.getSmsDatabase(context);
|
||||
}
|
||||
|
||||
@ -703,80 +668,76 @@ public class PushDecryptJob extends ContextJob {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
private void handleInvalidVersionMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleInvalidVersionMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Optional<InsertResult> insertResult = insertPlaceholder(envelope);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId());
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId());
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
} else {
|
||||
smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCorruptMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleCorruptMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Optional<InsertResult> insertResult = insertPlaceholder(envelope);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId());
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId());
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
} else {
|
||||
smsDatabase.markAsDecryptFailed(smsMessageId.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNoSessionMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleNoSessionMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Optional<InsertResult> insertResult = insertPlaceholder(envelope);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
smsDatabase.markAsNoSession(insertResult.get().getMessageId());
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId());
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
} else {
|
||||
smsDatabase.markAsNoSession(smsMessageId.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLegacyMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleLegacyMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Optional<InsertResult> insertResult = insertPlaceholder(envelope);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
smsDatabase.markAsLegacyVersion(insertResult.get().getMessageId());
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId());
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
} else {
|
||||
smsDatabase.markAsLegacyVersion(smsMessageId.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDuplicateMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@SuppressWarnings("unused")
|
||||
private void handleDuplicateMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
// Let's start ignoring these now
|
||||
@ -791,33 +752,32 @@ public class PushDecryptJob extends ContextJob {
|
||||
// }
|
||||
}
|
||||
|
||||
private void handleUntrustedIdentityMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
private void handleUntrustedIdentityMessage(@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
try {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
Address sourceAddress = Address.fromExternal(context, envelope.getSource());
|
||||
byte[] serialized = envelope.hasLegacyMessage() ? envelope.getLegacyMessage() : envelope.getContent();
|
||||
PreKeySignalMessage whisperMessage = new PreKeySignalMessage(serialized);
|
||||
IdentityKey identityKey = whisperMessage.getIdentityKey();
|
||||
String encoded = Base64.encodeBytes(serialized);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
Address sourceAddress = Address.fromExternal(context, envelope.getSource());
|
||||
byte[] serialized = envelope.hasLegacyMessage() ? envelope.getLegacyMessage() : envelope.getContent();
|
||||
PreKeySignalMessage whisperMessage = new PreKeySignalMessage(serialized);
|
||||
IdentityKey identityKey = whisperMessage.getIdentityKey();
|
||||
String encoded = Base64.encodeBytes(serialized);
|
||||
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(sourceAddress,
|
||||
envelope.getSourceDevice(),
|
||||
envelope.getTimestamp(), encoded,
|
||||
Optional.<SignalServiceGroup>absent(), 0);
|
||||
Optional.absent(), 0);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded, envelope.hasLegacyMessage());
|
||||
Optional<InsertResult> insertResult = database.insertMessageInbox(masterSecret, bundleMessage);
|
||||
Optional<InsertResult> insertResult = database.insertMessageInbox(bundleMessage);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
database.setMismatchedIdentity(insertResult.get().getMessageId(), sourceAddress, identityKey);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), insertResult.get().getThreadId());
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
} else {
|
||||
database.updateMessageBody(masterSecret, smsMessageId.get(), encoded);
|
||||
database.updateMessageBody(smsMessageId.get(), encoded);
|
||||
database.markAsPreKeyBundle(smsMessageId.get());
|
||||
database.setMismatchedIdentity(smsMessageId.get(), sourceAddress, identityKey);
|
||||
}
|
||||
@ -863,11 +823,11 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
|
||||
private Optional<InsertResult> insertPlaceholder(@NonNull SignalServiceEnvelope envelope) {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
envelope.getSourceDevice(),
|
||||
envelope.getTimestamp(), "",
|
||||
Optional.<SignalServiceGroup>absent(), 0);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromExternal(context, envelope.getSource()),
|
||||
envelope.getSourceDevice(),
|
||||
envelope.getTimestamp(), "",
|
||||
Optional.absent(), 0);
|
||||
|
||||
textMessage = new IncomingEncryptedMessage(textMessage, "");
|
||||
return database.insertMessageInbox(textMessage);
|
||||
|
@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
@ -82,10 +81,10 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
throws MmsException, IOException, NoSuchMessageException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId);
|
||||
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
|
||||
|
||||
try {
|
||||
deliver(masterSecret, message, filterAddress == null ? null : Address.fromSerialized(filterAddress));
|
||||
deliver(message, filterAddress == null ? null : Address.fromSerialized(filterAddress));
|
||||
|
||||
database.markAsSent(messageId, true);
|
||||
markAttachmentsUploaded(messageId, message.getAttachments());
|
||||
@ -135,7 +134,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
|
||||
}
|
||||
|
||||
private void deliver(MasterSecret masterSecret, OutgoingMediaMessage message, @Nullable Address filterAddress)
|
||||
private void deliver(OutgoingMediaMessage message, @Nullable Address filterAddress)
|
||||
throws IOException, RecipientFormattingException, InvalidNumberException,
|
||||
EncapsulatedExceptions, UndeliverableMessageException
|
||||
{
|
||||
@ -143,8 +142,8 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
|
||||
List<Address> recipients = getGroupMessageRecipients(groupId, messageId);
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
List<Attachment> scaledAttachments = scaleAttachments(masterSecret, mediaConstraints, message.getAttachments());
|
||||
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(masterSecret, scaledAttachments);
|
||||
List<Attachment> scaledAttachments = scaleAttachments(mediaConstraints, message.getAttachments());
|
||||
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(scaledAttachments);
|
||||
|
||||
List<SignalServiceAddress> addresses;
|
||||
|
||||
|
@ -59,10 +59,10 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
{
|
||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId);
|
||||
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
|
||||
|
||||
try {
|
||||
deliver(masterSecret, message);
|
||||
deliver(message);
|
||||
database.markAsSent(messageId, true);
|
||||
markAttachmentsUploaded(messageId, message.getAttachments());
|
||||
|
||||
@ -97,7 +97,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||
}
|
||||
|
||||
private void deliver(MasterSecret masterSecret, OutgoingMediaMessage message)
|
||||
private void deliver(OutgoingMediaMessage message)
|
||||
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
|
||||
UndeliverableMessageException
|
||||
{
|
||||
@ -108,8 +108,8 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
try {
|
||||
SignalServiceAddress address = getPushAddress(message.getRecipient().getAddress());
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
List<Attachment> scaledAttachments = scaleAttachments(masterSecret, mediaConstraints, message.getAttachments());
|
||||
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(masterSecret, scaledAttachments);
|
||||
List<Attachment> scaledAttachments = scaleAttachments(mediaConstraints, message.getAttachments());
|
||||
List<SignalServiceAttachment> attachmentStreams = getAttachmentsFor(scaledAttachments);
|
||||
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
|
||||
SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder()
|
||||
.withBody(message.getBody())
|
||||
|
@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.whispersystems.jobqueue.JobManager;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
@ -29,7 +28,7 @@ public abstract class PushReceivedJob extends ContextJob {
|
||||
|
||||
if (!isActiveNumber(recipient)) {
|
||||
DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED);
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context, KeyCachingService.getMasterSecret(context), recipient, false));
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context, recipient, false));
|
||||
}
|
||||
|
||||
if (envelope.isReceipt()) {
|
||||
|
@ -80,13 +80,13 @@ public abstract class PushSendJob extends SendJob {
|
||||
return new SignalServiceAddress(address.toPhoneString(), Optional.fromNullable(relay));
|
||||
}
|
||||
|
||||
protected List<SignalServiceAttachment> getAttachmentsFor(MasterSecret masterSecret, List<Attachment> parts) {
|
||||
protected List<SignalServiceAttachment> getAttachmentsFor(List<Attachment> parts) {
|
||||
List<SignalServiceAttachment> attachments = new LinkedList<>();
|
||||
|
||||
for (final Attachment attachment : parts) {
|
||||
try {
|
||||
if (attachment.getDataUri() == null || attachment.getSize() == 0) throw new IOException("Assertion failed, outgoing attachment has no data!");
|
||||
InputStream is = PartAuthority.getAttachmentStream(context, masterSecret, attachment.getDataUri());
|
||||
InputStream is = PartAuthority.getAttachmentStream(context, attachment.getDataUri());
|
||||
attachments.add(SignalServiceAttachment.newStreamBuilder()
|
||||
.withStream(is)
|
||||
.withContentType(attachment.getContentType())
|
||||
|
@ -7,8 +7,8 @@ import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
@ -48,8 +48,8 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
@Override
|
||||
public void onPushSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
|
||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(context).getExpiringMessageManager();
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(messageId);
|
||||
|
||||
try {
|
||||
Log.w(TAG, "Sending message: " + messageId);
|
||||
|
@ -22,6 +22,7 @@ import java.util.List;
|
||||
|
||||
public abstract class SendJob extends MasterSecretJob {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final static String TAG = SendJob.class.getSimpleName();
|
||||
|
||||
public SendJob(Context context, JobParameters parameters) {
|
||||
@ -49,8 +50,7 @@ public abstract class SendJob extends MasterSecretJob {
|
||||
}
|
||||
}
|
||||
|
||||
protected List<Attachment> scaleAttachments(@NonNull MasterSecret masterSecret,
|
||||
@NonNull MediaConstraints constraints,
|
||||
protected List<Attachment> scaleAttachments(@NonNull MediaConstraints constraints,
|
||||
@NonNull List<Attachment> attachments)
|
||||
throws UndeliverableMessageException
|
||||
{
|
||||
@ -59,11 +59,11 @@ public abstract class SendJob extends MasterSecretJob {
|
||||
|
||||
for (Attachment attachment : attachments) {
|
||||
try {
|
||||
if (constraints.isSatisfied(context, masterSecret, attachment)) {
|
||||
if (constraints.isSatisfied(context, attachment)) {
|
||||
results.add(attachment);
|
||||
} else if (constraints.canResize(attachment)) {
|
||||
MediaStream resized = constraints.getResizedMedia(context, masterSecret, attachment);
|
||||
results.add(attachmentDatabase.updateAttachmentData(masterSecret, attachment, resized));
|
||||
MediaStream resized = constraints.getResizedMedia(context, attachment);
|
||||
results.add(attachmentDatabase.updateAttachmentData(attachment, resized));
|
||||
} else {
|
||||
throw new UndeliverableMessageException("Size constraints could not be met!");
|
||||
}
|
||||
|
@ -6,15 +6,11 @@ import android.support.annotation.Nullable;
|
||||
import android.telephony.SmsMessage;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
@ -48,22 +44,13 @@ public class SmsReceiveJob extends ContextJob {
|
||||
public void onRun() {
|
||||
Log.w(TAG, "onRun()");
|
||||
|
||||
Optional<IncomingTextMessage> message = assembleMessageFragments(pdus, subscriptionId);
|
||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
||||
|
||||
MasterSecretUnion masterSecretUnion;
|
||||
|
||||
if (masterSecret == null) {
|
||||
masterSecretUnion = new MasterSecretUnion(MasterSecretUtil.getAsymmetricMasterSecret(context, null));
|
||||
} else {
|
||||
masterSecretUnion = new MasterSecretUnion(masterSecret);
|
||||
}
|
||||
Optional<IncomingTextMessage> message = assembleMessageFragments(pdus, subscriptionId);
|
||||
|
||||
if (message.isPresent() && !isBlocked(message.get())) {
|
||||
Optional<InsertResult> insertResult = storeMessage(masterSecretUnion, message.get());
|
||||
Optional<InsertResult> insertResult = storeMessage(message.get());
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
MessageNotifier.updateNotification(context, masterSecret, insertResult.get().getThreadId());
|
||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||
}
|
||||
} else if (message.isPresent()) {
|
||||
Log.w(TAG, "*** Received blocked SMS, ignoring...");
|
||||
@ -91,8 +78,8 @@ public class SmsReceiveJob extends ContextJob {
|
||||
return false;
|
||||
}
|
||||
|
||||
private Optional<InsertResult> storeMessage(MasterSecretUnion masterSecret, IncomingTextMessage message) {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
private Optional<InsertResult> storeMessage(IncomingTextMessage message) {
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
|
||||
if (message.isSecureMessage()) {
|
||||
IncomingTextMessage placeholder = new IncomingTextMessage(message, "");
|
||||
@ -101,7 +88,7 @@ public class SmsReceiveJob extends ContextJob {
|
||||
|
||||
return insertResult;
|
||||
} else {
|
||||
return database.insertMessageInbox(masterSecret, message);
|
||||
return database.insertMessageInbox(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,8 @@ import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.NetworkOrServiceRequirement;
|
||||
@ -43,8 +43,8 @@ public class SmsSendJob extends SendJob {
|
||||
|
||||
@Override
|
||||
public void onSend(MasterSecret masterSecret) throws NoSuchMessageException {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(messageId);
|
||||
|
||||
try {
|
||||
Log.w(TAG, "Sending message: " + messageId);
|
||||
|
@ -8,8 +8,8 @@ import android.util.Log;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
@ -46,7 +46,7 @@ public class SmsSentJob extends MasterSecretJob {
|
||||
|
||||
switch (action) {
|
||||
case SmsDeliveryListener.SENT_SMS_ACTION:
|
||||
handleSentResult(masterSecret, messageId, result);
|
||||
handleSentResult(messageId, result);
|
||||
break;
|
||||
case SmsDeliveryListener.DELIVERED_SMS_ACTION:
|
||||
handleDeliveredResult(messageId, result);
|
||||
@ -65,13 +65,13 @@ public class SmsSentJob extends MasterSecretJob {
|
||||
}
|
||||
|
||||
private void handleDeliveredResult(long messageId, int result) {
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).markStatus(messageId, result);
|
||||
DatabaseFactory.getSmsDatabase(context).markStatus(messageId, result);
|
||||
}
|
||||
|
||||
private void handleSentResult(MasterSecret masterSecret, long messageId, int result) {
|
||||
private void handleSentResult(long messageId, int result) {
|
||||
try {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(messageId);
|
||||
|
||||
switch (result) {
|
||||
case Activity.RESULT_OK:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user