This commit is contained in:
Ryan ZHAO 2020-12-18 16:53:05 +11:00
commit 80fa37e1a7
110 changed files with 1573 additions and 8367 deletions

54
TRANSLATION.md Normal file
View File

@ -0,0 +1,54 @@
## How to translate Session into new languages quickly and easily
There are people all around the world who can benefit from access to Session — and that means we need to make Session available in as many languages as possible. Weve had a number of requests from community members wanting to help out by translating Session into currently unsupported (or only partially supported) languages. If you speak multiple languages and you want to help us make Session available in another language you speak, this guide will show you how to translate Sessions iOS and Android apps quickly and easily.
### Translating Session for iOS:
Translating Session iOS into a new language is easy! Youll need a web browser, a plaintext editor (TextEdit on macOS or Notepad on Windows will do), and a little bit of time and patience.
#### Step 1: Retrieving English strings
- Go to [Sessions iOS GitHub page](https://github.com/loki-project/session-ios)
- In the list of folders and files, click **Session**
- Click **Meta**
- Click **Translations**
- Click **en.lproj**
- Click **localizable.strings**
- Scroll down to Line #2557 (using the line numbers on the left-hand side of the text). This line should contain the text `// MARK: - Session` (note: if this line does not contain that text, use Ctrl+F, Cmd+F, or your browsers Find on Page function to find the line which contains that text)
- Select and copy all text from that line down to the end of the file
- Open a plaintext editor (TextEdit on macOS, Notepad on Windows, or your preferred plaintext editor) and create a new file
- Paste the text you copied earlier into this blank file
#### Step 2: Translate!
This file will now have a large number of lines of code. Each line contains 2 sets of information, both in quotation marks, like this:
`"continue_2" = "Continue";`
For each line of code, translate the word or sentence to the **right** of the equals sign into the language you are translating into. Do **not** edit the text to the **left** of the equals sign.
Once you are finished translating, save the file with a filename that specifies which language you have translated the app into, and email the file to us at support@getsession.org along with information about the language you have translated the app into. Be sure to specify in your email that you have translated the iOS app (for information on translating the Android app, see below). Our developers will then take your translation and apply it to the Session iOS app. Thank you for helping make Session more accessible for everyone!
### Translating for Android:
Its just as easy to add new translations on Session Android! Once again, youll need a web browser, a plaintext editor (TextEdit on macOS or Notepad on Windows will do), and a little bit of time and patience.
#### Step 1: Retrieving English strings
- Go to [Sessions Android GitHub page](https://github.com/loki-project/session-android)
- In the list of files and folders, click **res**
- Click **values** (you will need to scroll down to find this folder; make sure you click the folder named **values** and not any of the folders named **values-xx** or with other suffixes)
- Click **strings.xml**
- Scroll down to Line #1657 (using the line numbers on the left-hand side of the text). This line should contain the text `<!-- Session -->` (note: if this line does not contain that text, use Ctrl+F, Cmd+F, or your browsers Find on Page function to find the line which contains that text)
- Select and copy all text from that line down to the end of the file
- Open a plaintext editor (TextEdit on macOS, Notepad on Windows, or your preferred plaintext editor) and create a new file
- Paste the text you copied earlier into this blank file
#### Step 2: Translate!
This file will now have a large number of lines of code. Each line will contain `<string name=”xxx”>` and `</string>` tags. To translate Session Android, translate the text between these tags. For example:
`<string name="continue_2">Continue</string>`
In this line, translate the word **Continue** into the language you are translating into. Do not translate any other text. Do **not** translate the text inside either pair of angled brackets <>.
Translate the word or sentence between each pair of `<string></string>` tags, on each line.
Once you are finished translating, save the file with a filename that specifies which language you have translated the app into, and email the file to us at support@getsession.org along with information about the language you have translated the app into. Be sure to specify in your email that you have translated the Android app (for information on translating the iOS app, see above). Our developers will then take your translation and apply it to the Session Android app. Thank you for helping make Session more accessible for everyone!

View File

@ -278,11 +278,6 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar" />
<activity
android:name="org.thoughtcrime.securesms.ExperienceUpgradeActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<activity
android:name="org.thoughtcrime.securesms.PassphraseCreateActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@ -312,13 +307,6 @@
android:screenOrientation="portrait"
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="org.thoughtcrime.securesms.PassphraseChangeActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:label="@string/AndroidManifest__change_passphrase" />
<activity
android:name="org.thoughtcrime.securesms.VerifyIdentityActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity
android:name="org.thoughtcrime.securesms.stickers.StickerManagementActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@ -483,11 +471,6 @@
<action android:name="network.loki.securesms.RESTART" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.service.RotateSenderCertificateListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.service.LocalBackupListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />

View File

@ -38,7 +38,6 @@ import org.signal.aesgcmprovider.AesGcmProvider;
import org.thoughtcrime.securesms.components.TypingStatusRepository;
import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address;
@ -53,7 +52,6 @@ import org.thoughtcrime.securesms.jobs.FastJobStorage;
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
import org.thoughtcrime.securesms.logging.Log;
@ -84,7 +82,6 @@ import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@ -112,7 +109,6 @@ import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeys
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysImplementationDelegate;
import org.session.libsignal.service.loki.protocol.mentions.MentionsManager;
import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol;
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol;
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocolDelegate;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink;
@ -227,7 +223,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
initializePeriodicTasks();
initializeWebRtc();
initializePendingMessages();
initializeUnidentifiedDeliveryAbilityRefresh();
initializeBlobProvider();
}
@ -376,7 +371,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private void initializePeriodicTasks() {
LocalBackupListener.schedule(this);
RotateSenderCertificateListener.schedule(this);
BackgroundPollWorker.schedulePeriodic(this); // Loki
if (BuildConfig.PLAY_STORE_DISABLED) {
@ -427,12 +421,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
}
private void initializeUnidentifiedDeliveryAbilityRefresh() {
if (TextSecurePreferences.isMultiDevice(this) && !TextSecurePreferences.isUnidentifiedDeliveryEnabled(this)) {
jobManager.add(new RefreshUnidentifiedDeliveryAbilityJob());
}
}
private void initializeBlobProvider() {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
BlobProvider.getInstance().onSessionStart(this);
@ -574,7 +562,16 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
});
}
public void clearData() {
//FIXME AC: Using this method to cleanup app data is unsafe due to potential concurrent
// activity that still might be using the data that is being deleted here.
// The most reliable and safe way to do this is to use official API call:
// https://developer.android.com/reference/android/app/ActivityManager.html#clearApplicationUserData()
// The downside is it kills the app in the process and there's no any conventional way to start
// another activity when the task is done.
// Dev community is in demand for such a feature, so check on it some time in the feature
// and replace our implementation with the API call when it's safe to do so.
// Here's a feature request related https://issuetracker.google.com/issues/174903931
public void clearAllData() {
String token = TextSecurePreferences.getFCMToken(this);
if (token != null && !token.isEmpty()) {
LokiPushNotificationManager.unregister(token, this);
@ -582,7 +579,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
boolean wasUnlinked = TextSecurePreferences.getWasUnlinked(this);
TextSecurePreferences.clearAll(this);
TextSecurePreferences.setWasUnlinked(this, wasUnlinked);
MasterSecretUtil.clear(this);
// MasterSecretUtil.clear(this);
if (!deleteDatabase("signal.db")) {
Log.d("Loki", "Failed to delete database.");
}

View File

@ -1,201 +0,0 @@
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.AsyncTask;
import androidx.appcompat.app.AlertDialog;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.VerifySpan;
import org.session.libsignal.libsignal.SignalProtocolAddress;
import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.internal.push.SignalServiceProtos;
import java.io.IOException;
import network.loki.messenger.R;
import static org.session.libsignal.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,
MessageRecord messageRecord,
IdentityKeyMismatch mismatch)
{
super(context);
Recipient recipient = Recipient.from(context, mismatch.getAddress(), false);
String name = recipient.toShortString();
String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name);
SpannableString spannableString = new SpannableString(introduction + " " +
context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact));
spannableString.setSpan(new VerifySpan(context, mismatch),
introduction.length()+1, spannableString.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setTitle(name);
setMessage(spannableString);
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());
}
@Override
public void show() {
super.show();
((TextView)this.findViewById(android.R.id.message))
.setMovementMethod(LinkMovementMethod.getInstance());
}
public void setCallback(OnClickListener callback) {
this.callback = callback;
}
private class AcceptListener implements OnClickListener {
private final MessageRecord messageRecord;
private final IdentityKeyMismatch mismatch;
private final Address address;
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>()
{
@Override
protected Void doInBackground(Void... params) {
synchronized (SESSION_LOCK) {
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(address.toPhoneString(), 1);
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext());
identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true);
}
processMessageRecord(messageRecord);
processPendingMessageRecords(messageRecord.getThreadId(), mismatch);
return null;
}
private void processMessageRecord(MessageRecord messageRecord) {
if (messageRecord.isOutgoing()) processOutgoingMessageRecord(messageRecord);
else processIncomingMessageRecord(messageRecord);
}
private void processPendingMessageRecords(long threadId, IdentityKeyMismatch mismatch) {
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(getContext());
Cursor cursor = mmsSmsDatabase.getIdentityConflictMessagesForThread(threadId);
MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(cursor);
MessageRecord record;
try {
while ((record = reader.getNext()) != null) {
for (IdentityKeyMismatch recordMismatch : record.getIdentityKeyMismatches()) {
if (mismatch.equals(recordMismatch)) {
processMessageRecord(record);
}
}
}
} finally {
if (reader != null)
reader.close();
}
}
private void processOutgoingMessageRecord(MessageRecord messageRecord) {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext());
if (messageRecord.isMms()) {
mmsDatabase.removeMismatchedIdentity(messageRecord.getId(),
mismatch.getAddress(),
mismatch.getIdentityKey());
if (messageRecord.getRecipient().isPushGroupRecipient()) {
MessageSender.resendGroupMessage(getContext(), messageRecord, mismatch.getAddress());
} else {
MessageSender.resend(getContext(), messageRecord);
}
} else {
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
mismatch.getAddress(),
mismatch.getIdentityKey());
MessageSender.resend(getContext(), messageRecord);
}
}
private void processIncomingMessageRecord(MessageRecord messageRecord) {
try {
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(getContext());
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
mismatch.getAddress(),
mismatch.getIdentityKey());
boolean legacy = !messageRecord.isContentBundleKeyExchange();
SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE,
messageRecord.getIndividualRecipient().getAddress().toPhoneString(),
messageRecord.getRecipientDeviceId(),
messageRecord.getDateSent(),
legacy ? Base64.decode(messageRecord.getBody()) : null,
!legacy ? Base64.decode(messageRecord.getBody()) : null,
0, null);
long pushId = pushDatabase.insert(envelope);
ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new PushDecryptJob(getContext(), pushId, messageRecord.getId()));
} catch (IOException e) {
throw new AssertionError(e);
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (callback != null) callback.onClick(null, 0);
}
}
private class CancelListener implements OnClickListener {
@Override
public void onClick(DialogInterface dialog, int which) {
if (callback != null) callback.onClick(null, 0);
}
}
}

View File

@ -23,15 +23,10 @@ import android.content.Intent;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.preference.PreferenceManager;
import android.view.View;
import android.widget.ProgressBar;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColorsLegacy;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
@ -40,17 +35,10 @@ import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.FileUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VersionTracker;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
@ -60,72 +48,72 @@ import network.loki.messenger.R;
public class DatabaseUpgradeActivity extends BaseActivity {
private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName();
public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 0; // 46
public static final int MMS_BODY_VERSION = 0; // 46
public static final int TOFU_IDENTITIES_VERSION = 1; // 50
public static final int CURVE25519_VERSION = 2; // 63
public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 3; // 73
public static final int NO_V1_VERSION = 4; // 83
public static final int SIGNED_PREKEY_VERSION = 4; // 83
public static final int NO_DECRYPT_QUEUE_VERSION = 5; // 113
public static final int PUSH_DECRYPT_SERIAL_ID_VERSION = 6; // 131
public static final int MIGRATE_SESSION_PLAINTEXT = 7; // 136
public static final int CONTACTS_ACCOUNT_VERSION = 7; // 136
public static final int MEDIA_DOWNLOAD_CONTROLS_VERSION = 8; // 151
public static final int REDPHONE_SUPPORT_VERSION = 9; // 157
public static final int NO_MORE_CANONICAL_DB_VERSION = 10; // 276
public static final int PROFILES = 11; // 289
public static final int SCREENSHOTS = 12; // 300
public static final int PERSISTENT_BLOBS = 13; // 317
public static final int INTERNALIZE_CONTACTS = 13; // 317
public static final int SQLCIPHER = 14; // 334
public static final int SQLCIPHER_COMPLETE = 15; // 352
public static final int REMOVE_JOURNAL = 16; // 353
public static final int REMOVE_CACHE = 17; // 354
public static final int FULL_TEXT_SEARCH = 18; // 358
public static final int BAD_IMPORT_CLEANUP = 19; // 373
public static final int IMAGE_CACHE_CLEANUP = 20; // 406
public static final int WORKMANAGER_MIGRATION = 21; // 408
public static final int COLOR_MIGRATION = 22; // 412
public static final int UNIDENTIFIED_DELIVERY = 23; // 422
public static final int SIGNALING_KEY_DEPRECATION = 24; // 447
public static final int CONVERSATION_SEARCH = 25; // 455
// public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 0; // 46
// public static final int MMS_BODY_VERSION = 0; // 46
// public static final int TOFU_IDENTITIES_VERSION = 1; // 50
// public static final int CURVE25519_VERSION = 2; // 63
// public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 3; // 73
// public static final int NO_V1_VERSION = 4; // 83
// public static final int SIGNED_PREKEY_VERSION = 4; // 83
// public static final int NO_DECRYPT_QUEUE_VERSION = 5; // 113
// public static final int PUSH_DECRYPT_SERIAL_ID_VERSION = 6; // 131
// public static final int MIGRATE_SESSION_PLAINTEXT = 7; // 136
// public static final int CONTACTS_ACCOUNT_VERSION = 7; // 136
// public static final int MEDIA_DOWNLOAD_CONTROLS_VERSION = 8; // 151
// public static final int REDPHONE_SUPPORT_VERSION = 9; // 157
// public static final int NO_MORE_CANONICAL_DB_VERSION = 10; // 276
// public static final int PROFILES = 11; // 289
// public static final int SCREENSHOTS = 12; // 300
// public static final int PERSISTENT_BLOBS = 13; // 317
// public static final int INTERNALIZE_CONTACTS = 13; // 317
// public static final int SQLCIPHER = 14; // 334
// public static final int SQLCIPHER_COMPLETE = 15; // 352
// public static final int REMOVE_JOURNAL = 16; // 353
// public static final int REMOVE_CACHE = 17; // 354
// public static final int FULL_TEXT_SEARCH = 18; // 358
// public static final int BAD_IMPORT_CLEANUP = 19; // 373
// public static final int IMAGE_CACHE_CLEANUP = 20; // 406
// public static final int WORKMANAGER_MIGRATION = 21; // 408
// public static final int COLOR_MIGRATION = 22; // 412
// public static final int UNIDENTIFIED_DELIVERY = 23; // 422
// public static final int SIGNALING_KEY_DEPRECATION = 24; // 447
// public static final int CONVERSATION_SEARCH = 25; // 455
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
add(TOFU_IDENTITIES_VERSION);
add(CURVE25519_VERSION);
add(ASYMMETRIC_MASTER_SECRET_FIX_VERSION);
add(NO_V1_VERSION);
add(SIGNED_PREKEY_VERSION);
add(NO_DECRYPT_QUEUE_VERSION);
add(PUSH_DECRYPT_SERIAL_ID_VERSION);
add(MIGRATE_SESSION_PLAINTEXT);
add(MEDIA_DOWNLOAD_CONTROLS_VERSION);
add(REDPHONE_SUPPORT_VERSION);
add(NO_MORE_CANONICAL_DB_VERSION);
add(SCREENSHOTS);
add(INTERNALIZE_CONTACTS);
add(PERSISTENT_BLOBS);
add(SQLCIPHER);
add(SQLCIPHER_COMPLETE);
add(REMOVE_CACHE);
add(FULL_TEXT_SEARCH);
add(BAD_IMPORT_CLEANUP);
add(IMAGE_CACHE_CLEANUP);
add(WORKMANAGER_MIGRATION);
add(COLOR_MIGRATION);
add(UNIDENTIFIED_DELIVERY);
add(SIGNALING_KEY_DEPRECATION);
add(CONVERSATION_SEARCH);
// add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
// add(TOFU_IDENTITIES_VERSION);
// add(CURVE25519_VERSION);
// add(ASYMMETRIC_MASTER_SECRET_FIX_VERSION);
// add(NO_V1_VERSION);
// add(SIGNED_PREKEY_VERSION);
// add(NO_DECRYPT_QUEUE_VERSION);
// add(PUSH_DECRYPT_SERIAL_ID_VERSION);
// add(MIGRATE_SESSION_PLAINTEXT);
// add(MEDIA_DOWNLOAD_CONTROLS_VERSION);
// add(REDPHONE_SUPPORT_VERSION);
// add(NO_MORE_CANONICAL_DB_VERSION);
// add(SCREENSHOTS);
// add(INTERNALIZE_CONTACTS);
// add(PERSISTENT_BLOBS);
// add(SQLCIPHER);
// add(SQLCIPHER_COMPLETE);
// add(REMOVE_CACHE);
// add(FULL_TEXT_SEARCH);
// add(BAD_IMPORT_CLEANUP);
// add(IMAGE_CACHE_CLEANUP);
// add(WORKMANAGER_MIGRATION);
// add(COLOR_MIGRATION);
// add(UNIDENTIFIED_DELIVERY);
// add(SIGNALING_KEY_DEPRECATION);
// add(CONVERSATION_SEARCH);
}};
private MasterSecret masterSecret;
// private MasterSecret masterSecret;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
this.masterSecret = KeyCachingService.getMasterSecret(this);
// this.masterSecret = KeyCachingService.getMasterSecret(this);
if (needsUpgradeTask()) {
Log.i("DatabaseUpgradeActivity", "Upgrading...");
@ -202,160 +190,160 @@ public class DatabaseUpgradeActivity extends BaseActivity {
Context context = DatabaseUpgradeActivity.this.getApplicationContext();
Log.i("DatabaseUpgradeActivity", "Running background upgrade..");
DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
.onApplicationLevelUpgrade(context, masterSecret, params[0], this);
// DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
// .onApplicationLevelUpgrade(context, params[0], this);
if (params[0] < CURVE25519_VERSION) {
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
}
if (params[0] < NO_V1_VERSION) {
File v1sessions = new File(context.getFilesDir(), "sessions");
if (v1sessions.exists() && v1sessions.isDirectory()) {
File[] contents = v1sessions.listFiles();
if (contents != null) {
for (File session : contents) {
session.delete();
}
}
v1sessions.delete();
}
}
if (params[0] < SIGNED_PREKEY_VERSION) {
// ApplicationContext.getInstance(getApplicationContext())
// .getJobManager()
// .add(new CreateSignedPreKeyJob(context));
}
if (params[0] < NO_DECRYPT_QUEUE_VERSION) {
scheduleMessagesInPushDatabase(context);
}
if (params[0] < PUSH_DECRYPT_SERIAL_ID_VERSION) {
scheduleMessagesInPushDatabase(context);
}
if (params[0] < MIGRATE_SESSION_PLAINTEXT) {
// new TextSecureSessionStore(context, masterSecret).migrateSessions();
// new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
scheduleMessagesInPushDatabase(context);;
}
if (params[0] < MEDIA_DOWNLOAD_CONTROLS_VERSION) {
schedulePendingIncomingParts(context);
}
if (params[0] < REDPHONE_SUPPORT_VERSION) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new RefreshAttributesJob());
}
if (params[0] < SCREENSHOTS) {
boolean screenSecurity = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, true);
TextSecurePreferences.setScreenSecurityEnabled(getApplicationContext(), screenSecurity);
}
if (params[0] < PERSISTENT_BLOBS) {
File externalDir = context.getExternalFilesDir(null);
if (externalDir != null && externalDir.isDirectory() && externalDir.exists()) {
for (File blob : externalDir.listFiles()) {
if (blob.exists() && blob.isFile()) blob.delete();
}
}
}
if (params[0] < INTERNALIZE_CONTACTS) {
if (TextSecurePreferences.isPushRegistered(getApplicationContext())) {
TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(getApplicationContext(), true);
}
}
if (params[0] < SQLCIPHER) {
scheduleMessagesInPushDatabase(context);
}
if (params[0] < SQLCIPHER_COMPLETE) {
File file = context.getDatabasePath("messages.db");
if (file != null && file.exists()) file.delete();
}
if (params[0] < REMOVE_JOURNAL) {
File file = context.getDatabasePath("messages.db-journal");
if (file != null && file.exists()) file.delete();
}
if (params[0] < REMOVE_CACHE) {
try {
FileUtils.deleteDirectoryContents(context.getCacheDir());
} catch (IOException e) {
Log.w(TAG, e);
}
}
if (params[0] < IMAGE_CACHE_CLEANUP) {
try {
FileUtils.deleteDirectoryContents(context.getExternalCacheDir());
GlideApp.get(context).clearDiskCache();
} catch (IOException e) {
Log.w(TAG, e);
}
}
// This migration became unnecessary after switching away from WorkManager
// if (params[0] < WORKMANAGER_MIGRATION) {
// Log.i(TAG, "Beginning migration of existing jobs to WorkManager");
// if (params[0] < CURVE25519_VERSION) {
// IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
// }
//
// JobManager jobManager = ApplicationContext.getInstance(getApplicationContext()).getJobManager();
// PersistentStorage storage = new PersistentStorage(getApplicationContext(), "TextSecureJobs", new JavaJobSerializer());
// if (params[0] < NO_V1_VERSION) {
// File v1sessions = new File(context.getFilesDir(), "sessions");
//
// for (Job job : storage.getAllUnencrypted()) {
// jobManager.add(job);
// Log.i(TAG, "Migrated job with class '" + job.getClass().getSimpleName() + "' to run on new JobManager.");
// if (v1sessions.exists() && v1sessions.isDirectory()) {
// File[] contents = v1sessions.listFiles();
//
// if (contents != null) {
// for (File session : contents) {
// session.delete();
// }
// }
//
// v1sessions.delete();
// }
// }
if (params[0] < COLOR_MIGRATION) {
long startTime = System.currentTimeMillis();
DatabaseFactory.getRecipientDatabase(context).updateSystemContactColors((name, color) -> {
if (color != null) {
try {
return MaterialColor.fromSerialized(color);
} catch (MaterialColor.UnknownColorException e) {
Log.w(TAG, "Encountered an unknown color during legacy color migration.", e);
return ContactColorsLegacy.generateFor(name);
}
}
return ContactColorsLegacy.generateFor(name);
});
Log.i(TAG, "Color migration took " + (System.currentTimeMillis() - startTime) + " ms");
}
if (params[0] < UNIDENTIFIED_DELIVERY) {
if (TextSecurePreferences.isMultiDevice(context)) {
Log.i(TAG, "MultiDevice: Disabling UD (will be re-enabled if possible after pending refresh).");
TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
}
Log.i(TAG, "Scheduling UD attributes refresh.");
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RefreshAttributesJob());
}
if (params[0] < SIGNALING_KEY_DEPRECATION) {
Log.i(TAG, "Scheduling a RefreshAttributesJob to remove the signaling key remotely.");
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RefreshAttributesJob());
}
//
// if (params[0] < SIGNED_PREKEY_VERSION) {
//// ApplicationContext.getInstance(getApplicationContext())
//// .getJobManager()
//// .add(new CreateSignedPreKeyJob(context));
// }
//
// if (params[0] < NO_DECRYPT_QUEUE_VERSION) {
// scheduleMessagesInPushDatabase(context);
// }
//
// if (params[0] < PUSH_DECRYPT_SERIAL_ID_VERSION) {
// scheduleMessagesInPushDatabase(context);
// }
//
// if (params[0] < MIGRATE_SESSION_PLAINTEXT) {
//// new TextSecureSessionStore(context, masterSecret).migrateSessions();
//// new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
//
// IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
// scheduleMessagesInPushDatabase(context);;
// }
//
// if (params[0] < MEDIA_DOWNLOAD_CONTROLS_VERSION) {
// schedulePendingIncomingParts(context);
// }
//
// if (params[0] < REDPHONE_SUPPORT_VERSION) {
// ApplicationContext.getInstance(getApplicationContext())
// .getJobManager()
// .add(new RefreshAttributesJob());
// }
//
// if (params[0] < SCREENSHOTS) {
// boolean screenSecurity = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, true);
// TextSecurePreferences.setScreenSecurityEnabled(getApplicationContext(), screenSecurity);
// }
//
// if (params[0] < PERSISTENT_BLOBS) {
// File externalDir = context.getExternalFilesDir(null);
//
// if (externalDir != null && externalDir.isDirectory() && externalDir.exists()) {
// for (File blob : externalDir.listFiles()) {
// if (blob.exists() && blob.isFile()) blob.delete();
// }
// }
// }
//
// if (params[0] < INTERNALIZE_CONTACTS) {
// if (TextSecurePreferences.isPushRegistered(getApplicationContext())) {
// TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(getApplicationContext(), true);
// }
// }
//
// if (params[0] < SQLCIPHER) {
// scheduleMessagesInPushDatabase(context);
// }
//
// if (params[0] < SQLCIPHER_COMPLETE) {
// File file = context.getDatabasePath("messages.db");
// if (file != null && file.exists()) file.delete();
// }
//
// if (params[0] < REMOVE_JOURNAL) {
// File file = context.getDatabasePath("messages.db-journal");
// if (file != null && file.exists()) file.delete();
// }
//
// if (params[0] < REMOVE_CACHE) {
// try {
// FileUtils.deleteDirectoryContents(context.getCacheDir());
// } catch (IOException e) {
// Log.w(TAG, e);
// }
// }
//
// if (params[0] < IMAGE_CACHE_CLEANUP) {
// try {
// FileUtils.deleteDirectoryContents(context.getExternalCacheDir());
// GlideApp.get(context).clearDiskCache();
// } catch (IOException e) {
// Log.w(TAG, e);
// }
// }
//
// // This migration became unnecessary after switching away from WorkManager
//// if (params[0] < WORKMANAGER_MIGRATION) {
//// Log.i(TAG, "Beginning migration of existing jobs to WorkManager");
////
//// JobManager jobManager = ApplicationContext.getInstance(getApplicationContext()).getJobManager();
//// PersistentStorage storage = new PersistentStorage(getApplicationContext(), "TextSecureJobs", new JavaJobSerializer());
////
//// for (Job job : storage.getAllUnencrypted()) {
//// jobManager.add(job);
//// Log.i(TAG, "Migrated job with class '" + job.getClass().getSimpleName() + "' to run on new JobManager.");
//// }
//// }
//
// if (params[0] < COLOR_MIGRATION) {
// long startTime = System.currentTimeMillis();
// DatabaseFactory.getRecipientDatabase(context).updateSystemContactColors((name, color) -> {
// if (color != null) {
// try {
// return MaterialColor.fromSerialized(color);
// } catch (MaterialColor.UnknownColorException e) {
// Log.w(TAG, "Encountered an unknown color during legacy color migration.", e);
// return ContactColorsLegacy.generateFor(name);
// }
// }
// return ContactColorsLegacy.generateFor(name);
// });
// Log.i(TAG, "Color migration took " + (System.currentTimeMillis() - startTime) + " ms");
// }
//
// if (params[0] < UNIDENTIFIED_DELIVERY) {
// if (TextSecurePreferences.isMultiDevice(context)) {
// Log.i(TAG, "MultiDevice: Disabling UD (will be re-enabled if possible after pending refresh).");
// TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
// }
//
// Log.i(TAG, "Scheduling UD attributes refresh.");
// ApplicationContext.getInstance(context)
// .getJobManager()
// .add(new RefreshAttributesJob());
// }
//
// if (params[0] < SIGNALING_KEY_DEPRECATION) {
// Log.i(TAG, "Scheduling a RefreshAttributesJob to remove the signaling key remotely.");
// ApplicationContext.getInstance(context)
// .getJobManager()
// .add(new RefreshAttributesJob());
// }
return null;
}

View File

@ -1,282 +0,0 @@
package org.thoughtcrime.securesms;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.viewpager.widget.ViewPager;
import android.view.View;
import com.melnykov.fab.FloatingActionButton;
import com.nineoldandroids.animation.ArgbEvaluator;
import org.thoughtcrime.securesms.IntroPagerAdapter.IntroPage;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.util.Collections;
import java.util.List;
import network.loki.messenger.R;
public class ExperienceUpgradeActivity extends BaseActionBarActivity implements TypingIndicatorIntroFragment.Controller, LinkPreviewsIntroFragment.Controller {
private static final String TAG = ExperienceUpgradeActivity.class.getSimpleName();
private static final String DISMISS_ACTION = "network.loki.securesms.ExperienceUpgradeActivity.DISMISS_ACTION";
private static final int NOTIFICATION_ID = 1339;
private enum ExperienceUpgrade {
TYPING_INDICATORS(432,
new IntroPage(0xFF2090EA,
TypingIndicatorIntroFragment.newInstance()),
R.string.ExperienceUpgradeActivity_introducing_typing_indicators,
R.string.ExperienceUpgradeActivity_now_you_can_optionally_see_and_share_when_messages_are_being_typed,
R.string.ExperienceUpgradeActivity_now_you_can_optionally_see_and_share_when_messages_are_being_typed,
null,
true),
LINK_PREVIEWS(449,
new IntroPage(0xFF2090EA, LinkPreviewsIntroFragment.newInstance()),
R.string.ExperienceUpgradeActivity_introducing_link_previews,
R.string.ExperienceUpgradeActivity_optional_link_previews_are_now_supported,
R.string.ExperienceUpgradeActivity_optional_link_previews_are_now_supported,
null,
true);
private int version;
private List<IntroPage> pages;
private @StringRes int notificationTitle;
private @StringRes int notificationText;
private @StringRes int notificationBigText;
private @Nullable Class nextIntent;
private boolean handlesNavigation;
ExperienceUpgrade(int version,
@NonNull List<IntroPage> pages,
@StringRes int notificationTitle,
@StringRes int notificationText,
@StringRes int notificationBigText,
@Nullable Class nextIntent,
boolean handlesNavigation)
{
this.version = version;
this.pages = pages;
this.notificationTitle = notificationTitle;
this.notificationText = notificationText;
this.notificationBigText = notificationBigText;
this.nextIntent = nextIntent;
this.handlesNavigation = handlesNavigation;
}
ExperienceUpgrade(int version,
@NonNull IntroPage page,
@StringRes int notificationTitle,
@StringRes int notificationText,
@StringRes int notificationBigText,
@Nullable Class nextIntent,
boolean handlesNavigation)
{
this(version, Collections.singletonList(page), notificationTitle, notificationText, notificationBigText, nextIntent, handlesNavigation);
}
public int getVersion() {
return version;
}
public List<IntroPage> getPages() {
return pages;
}
public IntroPage getPage(int i) {
return pages.get(i);
}
public int getNotificationTitle() {
return notificationTitle;
}
public int getNotificationText() {
return notificationText;
}
public int getNotificationBigText() {
return notificationBigText;
}
public boolean handlesNavigation() {
return handlesNavigation;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Optional<ExperienceUpgrade> upgrade = getExperienceUpgrade(this);
if (!upgrade.isPresent()) {
onContinue(upgrade);
return;
}
setContentView(R.layout.experience_upgrade_activity);
final ViewPager pager = ViewUtil.findById(this, R.id.pager);
final FloatingActionButton fab = ViewUtil.findById(this, R.id.fab);
pager.setAdapter(new IntroPagerAdapter(getSupportFragmentManager(), upgrade.get().getPages()));
if (upgrade.get().handlesNavigation()) {
fab.setVisibility(View.GONE);
} else {
fab.setVisibility(View.VISIBLE);
fab.setOnClickListener(v -> onContinue(upgrade));
}
getWindow().setBackgroundDrawable(new ColorDrawable(upgrade.get().getPage(0).backgroundColor));
ServiceUtil.getNotificationManager(this).cancel(NOTIFICATION_ID);
}
private void onContinue(Optional<ExperienceUpgrade> seenUpgrade) {
ServiceUtil.getNotificationManager(this).cancel(NOTIFICATION_ID);
int latestVersion = seenUpgrade.isPresent() ? seenUpgrade.get().getVersion()
: Util.getCanonicalVersionCode();
TextSecurePreferences.setLastExperienceVersionCode(this, latestVersion);
if (seenUpgrade.isPresent() && seenUpgrade.get().nextIntent != null) {
Intent intent = new Intent(this, seenUpgrade.get().nextIntent);
Intent nextIntent = new Intent(this, HomeActivity.class);
intent.putExtra("next_intent", nextIntent);
startActivity(intent);
} else {
startActivity(getIntent().getParcelableExtra("next_intent"));
}
finish();
}
public static boolean isUpdate(Context context) {
return getExperienceUpgrade(context).isPresent();
}
public static Optional<ExperienceUpgrade> getExperienceUpgrade(Context context) {
final int currentVersionCode = Util.getCanonicalVersionCode();
final int lastSeenVersion = TextSecurePreferences.getLastExperienceVersionCode(context);
Log.i(TAG, "getExperienceUpgrade(" + lastSeenVersion + ")");
if (lastSeenVersion >= currentVersionCode) {
TextSecurePreferences.setLastExperienceVersionCode(context, currentVersionCode);
return Optional.absent();
}
Optional<ExperienceUpgrade> eligibleUpgrade = Optional.absent();
for (ExperienceUpgrade upgrade : ExperienceUpgrade.values()) {
if (lastSeenVersion < upgrade.getVersion()) eligibleUpgrade = Optional.of(upgrade);
}
return eligibleUpgrade;
}
@Override
public void onTypingIndicatorsFinished() {
onContinue(Optional.of(ExperienceUpgrade.TYPING_INDICATORS));
}
@Override
public void onLinkPreviewsFinished() {
onContinue(Optional.of(ExperienceUpgrade.LINK_PREVIEWS));
}
private final class OnPageChangeListener implements ViewPager.OnPageChangeListener {
private final ArgbEvaluator evaluator = new ArgbEvaluator();
private final ExperienceUpgrade upgrade;
public OnPageChangeListener(ExperienceUpgrade upgrade) {
this.upgrade = upgrade;
}
@Override
public void onPageSelected(int position) {}
@Override
public void onPageScrollStateChanged(int state) {}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
final int nextPosition = (position + 1) % upgrade.getPages().size();
final int color = (Integer)evaluator.evaluate(positionOffset,
upgrade.getPage(position).backgroundColor,
upgrade.getPage(nextPosition).backgroundColor);
getWindow().setBackgroundDrawable(new ColorDrawable(color));
}
}
public static class AppUpgradeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction()) &&
intent.getData().getSchemeSpecificPart().equals(context.getPackageName()))
{
if (TextSecurePreferences.getLastExperienceVersionCode(context) < 339 &&
!TextSecurePreferences.isPasswordDisabled(context))
{
Notification notification = new NotificationCompat.Builder(context, NotificationChannels.OTHER)
.setSmallIcon(R.drawable.ic_notification)
.setColor(context.getResources().getColor(R.color.signal_primary))
.setContentTitle(context.getString(R.string.ExperienceUpgradeActivity_unlock_to_complete_update))
.setContentText(context.getString(R.string.ExperienceUpgradeActivity_please_unlock_signal_to_complete_update))
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.ExperienceUpgradeActivity_please_unlock_signal_to_complete_update)))
.setAutoCancel(true)
.setContentIntent(PendingIntent.getActivity(context, 0,
context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT))
.build();
ServiceUtil.getNotificationManager(context).notify(NOTIFICATION_ID, notification);
}
Optional<ExperienceUpgrade> experienceUpgrade = getExperienceUpgrade(context);
if (!experienceUpgrade.isPresent()) {
return;
}
if (experienceUpgrade.get().getVersion() == TextSecurePreferences.getExperienceDismissedVersionCode(context)) {
return;
}
Intent targetIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
Intent dismissIntent = new Intent(context, AppUpgradeReceiver.class);
dismissIntent.setAction(DISMISS_ACTION);
Notification notification = new NotificationCompat.Builder(context, NotificationChannels.OTHER)
.setSmallIcon(R.drawable.ic_notification)
.setColor(context.getResources().getColor(R.color.signal_primary))
.setContentTitle(context.getString(experienceUpgrade.get().getNotificationTitle()))
.setContentText(context.getString(experienceUpgrade.get().getNotificationText()))
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(experienceUpgrade.get().getNotificationBigText())))
.setAutoCancel(true)
.setContentIntent(PendingIntent.getActivity(context, 0,
targetIntent,
PendingIntent.FLAG_UPDATE_CURRENT))
.setDeleteIntent(PendingIntent.getBroadcast(context, 0,
dismissIntent,
PendingIntent.FLAG_UPDATE_CURRENT))
.build();
ServiceUtil.getNotificationManager(context).notify(NOTIFICATION_ID, notification);
} else if (DISMISS_ACTION.equals(intent.getAction())) {
TextSecurePreferences.setExperienceDismissedVersionCode(context, Util.getCanonicalVersionCode());
}
}
}
}

View File

@ -1,38 +0,0 @@
package org.thoughtcrime.securesms;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import java.util.List;
public class IntroPagerAdapter extends FragmentStatePagerAdapter {
public static class IntroPage {
final int backgroundColor;
final Fragment fragment;
public IntroPage(int backgroundColor, Fragment fragment) {
this.backgroundColor = backgroundColor;
this.fragment = fragment;
}
}
private List<IntroPage> pages;
public IntroPagerAdapter(FragmentManager fm, List<IntroPage> pages) {
super(fm);
this.pages = pages;
}
@Override
public Fragment getItem(int i) {
IntroPage page = pages.get(i);
return page.fragment;
}
@Override
public int getCount() {
return pages.size();
}
}

View File

@ -62,7 +62,6 @@ import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.Util;
@ -86,18 +85,11 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
public static final String ADDRESS_EXTRA = "address";
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
private Recipient recipient;
@Override
protected void onPreCreate() {
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle bundle, boolean ready) {
setContentView(R.layout.media_overview_activity);
@ -109,12 +101,6 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
this.viewPager.setAdapter(new MediaOverviewPagerAdapter(getSupportFragmentManager()));
}
@Override
public void onResume() {
super.onResume();
dynamicLanguage.onResume(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
@ -172,7 +158,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
Bundle args = new Bundle();
args.putString(MediaOverviewGalleryFragment.ADDRESS_EXTRA, recipient.getAddress().serialize());
args.putSerializable(MediaOverviewGalleryFragment.LOCALE_EXTRA, dynamicLanguage.getCurrentLocale());
args.putSerializable(MediaOverviewGalleryFragment.LOCALE_EXTRA, Locale.getDefault());
fragment.setArguments(args);

View File

@ -68,12 +68,12 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.util.Locale;
import java.util.WeakHashMap;
import network.loki.messenger.R;
@ -95,8 +95,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
public static final String OUTGOING_EXTRA = "outgoing";
public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent";
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private ViewPager mediaPager;
private View detailsContainer;
private TextView caption;
@ -120,8 +118,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
@SuppressWarnings("ConstantConditions")
@Override
protected void onCreate(Bundle bundle, boolean ready) {
dynamicLanguage.onCreate(this);
viewModel = new ViewModelProvider(this).get(MediaPreviewViewModel.class);
setContentView(R.layout.media_preview_activity);
@ -172,7 +168,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
CharSequence relativeTimeSpan;
if (mediaItem.date > 0) {
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this,dynamicLanguage.getCurrentLocale(), mediaItem.date);
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date);
} else {
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft);
}
@ -189,7 +185,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
public void onResume() {
super.onResume();
dynamicLanguage.onResume(this);
initializeMedia();
}

View File

@ -55,7 +55,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.Util;
import org.session.libsignal.libsignal.util.guava.Optional;
@ -102,15 +101,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
private ListView recipientsList;
private LayoutInflater inflater;
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
private boolean running;
@Override
protected void onPreCreate() {
dynamicLanguage.onCreate(this);
}
@Override
public void onCreate(Bundle bundle, boolean ready) {
super.onCreate(bundle, ready);
@ -125,7 +117,6 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onResume() {
super.onResume();
dynamicLanguage.onResume(this);
assert getSupportActionBar() != null;
getSupportActionBar().setTitle("Message Details");
@ -211,7 +202,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
sentDate.setText("-");
receivedContainer.setVisibility(View.GONE);
} else {
Locale dateLocale = dynamicLanguage.getCurrentLocale();
Locale dateLocale = Locale.getDefault();
SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(this, dateLocale);
sentDate.setText(dateFormatter.format(new Date(messageRecord.getDateSent())));
sentDate.setOnLongClickListener(v -> {
@ -271,7 +262,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
toFrom.setVisibility(View.GONE);
separator.setVisibility(View.GONE);
}
conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient, null, false);
conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), recipient, null, false);
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup));
}

View File

@ -1,77 +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;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.service.KeyCachingService;
/**
* Base Activity for changing/prompting local encryption passphrase.
*
* @author Moxie Marlinspike
*/
public abstract class PassphraseActivity extends BaseActionBarActivity {
private static final String TAG = PassphraseActivity.class.getSimpleName();
private KeyCachingService keyCachingService;
private MasterSecret masterSecret;
protected void setMasterSecret(MasterSecret masterSecret) {
this.masterSecret = masterSecret;
Intent bindIntent = new Intent(this, KeyCachingService.class);
startService(bindIntent);
bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
protected abstract void cleanup();
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
keyCachingService = ((KeyCachingService.KeySetBinder)service).getService();
keyCachingService.setMasterSecret(masterSecret);
PassphraseActivity.this.unbindService(PassphraseActivity.this.serviceConnection);
masterSecret = null;
cleanup();
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
if (nextIntent != null) {
try {
startActivity(nextIntent);
} catch (java.lang.SecurityException e) {
Log.w(TAG, "Access permission not passed from PassphraseActivity, retry sharing.");
}
}
finish();
}
@Override
public void onServiceDisconnected(ComponentName name) {
keyCachingService = null;
}
};
}

View File

@ -1,177 +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;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import network.loki.messenger.R;
/**
* Activity for changing a user's local encryption passphrase.
*
* @author Moxie Marlinspike
*/
public class PassphraseChangeActivity extends PassphraseActivity {
private DynamicTheme dynamicTheme = new DynamicTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
private EditText originalPassphrase;
private EditText newPassphrase;
private EditText repeatPassphrase;
private Button okButton;
private Button cancelButton;
@Override
public void onCreate(Bundle savedInstanceState) {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.change_passphrase_activity);
initializeResources();
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
private void initializeResources() {
this.originalPassphrase = (EditText) findViewById(R.id.old_passphrase );
this.newPassphrase = (EditText) findViewById(R.id.new_passphrase );
this.repeatPassphrase = (EditText) findViewById(R.id.repeat_passphrase );
this.okButton = (Button ) findViewById(R.id.ok_button );
this.cancelButton = (Button ) findViewById(R.id.cancel_button );
this.okButton.setOnClickListener(new OkButtonClickListener());
this.cancelButton.setOnClickListener(new CancelButtonClickListener());
if (TextSecurePreferences.isPasswordDisabled(this)) {
this.originalPassphrase.setVisibility(View.GONE);
} else {
this.originalPassphrase.setVisibility(View.VISIBLE);
}
}
private void verifyAndSavePassphrases() {
Editable originalText = this.originalPassphrase.getText();
Editable newText = this.newPassphrase.getText();
Editable repeatText = this.repeatPassphrase.getText();
String original = (originalText == null ? "" : originalText.toString());
String passphrase = (newText == null ? "" : newText.toString());
String passphraseRepeat = (repeatText == null ? "" : repeatText.toString());
if (TextSecurePreferences.isPasswordDisabled(this)) {
original = MasterSecretUtil.UNENCRYPTED_PASSPHRASE;
}
if (!passphrase.equals(passphraseRepeat)) {
this.newPassphrase.setText("");
this.repeatPassphrase.setText("");
this.newPassphrase.setError(getString(R.string.PassphraseChangeActivity_passphrases_dont_match_exclamation));
this.newPassphrase.requestFocus();
} else if (passphrase.equals("")) {
this.newPassphrase.setError(getString(R.string.PassphraseChangeActivity_enter_new_passphrase_exclamation));
this.newPassphrase.requestFocus();
} else {
new ChangePassphraseTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, original, passphrase);
}
}
private class CancelButtonClickListener implements OnClickListener {
public void onClick(View v) {
finish();
}
}
private class OkButtonClickListener implements OnClickListener {
public void onClick(View v) {
verifyAndSavePassphrases();
}
}
private class ChangePassphraseTask extends AsyncTask<String, Void, MasterSecret> {
private final Context context;
public ChangePassphraseTask(Context context) {
this.context = context;
}
@Override
protected void onPreExecute() {
okButton.setEnabled(false);
}
@Override
protected MasterSecret doInBackground(String... params) {
try {
MasterSecret masterSecret = MasterSecretUtil.changeMasterSecretPassphrase(context, params[0], params[1]);
TextSecurePreferences.setPasswordDisabled(context, false);
return masterSecret;
} catch (InvalidPassphraseException e) {
Log.w(PassphraseChangeActivity.class.getSimpleName(), e);
return null;
}
}
@Override
protected void onPostExecute(MasterSecret masterSecret) {
okButton.setEnabled(true);
if (masterSecret != null) {
setMasterSecret(masterSecret);
} else {
originalPassphrase.setText("");
originalPassphrase.setError(getString(R.string.PassphraseChangeActivity_incorrect_old_passphrase_exclamation));
originalPassphrase.requestFocus();
}
}
}
@Override
protected void cleanup() {
this.originalPassphrase = null;
this.newPassphrase = null;
this.repeatPassphrase = null;
System.gc();
}
}

View File

@ -1,90 +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;
import android.os.AsyncTask;
import android.os.Bundle;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VersionTracker;
import network.loki.messenger.R;
/**
* Activity for creating a user's local encryption passphrase.
*
* @author Moxie Marlinspike
*/
public class PassphraseCreateActivity extends PassphraseActivity {
public PassphraseCreateActivity() { }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.create_passphrase_activity);
initializeResources();
}
private void initializeResources() {
new SecretGenerator().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
}
private class SecretGenerator extends AsyncTask<String, Void, Void> {
private MasterSecret masterSecret;
@Override
protected void onPreExecute() {
}
@Override
protected Void doInBackground(String... params) {
String passphrase = params[0];
masterSecret = MasterSecretUtil.generateMasterSecret(PassphraseCreateActivity.this,
passphrase);
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
IdentityKeyUtil.generateIdentityKeyPair(PassphraseCreateActivity.this);
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCanonicalVersionCode());
TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true);
TextSecurePreferences.setReadReceiptsEnabled(PassphraseCreateActivity.this, true);
TextSecurePreferences.setTypingIndicatorsEnabled(PassphraseCreateActivity.this, true);
TextSecurePreferences.setHasSeenWelcomeScreen(PassphraseCreateActivity.this, false);
return null;
}
@Override
protected void onPostExecute(Void param) {
setMasterSecret(masterSecret);
}
}
@Override
protected void cleanup() {
System.gc();
}
}

View File

@ -18,66 +18,43 @@ package org.thoughtcrime.securesms;
import android.animation.Animator;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.os.IBinder;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.RelativeSizeSpan;
import android.text.style.TypefaceSpan;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.BounceInterpolator;
import android.view.animation.TranslateAnimation;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.core.os.CancellationSignal;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import network.loki.messenger.R;
/**
* Activity that prompts for a user's passphrase.
*
* @author Moxie Marlinspike
*/
public class PassphrasePromptActivity extends PassphraseActivity {
//TODO Rename to ScreenLockActivity and refactor to Kotlin.
public class PassphrasePromptActivity extends BaseActionBarActivity {
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
private View passphraseAuthContainer;
private ImageView fingerprintPrompt;
private Button lockScreenButton;
private EditText passphraseText;
private ImageButton showButton;
private ImageButton hideButton;
private AnimatingToggle visibilityToggle;
private FingerprintManagerCompat fingerprintManager;
@ -87,20 +64,34 @@ public class PassphrasePromptActivity extends PassphraseActivity {
private boolean authenticated;
private boolean failure;
private KeyCachingService keyCachingService;
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate()");
dynamicLanguage.onCreate(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.prompt_passphrase_activity);
initializeResources();
// Start and bind to the KeyCachingService instance.
Intent bindIntent = new Intent(this, KeyCachingService.class);
startService(bindIntent);
bindService(bindIntent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
keyCachingService = ((KeyCachingService.KeySetBinder)service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
keyCachingService = null;
}
}, Context.BIND_AUTO_CREATE);
}
@Override
public void onResume() {
super.onResume();
dynamicLanguage.onResume(this);
setLockTypeVisibility();
@ -126,26 +117,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
setIntent(intent);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = this.getMenuInflater();
menu.clear();
// inflater.inflate(R.menu.log_submit, menu);
super.onPrepareOptionsMenu(menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_submit_debug_logs: handleLogSubmit(); return true;
}
return false;
}
@Override
public void onActivityResult(int requestCode, int resultcode, Intent data) {
super.onActivityResult(requestCode, resultcode, data);
@ -159,57 +130,26 @@ public class PassphrasePromptActivity extends PassphraseActivity {
}
}
private void handleLogSubmit() {
Intent intent = new Intent(this, LogSubmitActivity.class);
startActivity(intent);
}
private void handlePassphrase() {
try {
Editable text = passphraseText.getText();
String passphrase = (text == null ? "" : text.toString());
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, passphrase);
setMasterSecret(masterSecret);
} catch (InvalidPassphraseException ipe) {
passphraseText.setText("");
passphraseText.setError(
getString(R.string.PassphrasePromptActivity_invalid_passphrase_exclamation));
}
}
private void handleAuthenticated() {
try {
authenticated = true;
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
} catch (InvalidPassphraseException e) {
throw new AssertionError(e);
}
}
authenticated = true;
//TODO Replace with a proper call.
keyCachingService.setMasterSecret(new Object());
private void setPassphraseVisibility(boolean visibility) {
int cursorPosition = passphraseText.getSelectionStart();
if (visibility) {
passphraseText.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
} else {
passphraseText.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_PASSWORD);
// Finish and proceed with the next intent.
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
if (nextIntent != null) {
startActivity(nextIntent);
// try {
// startActivity(nextIntent);
// } catch (java.lang.SecurityException e) {
// Log.w(TAG, "Access permission not passed from PassphraseActivity, retry sharing.");
// }
}
passphraseText.setSelection(cursorPosition);
finish();
}
private void initializeResources() {
ImageButton okButton = findViewById(R.id.ok_button);
showButton = findViewById(R.id.passphrase_visibility);
hideButton = findViewById(R.id.passphrase_visibility_off);
visibilityToggle = findViewById(R.id.button_toggle);
passphraseText = findViewById(R.id.passphrase_edit);
passphraseAuthContainer = findViewById(R.id.password_auth_container);
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
fingerprintManager = FingerprintManagerCompat.from(this);
@ -220,13 +160,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
hint.setSpan(new RelativeSizeSpan(0.9f), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
hint.setSpan(new TypefaceSpan("sans-serif"), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
passphraseText.setHint(hint);
okButton.setOnClickListener(new OkButtonClickListener());
showButton.setOnClickListener(new ShowButtonOnClickListener());
hideButton.setOnClickListener(new HideButtonOnClickListener());
passphraseText.setOnEditorActionListener(new PassphraseActionListener());
passphraseText.setImeActionLabel(getString(R.string.prompt_passphrase_activity__unlock), EditorInfo.IME_ACTION_DONE);
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
@ -235,8 +168,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
private void setLockTypeVisibility() {
if (TextSecurePreferences.isScreenLockEnabled(this)) {
passphraseAuthContainer.setVisibility(View.GONE);
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
fingerprintPrompt.setVisibility(View.VISIBLE);
lockScreenButton.setVisibility(View.GONE);
@ -245,7 +176,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
lockScreenButton.setVisibility(View.VISIBLE);
}
} else {
passphraseAuthContainer.setVisibility(View.VISIBLE);
fingerprintPrompt.setVisibility(View.GONE);
lockScreenButton.setVisibility(View.GONE);
}
@ -266,13 +196,10 @@ public class PassphrasePromptActivity extends PassphraseActivity {
Log.i(TAG, "Listening for fingerprints...");
fingerprintCancellationSignal = new CancellationSignal();
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
} else if (Build.VERSION.SDK_INT >= 21){
} else {
Log.i(TAG, "firing intent...");
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Session", "");
startActivityForResult(intent, 1);
} else {
Log.w(TAG, "Not compatible...");
handleAuthenticated();
}
}
@ -282,54 +209,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
}
}
private class PassphraseActionListener implements TextView.OnEditorActionListener {
@Override
public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent keyEvent) {
if ((keyEvent == null && actionId == EditorInfo.IME_ACTION_DONE) ||
(keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
(actionId == EditorInfo.IME_NULL)))
{
handlePassphrase();
return true;
} else if (keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_UP &&
actionId == EditorInfo.IME_NULL)
{
return true;
}
return false;
}
}
private class OkButtonClickListener implements OnClickListener {
@Override
public void onClick(View v) {
handlePassphrase();
}
}
private class ShowButtonOnClickListener implements OnClickListener {
@Override
public void onClick(View v) {
visibilityToggle.display(hideButton);
setPassphraseVisibility(true);
}
}
private class HideButtonOnClickListener implements OnClickListener {
@Override
public void onClick(View v) {
visibilityToggle.display(showButton);
setPassphraseVisibility(false);
}
}
@Override
protected void cleanup() {
this.passphraseText.setText("");
System.gc();
}
private class FingerprintListener extends FingerprintManagerCompat.AuthenticationCallback {
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
@ -356,7 +235,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
@Override
public void onAuthenticationFailed() {
Log.w(TAG, "onAuthenticatoinFailed()");
FingerprintManagerCompat.AuthenticationCallback callback = this;
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
@ -380,6 +258,5 @@ public class PassphrasePromptActivity extends PassphraseActivity {
fingerprintPrompt.startAnimation(shake);
}
}
}
}

View File

@ -5,34 +5,30 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.activities.LandingActivity;
import org.thoughtcrime.securesms.loki.activities.SeedActivity;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.Locale;
//TODO AC: Rename to ScreenLockActionBarActivity.
public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarActivity {
private static final String TAG = PassphraseRequiredActionBarActivity.class.getSimpleName();
public static final String LOCALE_EXTRA = "locale_extra";
private static final int STATE_NORMAL = 0;
private static final int STATE_CREATE_PASSPHRASE = 1;
private static final int STATE_PROMPT_PASSPHRASE = 2;
private static final int STATE_UPGRADE_DATABASE = 3;
private static final int STATE_PROMPT_PUSH_REGISTRATION = 4;
private static final int STATE_EXPERIENCE_UPGRADE = 5;
private static final int STATE_WELCOME_SCREEN = 6;
private static final int STATE_PROMPT_PASSPHRASE = 1; //TODO AC: Rename to STATE_SCREEN_LOCKED
private static final int STATE_UPGRADE_DATABASE = 2; //TODO AC: Rename to STATE_MIGRATE_DATA
private static final int STATE_WELCOME_SCREEN = 3;
private BroadcastReceiver clearKeyReceiver;
@ -41,7 +37,9 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
Log.i(TAG, "onCreate(" + savedInstanceState + ")");
onPreCreate();
final boolean locked = KeyCachingService.isLocked(this);
final boolean locked = KeyCachingService.isLocked(this) &&
TextSecurePreferences.isScreenLockEnabled(this) &&
TextSecurePreferences.getLocalNumber(this) != null;
routeApplicationState(locked);
super.onCreate(savedInstanceState);
@ -118,61 +116,39 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
Log.i(TAG, "routeApplicationState(), state: " + state);
switch (state) {
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
case STATE_UPGRADE_DATABASE: return getUpgradeDatabaseIntent();
case STATE_WELCOME_SCREEN: return getWelcomeIntent();
case STATE_PROMPT_PUSH_REGISTRATION: return getPushRegistrationIntent();
case STATE_EXPERIENCE_UPGRADE: return getExperienceUpgradeIntent();
default: return null;
}
}
private int getApplicationState(boolean locked) {
if (!MasterSecretUtil.isPassphraseInitialized(this)) {
return STATE_CREATE_PASSPHRASE;
} else if (locked) {
if (locked) {
return STATE_PROMPT_PASSPHRASE;
} else if (DatabaseUpgradeActivity.isUpdate(this)) {
return STATE_UPGRADE_DATABASE;
} else if (!TextSecurePreferences.hasSeenWelcomeScreen(this)) {
return STATE_WELCOME_SCREEN;
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
return STATE_PROMPT_PUSH_REGISTRATION;
}
// else if (ExperienceUpgradeActivity.isUpdate(this)) {
// return STATE_EXPERIENCE_UPGRADE;
// }
else {
} else {
return STATE_NORMAL;
}
}
private Intent getCreatePassphraseIntent() {
return getRoutedIntent(PassphraseCreateActivity.class, getIntent());
}
private Intent getPromptPassphraseIntent() {
return getRoutedIntent(PassphrasePromptActivity.class, getIntent());
}
private Intent getUpgradeDatabaseIntent() {
return getRoutedIntent(DatabaseUpgradeActivity.class,
TextSecurePreferences.hasPromptedPushRegistration(this)
? getConversationListIntent()
: getPushRegistrationIntent());
}
private Intent getExperienceUpgradeIntent() {
return getRoutedIntent(ExperienceUpgradeActivity.class, getIntent());
return getRoutedIntent(DatabaseUpgradeActivity.class, getConversationListIntent());
}
private Intent getWelcomeIntent() {
return getRoutedIntent(LandingActivity.class, getPushRegistrationIntent());
return getRoutedIntent(LandingActivity.class, getConversationListIntent());
}
private Intent getPushRegistrationIntent() {
return getRoutedIntent(SeedActivity.class, getConversationListIntent());
private Intent getConversationListIntent() {
return new Intent(this, HomeActivity.class);
}
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
@ -181,10 +157,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
return intent;
}
private Intent getConversationListIntent() {
return new Intent(this, HomeActivity.class);
}
private void initializeClearKeyReceiver() {
Log.i(TAG, "initializeClearKeyReceiver()");
this.clearKeyReceiver = new BroadcastReceiver() {

View File

@ -48,7 +48,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ViewUtil;

View File

@ -1,660 +0,0 @@
/*
* Copyright (C) 2016-2017 Open 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;
import android.Manifest;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.appcompat.widget.SwitchCompat;
import android.text.Html;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.view.animation.ScaleAnimation;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.components.camera.CameraView;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.qr.QrCode;
import org.thoughtcrime.securesms.qr.ScanListener;
import org.thoughtcrime.securesms.qr.ScanningThread;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.fingerprint.Fingerprint;
import org.session.libsignal.libsignal.fingerprint.FingerprintParsingException;
import org.session.libsignal.libsignal.fingerprint.FingerprintVersionMismatchException;
import org.session.libsignal.libsignal.fingerprint.NumericFingerprintGenerator;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import network.loki.messenger.R;
import static org.session.libsignal.libsignal.SessionCipher.SESSION_LOCK;
/**
* Activity for verifying identity keys.
*
* @author Moxie Marlinspike
*/
@SuppressLint("StaticFieldLeak")
public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener, ScanListener, View.OnClickListener {
private static final String TAG = VerifyIdentityActivity.class.getSimpleName();
public static final String ADDRESS_EXTRA = "address";
public static final String IDENTITY_EXTRA = "recipient_identity";
public static final String VERIFIED_EXTRA = "verified_state";
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private VerifyDisplayFragment displayFragment = new VerifyDisplayFragment();
private VerifyScanFragment scanFragment = new VerifyScanFragment();
@Override
public void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle state, boolean ready) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.AndroidManifest__verify_safety_number);
Recipient recipient = Recipient.from(this, (Address)getIntent().getParcelableExtra(ADDRESS_EXTRA), true);
recipient.addListener(this);
setActionBarNotificationBarColor(recipient.getColor());
Bundle extras = new Bundle();
extras.putParcelable(VerifyDisplayFragment.REMOTE_ADDRESS, getIntent().getParcelableExtra(ADDRESS_EXTRA));
extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(IDENTITY_EXTRA));
extras.putString(VerifyDisplayFragment.REMOTE_NUMBER, recipient.getAddress().toPhoneString());
extras.putParcelable(VerifyDisplayFragment.LOCAL_IDENTITY, new IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(this)));
extras.putString(VerifyDisplayFragment.LOCAL_NUMBER, TextSecurePreferences.getLocalNumber(this));
extras.putBoolean(VerifyDisplayFragment.VERIFIED_STATE, getIntent().getBooleanExtra(VERIFIED_EXTRA, false));
scanFragment.setScanListener(this);
displayFragment.setClickListener(this);
initFragment(android.R.id.content, displayFragment, dynamicLanguage.getCurrentLocale(), extras);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: finish(); return true;
}
return false;
}
@Override
public void onModified(final Recipient recipient) {
Util.runOnMain(() -> setActionBarNotificationBarColor(recipient.getColor()));
}
@Override
public void onQrDataFound(final String data) {
Util.runOnMain(() -> {
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
getSupportFragmentManager().popBackStack();
displayFragment.setScannedFingerprint(data);
});
}
@Override
public void onClick(View v) {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.withPermanentDenialDialog(getString(R.string.VerifyIdentityActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code_but_it_has_been_permanently_denied))
.onAllGranted(() -> {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_from_top, R.anim.slide_to_bottom,
R.anim.slide_from_bottom, R.anim.slide_to_top);
transaction.replace(android.R.id.content, scanFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
})
.onAnyDenied(() -> Toast.makeText(this, R.string.VerifyIdentityActivity_unable_to_scan_qr_code_without_camera_permission, Toast.LENGTH_LONG).show())
.execute();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
private void setActionBarNotificationBarColor(MaterialColor color) {
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color.toActionBarColor(this)));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(color.toStatusBarColor(this));
}
}
public static class VerifyDisplayFragment extends Fragment implements RecipientModifiedListener, CompoundButton.OnCheckedChangeListener {
public static final String REMOTE_ADDRESS = "remote_address";
public static final String REMOTE_NUMBER = "remote_number";
public static final String REMOTE_IDENTITY = "remote_identity";
public static final String LOCAL_IDENTITY = "local_identity";
public static final String LOCAL_NUMBER = "local_number";
public static final String VERIFIED_STATE = "verified_state";
private Recipient recipient;
private String localNumber;
private String remoteNumber;
private IdentityKey localIdentity;
private IdentityKey remoteIdentity;
private Fingerprint fingerprint;
private View container;
private View numbersContainer;
private ImageView qrCode;
private ImageView qrVerified;
private TextView tapLabel;
private TextView description;
private View.OnClickListener clickListener;
private SwitchCompat verified;
private TextView[] codes = new TextView[12];
private boolean animateSuccessOnDraw = false;
private boolean animateFailureOnDraw = false;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_display_fragment);
this.numbersContainer = ViewUtil.findById(container, R.id.number_table);
this.qrCode = ViewUtil.findById(container, R.id.qr_code);
this.verified = ViewUtil.findById(container, R.id.verified_switch);
this.qrVerified = ViewUtil.findById(container, R.id.qr_verified);
this.description = ViewUtil.findById(container, R.id.description);
this.tapLabel = ViewUtil.findById(container, R.id.tap_label);
this.codes[0] = ViewUtil.findById(container, R.id.code_first);
this.codes[1] = ViewUtil.findById(container, R.id.code_second);
this.codes[2] = ViewUtil.findById(container, R.id.code_third);
this.codes[3] = ViewUtil.findById(container, R.id.code_fourth);
this.codes[4] = ViewUtil.findById(container, R.id.code_fifth);
this.codes[5] = ViewUtil.findById(container, R.id.code_sixth);
this.codes[6] = ViewUtil.findById(container, R.id.code_seventh);
this.codes[7] = ViewUtil.findById(container, R.id.code_eighth);
this.codes[8] = ViewUtil.findById(container, R.id.code_ninth);
this.codes[9] = ViewUtil.findById(container, R.id.code_tenth);
this.codes[10] = ViewUtil.findById(container, R.id.code_eleventh);
this.codes[11] = ViewUtil.findById(container, R.id.code_twelth);
this.qrCode.setOnClickListener(clickListener);
this.registerForContextMenu(numbersContainer);
this.verified.setChecked(getArguments().getBoolean(VERIFIED_STATE, false));
this.verified.setOnCheckedChangeListener(this);
return container;
}
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Address address = getArguments().getParcelable(REMOTE_ADDRESS);
IdentityKeyParcelable localIdentityParcelable = getArguments().getParcelable(LOCAL_IDENTITY);
IdentityKeyParcelable remoteIdentityParcelable = getArguments().getParcelable(REMOTE_IDENTITY);
if (address == null) throw new AssertionError("Address required");
if (localIdentityParcelable == null) throw new AssertionError("local identity required");
if (remoteIdentityParcelable == null) throw new AssertionError("remote identity required");
this.localNumber = getArguments().getString(LOCAL_NUMBER);
this.localIdentity = localIdentityParcelable.get();
this.remoteNumber = getArguments().getString(REMOTE_NUMBER);
this.recipient = Recipient.from(getActivity(), address, true);
this.remoteIdentity = remoteIdentityParcelable.get();
this.recipient.addListener(this);
new AsyncTask<Void, Void, Fingerprint>() {
@Override
protected Fingerprint doInBackground(Void... params) {
return new NumericFingerprintGenerator(5200).createFor(localNumber, localIdentity,
remoteNumber, remoteIdentity);
}
@Override
protected void onPostExecute(Fingerprint fingerprint) {
VerifyDisplayFragment.this.fingerprint = fingerprint;
setFingerprintViews(fingerprint, true);
getActivity().supportInvalidateOptionsMenu();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
setHasOptionsMenu(true);
}
@Override
public void onModified(final Recipient recipient) {
Util.runOnMain(() -> setRecipientText(recipient));
}
@Override
public void onResume() {
super.onResume();
setRecipientText(recipient);
if (fingerprint != null) {
setFingerprintViews(fingerprint, false);
}
if (animateSuccessOnDraw) {
animateSuccessOnDraw = false;
animateVerifiedSuccess();
} else if (animateFailureOnDraw) {
animateFailureOnDraw = false;
animateVerifiedFailure();
}
}
@Override
public void onDestroy() {
super.onDestroy();
recipient.removeListener(this);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view,
ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, view, menuInfo);
if (fingerprint != null) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.verify_display_fragment_context_menu, menu);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (fingerprint == null) return super.onContextItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_copy: handleCopyToClipboard(fingerprint, codes.length); return true;
case R.id.menu_compare: handleCompareWithClipboard(fingerprint); return true;
default: return super.onContextItemSelected(item);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (fingerprint != null) {
inflater.inflate(R.menu.verify_identity, menu);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.verify_identity__share: handleShare(fingerprint, codes.length); return true;
}
return false;
}
public void setScannedFingerprint(String scanned) {
try {
if (fingerprint.getScannableFingerprint().compareTo(scanned.getBytes("ISO-8859-1"))) {
this.animateSuccessOnDraw = true;
} else {
this.animateFailureOnDraw = true;
}
} catch (FingerprintVersionMismatchException e) {
Log.w(TAG, e);
if (e.getOurVersion() < e.getTheirVersion()) {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_a_newer_version_of_Signal, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_an_old_version_of_signal, Toast.LENGTH_LONG).show();
}
} catch (FingerprintParsingException e) {
Log.w(TAG, e);
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_the_scanned_qr_code_is_not_a_correctly_formatted_safety_number, Toast.LENGTH_LONG).show();
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
public void setClickListener(View.OnClickListener listener) {
this.clickListener = listener;
}
private @NonNull String getFormattedSafetyNumbers(@NonNull Fingerprint fingerprint, int segmentCount) {
String[] segments = getSegments(fingerprint, segmentCount);
StringBuilder result = new StringBuilder();
for (int i = 0; i < segments.length; i++) {
result.append(segments[i]);
if (i != segments.length - 1) {
if (((i+1) % 4) == 0) result.append('\n');
else result.append(' ');
}
}
return result.toString();
}
private void handleCopyToClipboard(Fingerprint fingerprint, int segmentCount) {
Util.writeTextToClipboard(getActivity(), getFormattedSafetyNumbers(fingerprint, segmentCount));
}
private void handleCompareWithClipboard(Fingerprint fingerprint) {
String clipboardData = Util.readTextFromClipboard(getActivity());
if (clipboardData == null) {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_safety_number_to_compare_was_found_in_the_clipboard, Toast.LENGTH_LONG).show();
return;
}
String numericClipboardData = clipboardData.replaceAll("\\D", "");
if (TextUtils.isEmpty(numericClipboardData) || numericClipboardData.length() != 60) {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_safety_number_to_compare_was_found_in_the_clipboard, Toast.LENGTH_LONG).show();
return;
}
if (fingerprint.getDisplayableFingerprint().getDisplayText().equals(numericClipboardData)) {
animateVerifiedSuccess();
} else {
animateVerifiedFailure();
}
}
private void handleShare(@NonNull Fingerprint fingerprint, int segmentCount) {
String shareString =
getString(R.string.VerifyIdentityActivity_our_signal_safety_number) + "\n" +
getFormattedSafetyNumbers(fingerprint, segmentCount) + "\n";
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, shareString);
intent.setType("text/plain");
try {
startActivity(Intent.createChooser(intent, getString(R.string.VerifyIdentityActivity_share_safety_number_via)));
} catch (ActivityNotFoundException e) {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_app_to_share_to, Toast.LENGTH_LONG).show();
}
}
private void setRecipientText(Recipient recipient) {
description.setText(Html.fromHtml(String.format(getActivity().getString(R.string.verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s), recipient.toShortString())));
description.setMovementMethod(LinkMovementMethod.getInstance());
}
private void setFingerprintViews(Fingerprint fingerprint, boolean animate) {
String[] segments = getSegments(fingerprint, codes.length);
for (int i=0;i<codes.length;i++) {
if (animate) setCodeSegment(codes[i], segments[i]);
else codes[i].setText(segments[i]);
}
byte[] qrCodeData = fingerprint.getScannableFingerprint().getSerialized();
String qrCodeString = new String(qrCodeData, Charset.forName("ISO-8859-1"));
Bitmap qrCodeBitmap = QrCode.create(qrCodeString);
qrCode.setImageBitmap(qrCodeBitmap);
if (animate) {
ViewUtil.fadeIn(qrCode, 1000);
ViewUtil.fadeIn(tapLabel, 1000);
} else {
qrCode.setVisibility(View.VISIBLE);
tapLabel.setVisibility(View.VISIBLE);
}
}
private void setCodeSegment(final TextView codeView, String segment) {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setObjectValues(0, Integer.parseInt(segment));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
codeView.setText(String.format("%05d", value));
}
});
valueAnimator.setEvaluator(new TypeEvaluator<Integer>() {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
return Math.round(startValue + (endValue - startValue) * fraction);
}
});
valueAnimator.setDuration(1000);
valueAnimator.start();
}
private String[] getSegments(Fingerprint fingerprint, int segmentCount) {
String[] segments = new String[segmentCount];
String digits = fingerprint.getDisplayableFingerprint().getDisplayText();
int partSize = digits.length() / segmentCount;
for (int i=0;i<segmentCount;i++) {
segments[i] = digits.substring(i * partSize, (i * partSize) + partSize);
}
return segments;
}
private Bitmap createVerifiedBitmap(int width, int height, @DrawableRes int id) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Bitmap check = BitmapFactory.decodeResource(getResources(), id);
float offset = (width - check.getWidth()) / 2;
canvas.drawBitmap(check, offset, offset, null);
return bitmap;
}
private void animateVerifiedSuccess() {
Bitmap qrBitmap = ((BitmapDrawable)qrCode.getDrawable()).getBitmap();
Bitmap qrSuccess = createVerifiedBitmap(qrBitmap.getWidth(), qrBitmap.getHeight(), R.drawable.ic_check_white_48dp);
qrVerified.setImageBitmap(qrSuccess);
qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.MULTIPLY);
animateVerified();
}
private void animateVerifiedFailure() {
Bitmap qrBitmap = ((BitmapDrawable)qrCode.getDrawable()).getBitmap();
Bitmap qrSuccess = createVerifiedBitmap(qrBitmap.getWidth(), qrBitmap.getHeight(), R.drawable.ic_close_white_48dp);
qrVerified.setImageBitmap(qrSuccess);
qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.MULTIPLY);
animateVerified();
}
private void animateVerified() {
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setInterpolator(new OvershootInterpolator());
scaleAnimation.setDuration(800);
scaleAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
qrVerified.postDelayed(new Runnable() {
@Override
public void run() {
ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0, 1, 0,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setInterpolator(new AnticipateInterpolator());
scaleAnimation.setDuration(500);
ViewUtil.animateOut(qrVerified, scaleAnimation, View.GONE);
}
}, 2000);
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
ViewUtil.animateIn(qrVerified, scaleAnimation);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
new AsyncTask<Recipient, Void, Void>() {
@Override
protected Void doInBackground(Recipient... params) {
synchronized (SESSION_LOCK) {
if (isChecked) {
Log.i(TAG, "Saving identity: " + params[0].getAddress());
DatabaseFactory.getIdentityDatabase(getActivity())
.saveIdentity(params[0].getAddress(),
remoteIdentity,
VerifiedStatus.VERIFIED, false,
System.currentTimeMillis(), true);
} else {
DatabaseFactory.getIdentityDatabase(getActivity())
.setVerified(params[0].getAddress(),
remoteIdentity,
VerifiedStatus.DEFAULT);
}
IdentityUtil.markIdentityVerified(getActivity(), recipient, isChecked, false);
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient);
}
}
public static class VerifyScanFragment extends Fragment {
private View container;
private CameraView cameraView;
private ScanningThread scanningThread;
private ScanListener scanListener;
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_scan_fragment);
this.cameraView = ViewUtil.findById(container, R.id.scanner);
return container;
}
@Override
public void onResume() {
super.onResume();
this.scanningThread = new ScanningThread();
this.scanningThread.setScanListener(scanListener);
this.scanningThread.setCharacterSet("ISO-8859-1");
this.cameraView.onResume();
this.cameraView.setPreviewCallback(scanningThread);
this.scanningThread.start();
}
@Override
public void onPause() {
super.onPause();
this.cameraView.onPause();
this.scanningThread.stopScanning();
}
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
super.onConfigurationChanged(newConfiguration);
this.cameraView.onPause();
this.cameraView.onResume();
this.cameraView.setPreviewCallback(scanningThread);
}
public void setScanListener(ScanListener listener) {
if (this.scanningThread != null) scanningThread.setScanListener(listener);
this.scanListener = listener;
}
}
}

View File

@ -2,49 +2,70 @@ package org.thoughtcrime.securesms.attachments
import android.content.Context
import com.google.protobuf.ByteString
import org.session.libsession.database.dto.DatabaseAttachmentDTO
import org.session.libsession.database.MessageDataProvider
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentPointer
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream
import org.session.libsignal.libsignal.util.guava.Optional
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob
import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.util.MediaUtil
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
override fun getAttachment(uniqueID: String): DatabaseAttachmentDTO? {
override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
val uniqueID = uniqueID.toLongOrNull() ?: return null
val attachmentID = AttachmentId(0, uniqueID)
val databaseAttachment = attachmentDatabase.getAttachment(attachmentID) ?: return null
val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
return databaseAttachment.toAttachmentStream(context)
}
return databaseAttachment.toDTO()
override fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer? {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
return databaseAttachment.toAttachmentPointer()
}
override fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long) {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
attachmentDatabase.setTransferState(messageID, AttachmentId(attachmentId, 0), attachmentState.value)
}
@Throws(Exception::class)
override fun uploadAttachment(attachmentId: Long) {
val attachmentUploadJob = AttachmentUploadJob(AttachmentId(attachmentId, 0), null)
attachmentUploadJob.onRun()
}
override fun isOutgoingMessage(timestamp: Long): Boolean {
val smsDatabase = DatabaseFactory.getSmsDatabase(context)
return smsDatabase.isOutgoingMessage(timestamp)
}
}
// Extension to DatabaseAttachment class
fun DatabaseAttachment.toAttachmentPointer(): SessionServiceAttachmentPointer {
return SessionServiceAttachmentPointer(attachmentId.rowId, contentType, key?.toByteArray(), Optional.fromNullable(size.toInt()), Optional.absent(), width, height, Optional.fromNullable(digest), Optional.fromNullable(fileName), isVoiceNote, Optional.fromNullable(caption), url)
}
fun DatabaseAttachment.toDTO(): DatabaseAttachmentDTO {
var databaseAttachmentDTO = DatabaseAttachmentDTO()
databaseAttachmentDTO.contentType = this.contentType
databaseAttachmentDTO.fileName = this.fileName
databaseAttachmentDTO.caption = this.caption
fun DatabaseAttachment.toAttachmentStream(context: Context): SessionServiceAttachmentStream {
val stream = PartAuthority.getAttachmentStream(context, this.dataUri!!)
var attachmentStream = SessionServiceAttachmentStream(stream, this.contentType, this.size, Optional.fromNullable(this.fileName), this.isVoiceNote, Optional.absent(), this.width, this.height, Optional.fromNullable(this.caption), null)
attachmentStream.attachmentId = this.attachmentId.rowId
attachmentStream.isAudio = MediaUtil.isAudio(this)
attachmentStream.isGif = MediaUtil.isGif(this)
attachmentStream.isVideo = MediaUtil.isVideo(this)
attachmentStream.isImage = MediaUtil.isImage(this)
databaseAttachmentDTO.size = this.size.toInt()
databaseAttachmentDTO.key = ByteString.copyFrom(this.key?.toByteArray())
databaseAttachmentDTO.digest = ByteString.copyFrom(this.digest)
databaseAttachmentDTO.flags = if (this.isVoiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0
attachmentStream.key = ByteString.copyFrom(this.key?.toByteArray())
attachmentStream.digest = this.digest
//attachmentStream.flags = if (this.isVoiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0
databaseAttachmentDTO.url = this.url
attachmentStream.url = this.url
if (this.shouldHaveImageSize()) {
databaseAttachmentDTO.shouldHaveImageSize = true
databaseAttachmentDTO.width = this.width
databaseAttachmentDTO.height = this.height
}
return databaseAttachmentDTO
return attachmentStream
}
fun DatabaseAttachment.shouldHaveImageSize(): Boolean {

View File

@ -1,66 +0,0 @@
package org.thoughtcrime.securesms.components.identity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import java.util.List;
import static org.session.libsignal.libsignal.SessionCipher.SESSION_LOCK;
public class UntrustedSendDialog extends AlertDialog.Builder implements DialogInterface.OnClickListener {
private final List<IdentityRecord> untrustedRecords;
private final ResendListener resendListener;
public UntrustedSendDialog(@NonNull Context context,
@NonNull String message,
@NonNull List<IdentityRecord> untrustedRecords,
@NonNull ResendListener resendListener)
{
super(context);
this.untrustedRecords = untrustedRecords;
this.resendListener = resendListener;
setTitle(R.string.UntrustedSendDialog_send_message);
setIconAttribute(R.attr.dialog_alert_icon);
setMessage(message);
setPositiveButton(R.string.UntrustedSendDialog_send, this);
setNegativeButton(android.R.string.cancel, null);
}
@Override
public void onClick(DialogInterface dialog, int which) {
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
synchronized (SESSION_LOCK) {
for (IdentityRecord identityRecord : untrustedRecords) {
identityDatabase.setApproval(identityRecord.getAddress(), true);
}
}
return null;
}
@Override
protected void onPostExecute(Void result) {
resendListener.onResendMessage();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public interface ResendListener {
public void onResendMessage();
}
}

View File

@ -1,97 +0,0 @@
package org.thoughtcrime.securesms.components.identity;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import android.util.AttributeSet;
import org.thoughtcrime.securesms.logging.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.List;
public class UnverifiedBannerView extends LinearLayout {
private static final String TAG = UnverifiedBannerView.class.getSimpleName();
private View container;
private TextView text;
private ImageView closeButton;
public UnverifiedBannerView(Context context) {
super(context);
initialize();
}
public UnverifiedBannerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize();
}
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
public UnverifiedBannerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public UnverifiedBannerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
private void initialize() {
LayoutInflater.from(getContext()).inflate(R.layout.unverified_banner_view, this, true);
this.container = ViewUtil.findById(this, R.id.container);
this.text = ViewUtil.findById(this, R.id.unverified_text);
this.closeButton = ViewUtil.findById(this, R.id.cancel);
}
public void display(@NonNull final String text,
@NonNull final List<IdentityRecord> unverifiedIdentities,
@NonNull final ClickListener clickListener,
@NonNull final DismissListener dismissListener)
{
this.text.setText(text);
setVisibility(View.VISIBLE);
this.container.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "onClick()");
clickListener.onClicked(unverifiedIdentities);
}
});
this.closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hide();
dismissListener.onDismissed(unverifiedIdentities);
}
});
}
public void hide() {
setVisibility(View.GONE);
}
public interface DismissListener {
public void onDismissed(List<IdentityRecord> unverifiedIdentities);
}
public interface ClickListener {
public void onClicked(List<IdentityRecord> unverifiedIdentities);
}
}

View File

@ -1,67 +0,0 @@
package org.thoughtcrime.securesms.components.identity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import java.util.List;
import static org.session.libsignal.libsignal.SessionCipher.SESSION_LOCK;
public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogInterface.OnClickListener {
private final List<IdentityRecord> untrustedRecords;
private final ResendListener resendListener;
public UnverifiedSendDialog(@NonNull Context context,
@NonNull String message,
@NonNull List<IdentityRecord> untrustedRecords,
@NonNull ResendListener resendListener)
{
super(context);
this.untrustedRecords = untrustedRecords;
this.resendListener = resendListener;
setTitle(R.string.UnverifiedSendDialog_send_message);
setIconAttribute(R.attr.dialog_alert_icon);
setMessage(message);
setPositiveButton(R.string.UnverifiedSendDialog_send, this);
setNegativeButton(android.R.string.cancel, null);
}
@Override
public void onClick(DialogInterface dialog, int which) {
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
synchronized (SESSION_LOCK) {
for (IdentityRecord identityRecord : untrustedRecords) {
identityDatabase.setVerified(identityRecord.getAddress(),
identityRecord.getIdentityKey(),
IdentityDatabase.VerifiedStatus.DEFAULT);
}
}
return null;
}
@Override
protected void onPostExecute(Void result) {
resendListener.onResendMessage();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public interface ResendListener {
public void onResendMessage();
}
}

View File

@ -98,7 +98,6 @@ import org.thoughtcrime.securesms.MediaOverviewActivity;
import org.thoughtcrime.securesms.MuteDialog;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
import org.thoughtcrime.securesms.VerifyIdentityActivity;
import org.thoughtcrime.securesms.audio.AudioRecorder;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.components.AnimatingToggle;
@ -113,29 +112,21 @@ import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiStrings;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.components.identity.UntrustedSendDialog;
import org.thoughtcrime.securesms.components.identity.UnverifiedBannerView;
import org.thoughtcrime.securesms.components.identity.UnverifiedSendDialog;
import org.thoughtcrime.securesms.components.location.SignalPlace;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
import org.thoughtcrime.securesms.database.DraftDatabase.Drafts;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
@ -201,7 +192,6 @@ import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.PushCharacterCalculator;
import org.thoughtcrime.securesms.util.ServiceUtil;
@ -219,7 +209,6 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@ -230,7 +219,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import kotlin.Unit;
import network.loki.messenger.R;
import static org.session.libsignal.libsignal.SessionCipher.SESSION_LOCK;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
/**
@ -291,7 +279,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private Button unblockButton;
private Button makeDefaultSmsButton;
private InputAwareLayout container;
private Stub<UnverifiedBannerView> unverifiedBannerView;
private Stub<GroupShareProfileView> groupShareProfileView;
private TypingStatusTextWatcher typingTextWatcher;
private MentionTextWatcher mentionTextWatcher;
@ -328,8 +315,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private int collapsedKeyboardHeight = Integer.MAX_VALUE;
private int keyboardHeight = 0;
private final IdentityRecordList identityRecords = new IdentityRecordList();
// Message status bar
private ArrayList<BroadcastReceiver> broadcastReceivers = new ArrayList<>();
private String messageStatus = null;
@ -506,7 +491,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
EventBus.getDefault().register(this);
initializeEnabledCheck();
initializeMmsEnabledCheck();
initializeIdentityRecords();
composeText.setTransport();
updateTitleTextView(recipient);
@ -514,7 +498,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateSubtitleTextView();
updateInputUI(recipient);
setGroupShareProfileReminder(recipient);
calculateCharactersRemaining();
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId);
markThreadAsRead();
@ -734,9 +717,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inflater.inflate(R.menu.conversation, menu);
if (isSingleConversation()) {
inflater.inflate(R.menu.conversation_secure, menu);
}
// if (isSingleConversation()) {
// inflater.inflate(R.menu.conversation_secure, menu);
// }
if (recipient != null && recipient.isMuted()) inflater.inflate(R.menu.conversation_muted, menu);
else inflater.inflate(R.menu.conversation_unmuted, menu);
@ -827,7 +810,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
case R.id.menu_add_shortcut: handleAddShortcut(); return true;
case R.id.menu_search: handleSearch(); return true;
// case R.id.menu_add_to_contacts: handleAddToContacts(); return true;
case R.id.menu_reset_secure_session: handleResetSecureSession(); return true;
// case R.id.menu_reset_secure_session: handleResetSecureSession(); return true;
// case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true;
case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true;
@ -1179,52 +1162,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private void handleUnverifiedRecipients() {
List<Recipient> unverifiedRecipients = identityRecords.getUnverifiedRecipients(this);
List<IdentityRecord> unverifiedRecords = identityRecords.getUnverifiedRecords();
String message = IdentityUtil.getUnverifiedSendDialogDescription(this, unverifiedRecipients);
if (message == null) return;
//noinspection CodeBlock2Expr
new UnverifiedSendDialog(this, message, unverifiedRecords, () -> {
initializeIdentityRecords().addListener(new ListenableFuture.Listener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
sendMessage();
}
@Override
public void onFailure(ExecutionException e) {
throw new AssertionError(e);
}
});
}).show();
}
private void handleUntrustedRecipients() {
List<Recipient> untrustedRecipients = identityRecords.getUntrustedRecipients(this);
List<IdentityRecord> untrustedRecords = identityRecords.getUntrustedRecords();
String untrustedMessage = IdentityUtil.getUntrustedSendDialogDescription(this, untrustedRecipients);
if (untrustedMessage == null) return;
//noinspection CodeBlock2Expr
new UntrustedSendDialog(this, untrustedMessage, untrustedRecords, () -> {
initializeIdentityRecords().addListener(new ListenableFuture.Listener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
sendMessage();
}
@Override
public void onFailure(ExecutionException e) {
throw new AssertionError(e);
}
});
}).show();
}
private void handleSecurityChange(boolean isSecureText, boolean isDefaultSms) {
Log.i(TAG, "handleSecurityChange(" + isSecureText + ", " + isDefaultSms + ")");
if (isSecurityInitialized && isSecureText == true && isDefaultSms == this.isDefaultSms) {
@ -1250,7 +1187,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
*/
calculateCharactersRemaining();
supportInvalidateOptionsMenu();
updateInputUI(recipient);
}
@ -1430,62 +1366,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private ListenableFuture<Boolean> initializeIdentityRecords() {
final SettableFuture<Boolean> future = new SettableFuture<>();
new AsyncTask<Recipient, Void, Pair<IdentityRecordList, String>>() {
@Override
protected @NonNull Pair<IdentityRecordList, String> doInBackground(Recipient... params) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ConversationActivity.this);
IdentityRecordList identityRecordList = new IdentityRecordList();
List<Recipient> recipients = new LinkedList<>();
if (params[0].isGroupRecipient()) {
recipients.addAll(DatabaseFactory.getGroupDatabase(ConversationActivity.this)
.getGroupMembers(params[0].getAddress().toGroupString(), false));
} else {
recipients.add(params[0]);
}
for (Recipient recipient : recipients) {
Log.i(TAG, "Loading identity for: " + recipient.getAddress());
identityRecordList.add(identityDatabase.getIdentity(recipient.getAddress()));
}
String message = null;
if (identityRecordList.isUnverified()) {
message = IdentityUtil.getUnverifiedBannerDescription(ConversationActivity.this, identityRecordList.getUnverifiedRecipients(ConversationActivity.this));
}
return new Pair<>(identityRecordList, message);
}
@Override
protected void onPostExecute(@NonNull Pair<IdentityRecordList, String> result) {
Log.i(TAG, "Got identity records: " + result.first.isUnverified());
identityRecords.replaceWith(result.first);
if (result.second != null) {
Log.d(TAG, "Replacing banner...");
unverifiedBannerView.get().display(result.second, result.first.getUnverifiedRecords(),
new UnverifiedClickedListener(),
new UnverifiedDismissedListener());
} else if (unverifiedBannerView.resolved()) {
Log.d(TAG, "Clearing banner...");
unverifiedBannerView.get().hide();
}
// titleView.setVerified(isSecureText && identityRecords.isVerified());
future.set(true);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient);
return future;
}
private void initializeViews() {
profilePictureView = findViewById(R.id.profilePictureView);
titleTextView = findViewById(R.id.titleTextView);
@ -1498,7 +1378,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
unblockButton = ViewUtil.findById(this, R.id.unblock_button);
makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button);
container = ViewUtil.findById(this, R.id.layout_container);
unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub);
groupShareProfileView = ViewUtil.findStubById(this, R.id.group_share_profile_view_stub);
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container);
@ -1700,11 +1579,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
});
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onIdentityRecordUpdate(final IdentityRecord event) {
initializeIdentityRecords();
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onStickerPackInstalled(final StickerPackInstallEvent event) {
if (!TextSecurePreferences.hasSeenStickerIntroTooltip(this)) return;
@ -1731,7 +1605,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onReceive(Context context, Intent intent) {
initializeSecurity(true, isDefaultSms);
calculateCharactersRemaining();
}
};
@ -1931,25 +1804,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private void calculateCharactersRemaining() {
/*
String messageBody = composeText.getTextTrimmed();
TransportOption transportOption = sendButton.getSelectedTransport();
CharacterState characterState = transportOption.calculateCharacters(messageBody);
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
charactersLeft.setText(String.format(dynamicLanguage.getCurrentLocale(),
"%d/%d (%d)",
characterState.charactersRemaining,
characterState.maxTotalMessageSize,
characterState.messagesSpent));
charactersLeft.setVisibility(View.VISIBLE);
} else {
charactersLeft.setVisibility(View.GONE);
}
*/
}
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard, boolean stickersAvailable) {
boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this);
@ -2127,9 +1981,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
LinkPreviewUtil.isValidMediaUrl(message) || // Loki - Send GIFs as media messages
needsSplit;
if (identityRecords.isUnverified()) {
handleUnverifiedRecipients();
} else if (isMediaMessage) {
if (isMediaMessage) {
sendMediaMessage(expiresIn, subscriptionId, initiating);
} else {
sendTextMessage(expiresIn, subscriptionId, initiating);
@ -2552,7 +2404,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void afterTextChanged(Editable s) {
calculateCharactersRemaining();
if (composeText.getTextTrimmed().length() == 0 || beforeLength == 0) {
composeText.postDelayed(ConversationActivity.this::updateToggleButtonState, 50);
@ -2719,67 +2570,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateLinkPreviewState();
}
private class UnverifiedDismissedListener implements UnverifiedBannerView.DismissListener {
@Override
public void onDismissed(final List<IdentityRecord> unverifiedIdentities) {
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ConversationActivity.this);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
synchronized (SESSION_LOCK) {
for (IdentityRecord identityRecord : unverifiedIdentities) {
identityDatabase.setVerified(identityRecord.getAddress(),
identityRecord.getIdentityKey(),
VerifiedStatus.DEFAULT);
}
}
return null;
}
@Override
protected void onPostExecute(Void result) {
initializeIdentityRecords();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private class UnverifiedClickedListener implements UnverifiedBannerView.ClickListener {
@Override
public void onClicked(final List<IdentityRecord> unverifiedIdentities) {
Log.i(TAG, "onClicked: " + unverifiedIdentities.size());
if (unverifiedIdentities.size() == 1) {
Intent intent = new Intent(ConversationActivity.this, VerifyIdentityActivity.class);
intent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, unverifiedIdentities.get(0).getAddress());
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(unverifiedIdentities.get(0).getIdentityKey()));
intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, false);
startActivity(intent);
} else {
String[] unverifiedNames = new String[unverifiedIdentities.size()];
for (int i=0;i<unverifiedIdentities.size();i++) {
unverifiedNames[i] = Recipient.from(ConversationActivity.this, unverifiedIdentities.get(i).getAddress(), false).toShortString();
}
AlertDialog.Builder builder = new AlertDialog.Builder(ConversationActivity.this);
builder.setIconAttribute(R.attr.dialog_alert_icon);
builder.setTitle("No longer verified");
builder.setItems(unverifiedNames, (dialog, which) -> {
Intent intent = new Intent(ConversationActivity.this, VerifyIdentityActivity.class);
intent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, unverifiedIdentities.get(which).getAddress());
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(unverifiedIdentities.get(which).getIdentityKey()));
intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, false);
startActivity(intent);
});
builder.show();
}
}
}
private class QuoteRestorationTask extends AsyncTask<Void, Void, MessageRecord> {
private final String serialized;

View File

@ -48,8 +48,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
@ -66,8 +64,6 @@ import org.thoughtcrime.securesms.ShareActivity;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.components.ConversationTypingView;
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
import org.thoughtcrime.securesms.database.Address;

View File

@ -53,14 +53,15 @@ import androidx.appcompat.app.AlertDialog;
import com.annimon.stream.Stream;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.ConfirmIdentityDialog;
import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.MessageDetailsActivity;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.ConversationItemAlertView;
import org.thoughtcrime.securesms.loki.views.MessageAudioView;
import org.thoughtcrime.securesms.components.ConversationItemFooter;
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
import org.thoughtcrime.securesms.components.DocumentView;
@ -73,7 +74,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
@ -86,8 +86,8 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
import org.thoughtcrime.securesms.loki.views.MessageAudioView;
import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
import org.thoughtcrime.securesms.loki.views.TapJackingProofLinearLayout;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.PartAuthority;
@ -108,9 +108,6 @@ import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import java.util.Collections;
import java.util.HashSet;
@ -128,7 +125,7 @@ import network.loki.messenger.R;
*
*/
public class ConversationItem extends TapJackingProofLinearLayout
public class ConversationItem extends LinearLayout
implements RecipientModifiedListener, BindableConversationItem
{
private static final String TAG = ConversationItem.class.getSimpleName();
@ -1036,16 +1033,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
/// Event handlers
private void handleApproveIdentity() {
List<IdentityKeyMismatch> mismatches = messageRecord.getIdentityKeyMismatches();
if (mismatches.size() != 1) {
throw new AssertionError("Identity mismatch count: " + mismatches.size());
}
new ConfirmIdentityDialog(context, messageRecord, mismatches.get(0)).show();
}
private Spannable getLongMessageSpan(@NonNull MessageRecord messageRecord) {
String message;
Runnable action;
@ -1238,7 +1225,7 @@ public class ConversationItem extends TapJackingProofLinearLayout
intent.putExtra(MessageDetailsActivity.ADDRESS_EXTRA, conversationRecipient.getAddress());
context.startActivity(intent);
} else if (!messageRecord.isOutgoing() && messageRecord.isIdentityMismatchFailure()) {
handleApproveIdentity();
// handleApproveIdentity();
} else if (messageRecord.isPendingInsecureSmsFallback()) {
handleMessageApproval();
}

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.conversation;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@ -16,12 +15,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.VerifyIdentityActivity;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -29,17 +23,15 @@ import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import network.loki.messenger.R;
//TODO Remove this class.
public class ConversationUpdateItem extends LinearLayout
implements RecipientModifiedListener, BindableConversationItem
{
@ -256,24 +248,24 @@ public class ConversationUpdateItem extends LinearLayout
final Recipient sender = ConversationUpdateItem.this.sender;
IdentityUtil.getRemoteIdentityKey(getContext(), sender).addListener(new ListenableFuture.Listener<Optional<IdentityRecord>>() {
@Override
public void onSuccess(Optional<IdentityRecord> result) {
if (result.isPresent()) {
Intent intent = new Intent(getContext(), VerifyIdentityActivity.class);
intent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, sender.getAddress());
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(result.get().getIdentityKey()));
intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, result.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
getContext().startActivity(intent);
}
}
@Override
public void onFailure(ExecutionException e) {
Log.w(TAG, e);
}
});
// IdentityUtil.getRemoteIdentityKey(getContext(), sender).addListener(new ListenableFuture.Listener<Optional<IdentityRecord>>() {
// @Override
// public void onSuccess(Optional<IdentityRecord> result) {
// if (result.isPresent()) {
// Intent intent = new Intent(getContext(), VerifyIdentityActivity.class);
// intent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, sender.getAddress());
// intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(result.get().getIdentityKey()));
// intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, result.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
//
// getContext().startActivity(intent);
// }
// }
//
// @Override
// public void onFailure(ExecutionException e) {
// Log.w(TAG, e);
// }
// });
}
}

View File

@ -1,138 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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 org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Util;
import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.ecc.Curve;
import org.session.libsignal.libsignal.ecc.ECKeyPair;
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
import org.session.libsignal.libsignal.ecc.ECPublicKey;
import org.thoughtcrime.securesms.util.Conversions;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* This class is used to asymmetricly encrypt local data. This is used in the case
* where TextSecure receives an SMS, but the user's local encryption passphrase is
* not cached (either because of a timeout, or because it hasn't yet been entered).
*
* In this case, we have access to the public key of a local keypair. We encrypt
* the message with this, and put it into the DB. When the user enters their passphrase,
* we can get access to the private key of the local keypair, decrypt the message, and
* replace it into the DB with symmetric encryption.
*
* The encryption protocol is as follows:
*
* 1) Generate an ephemeral keypair.
* 2) Do ECDH with the public key of the local durable keypair.
* 3) Do KMF with the ECDH result to obtain a master secret.
* 4) Encrypt the message with that master secret.
*
* @author Moxie Marlinspike
*
*/
public class AsymmetricMasterCipher {
private final AsymmetricMasterSecret asymmetricMasterSecret;
public AsymmetricMasterCipher(AsymmetricMasterSecret asymmetricMasterSecret) {
this.asymmetricMasterSecret = asymmetricMasterSecret;
}
public byte[] encryptBytes(byte[] body) {
try {
ECPublicKey theirPublic = asymmetricMasterSecret.getDjbPublicKey();
ECKeyPair ourKeyPair = Curve.generateKeyPair();
byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey());
MasterCipher masterCipher = getMasterCipherForSecret(secret);
byte[] encryptedBodyBytes = masterCipher.encryptBytes(body);
PublicKey ourPublicKey = new PublicKey(31337, ourKeyPair.getPublicKey());
byte[] publicKeyBytes = ourPublicKey.serialize();
return Util.combine(publicKeyBytes, encryptedBodyBytes);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public byte[] decryptBytes(byte[] combined) throws IOException, InvalidMessageException {
try {
byte[][] parts = Util.split(combined, PublicKey.KEY_SIZE, combined.length - PublicKey.KEY_SIZE);
PublicKey theirPublicKey = new PublicKey(parts[0], 0);
ECPrivateKey ourPrivateKey = asymmetricMasterSecret.getPrivateKey();
byte[] secret = Curve.calculateAgreement(theirPublicKey.getKey(), ourPrivateKey);
MasterCipher masterCipher = getMasterCipherForSecret(secret);
return masterCipher.decryptBytes(parts[1]);
} catch (InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
public String decryptBody(String body) throws IOException, InvalidMessageException {
byte[] combined = Base64.decode(body);
return new String(decryptBytes(combined));
}
public String encryptBody(String body) {
return Base64.encodeBytes(encryptBytes(body.getBytes()));
}
private MasterCipher getMasterCipherForSecret(byte[] secretBytes) {
SecretKeySpec cipherKey = deriveCipherKey(secretBytes);
SecretKeySpec macKey = deriveMacKey(secretBytes);
MasterSecret masterSecret = new MasterSecret(cipherKey, macKey);
return new MasterCipher(masterSecret);
}
private SecretKeySpec deriveMacKey(byte[] secretBytes) {
byte[] digestedBytes = getDigestedBytes(secretBytes, 1);
byte[] macKeyBytes = new byte[20];
System.arraycopy(digestedBytes, 0, macKeyBytes, 0, macKeyBytes.length);
return new SecretKeySpec(macKeyBytes, "HmacSHA1");
}
private SecretKeySpec deriveCipherKey(byte[] secretBytes) {
byte[] digestedBytes = getDigestedBytes(secretBytes, 0);
byte[] cipherKeyBytes = new byte[16];
System.arraycopy(digestedBytes, 0, cipherKeyBytes, 0, cipherKeyBytes.length);
return new SecretKeySpec(cipherKeyBytes, "AES");
}
private byte[] getDigestedBytes(byte[] secretBytes, int iteration) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secretBytes, "HmacSHA256"));
return mac.doFinal(Conversions.intToByteArray(iteration));
} catch (NoSuchAlgorithmException | java.security.InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

@ -22,6 +22,7 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import androidx.annotation.NonNull;
import org.session.libsignal.libsignal.ecc.ECPublicKey;
import org.thoughtcrime.securesms.backup.BackupProtos;
import org.thoughtcrime.securesms.util.Base64;
import org.session.libsignal.libsignal.IdentityKey;
@ -40,15 +41,13 @@ import java.util.List;
*
* @author Moxie Marlinspike
*/
public class IdentityKeyUtil {
private static final String MASTER_SECRET_UTIL_PREFERENCES_NAME = "SecureSMS-Preferences";
@SuppressWarnings("unused")
private static final String TAG = IdentityKeyUtil.class.getSimpleName();
private static final String IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_public_curve25519";
private static final String IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_private_curve25519";
public static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public_v3";
public static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3";
public static final String ED25519_PUBLIC_KEY = "pref_ed25519_public_key";
@ -56,7 +55,7 @@ public class IdentityKeyUtil {
public static final String LOKI_SEED = "loki_seed";
public static boolean hasIdentityKey(Context context) {
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
return
preferences.contains(IDENTITY_PUBLIC_KEY_PREF) &&
@ -87,63 +86,46 @@ public class IdentityKeyUtil {
}
}
public static void generateIdentityKeyPair(Context context) {
ECKeyPair keyPair = Curve.generateKeyPair();;
IdentityKey publicKey = new IdentityKey(keyPair.getPublicKey());
public static void generateIdentityKeyPair(@NonNull Context context) {
ECKeyPair keyPair = Curve.generateKeyPair();
ECPublicKey publicKey = keyPair.getPublicKey();
ECPrivateKey privateKey = keyPair.getPrivateKey();
save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(publicKey.serialize()));
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(privateKey.serialize()));
}
public static void migrateIdentityKeys(@NonNull Context context,
@NonNull MasterSecret masterSecret)
{
if (!hasIdentityKey(context)) {
if (hasLegacyIdentityKeys(context)) {
IdentityKeyPair legacyPair = getLegacyIdentityKeyPair(context, masterSecret);
save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(legacyPair.getPublicKey().serialize()));
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(legacyPair.getPrivateKey().serialize()));
delete(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF);
delete(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF);
} else {
generateIdentityKeyPair(context);
}
}
}
public static List<BackupProtos.SharedPreference> getBackupRecords(@NonNull Context context) {
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
final String prefName = MASTER_SECRET_UTIL_PREFERENCES_NAME;
SharedPreferences preferences = context.getSharedPreferences(prefName, 0);
LinkedList<BackupProtos.SharedPreference> prefList = new LinkedList<>();
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setFile(prefName)
.setKey(IDENTITY_PUBLIC_KEY_PREF)
.setValue(preferences.getString(IDENTITY_PUBLIC_KEY_PREF, null))
.build());
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setFile(prefName)
.setKey(IDENTITY_PRIVATE_KEY_PREF)
.setValue(preferences.getString(IDENTITY_PRIVATE_KEY_PREF, null))
.build());
if (preferences.contains(ED25519_PUBLIC_KEY)) {
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setFile(prefName)
.setKey(ED25519_PUBLIC_KEY)
.setValue(preferences.getString(ED25519_PUBLIC_KEY, null))
.build());
}
if (preferences.contains(ED25519_SECRET_KEY)) {
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setFile(prefName)
.setKey(ED25519_SECRET_KEY)
.setValue(preferences.getString(ED25519_SECRET_KEY, null))
.build());
}
prefList.add(BackupProtos.SharedPreference.newBuilder()
.setFile(MasterSecretUtil.PREFERENCES_NAME)
.setFile(prefName)
.setKey(LOKI_SEED)
.setValue(preferences.getString(LOKI_SEED, null))
.build());
@ -151,34 +133,13 @@ public class IdentityKeyUtil {
return prefList;
}
private static boolean hasLegacyIdentityKeys(Context context) {
return
retrieve(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF) != null &&
retrieve(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF) != null;
}
private static IdentityKeyPair getLegacyIdentityKeyPair(@NonNull Context context,
@NonNull MasterSecret masterSecret)
{
try {
MasterCipher masterCipher = new MasterCipher(masterSecret);
byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF));
IdentityKey identityKey = new IdentityKey(publicKeyBytes, 0);
ECPrivateKey privateKey = masterCipher.decryptKey(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF)));
return new IdentityKeyPair(identityKey, privateKey);
} catch (IOException | InvalidKeyException e) {
throw new AssertionError(e);
}
}
public static String retrieve(Context context, String key) {
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
return preferences.getString(key, null);
}
public static void save(Context context, String key, String value) {
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
Editor preferencesEditor = preferences.edit();
preferencesEditor.putString(key, value);
@ -186,6 +147,6 @@ public class IdentityKeyUtil {
}
public static void delete(Context context, String key) {
context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0).edit().remove(key).commit();
context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0).edit().remove(key).commit();
}
}

View File

@ -1,225 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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 androidx.annotation.NonNull;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Hex;
import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.ecc.Curve;
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Class that handles encryption for local storage.
*
* The protocol format is roughly:
*
* 1) 16 byte random IV.
* 2) AES-CBC(plaintext)
* 3) HMAC-SHA1 of 1 and 2
*
* @author Moxie Marlinspike
*/
public class MasterCipher {
private static final String TAG = MasterCipher.class.getSimpleName();
private final MasterSecret masterSecret;
private final Cipher encryptingCipher;
private final Cipher decryptingCipher;
private final Mac hmac;
public MasterCipher(MasterSecret masterSecret) {
try {
this.masterSecret = masterSecret;
this.encryptingCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
this.decryptingCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
this.hmac = Mac.getInstance("HmacSHA1");
} catch (NoSuchPaddingException | NoSuchAlgorithmException nspe) {
throw new AssertionError(nspe);
}
}
public byte[] encryptKey(ECPrivateKey privateKey) {
return encryptBytes(privateKey.serialize());
}
public String encryptBody(@NonNull String body) {
return encryptAndEncodeBytes(body.getBytes());
}
public String decryptBody(String body) throws InvalidMessageException {
return new String(decodeAndDecryptBytes(body));
}
public ECPrivateKey decryptKey(byte[] key)
throws org.session.libsignal.libsignal.InvalidKeyException
{
try {
return Curve.decodePrivatePoint(decryptBytes(key));
} catch (InvalidMessageException ime) {
throw new org.session.libsignal.libsignal.InvalidKeyException(ime);
}
}
public byte[] decryptBytes(@NonNull byte[] decodedBody) throws InvalidMessageException {
try {
Mac mac = getMac(masterSecret.getMacKey());
byte[] encryptedBody = verifyMacBody(mac, decodedBody);
Cipher cipher = getDecryptingCipher(masterSecret.getEncryptionKey(), encryptedBody);
byte[] encrypted = getDecryptedBody(cipher, encryptedBody);
return encrypted;
} catch (GeneralSecurityException ge) {
throw new InvalidMessageException(ge);
}
}
public byte[] encryptBytes(byte[] body) {
try {
Cipher cipher = getEncryptingCipher(masterSecret.getEncryptionKey());
Mac mac = getMac(masterSecret.getMacKey());
byte[] encryptedBody = getEncryptedBody(cipher, body);
byte[] encryptedAndMacBody = getMacBody(mac, encryptedBody);
return encryptedAndMacBody;
} catch (GeneralSecurityException ge) {
Log.w("bodycipher", ge);
return null;
}
}
public boolean verifyMacFor(String content, byte[] theirMac) {
byte[] ourMac = getMacFor(content);
Log.i(TAG, "Our Mac: " + Hex.toString(ourMac));
Log.i(TAG, "Thr Mac: " + Hex.toString(theirMac));
return Arrays.equals(ourMac, theirMac);
}
public byte[] getMacFor(String content) {
Log.w(TAG, "Macing: " + content);
try {
Mac mac = getMac(masterSecret.getMacKey());
return mac.doFinal(content.getBytes());
} catch (GeneralSecurityException ike) {
throw new AssertionError(ike);
}
}
private byte[] decodeAndDecryptBytes(String body) throws InvalidMessageException {
try {
byte[] decodedBody = Base64.decode(body);
return decryptBytes(decodedBody);
} catch (IOException e) {
throw new InvalidMessageException("Bad Base64 Encoding...", e);
}
}
private String encryptAndEncodeBytes(@NonNull byte[] bytes) {
byte[] encryptedAndMacBody = encryptBytes(bytes);
return Base64.encodeBytes(encryptedAndMacBody);
}
private byte[] verifyMacBody(@NonNull Mac hmac, @NonNull byte[] encryptedAndMac) throws InvalidMessageException {
if (encryptedAndMac.length < hmac.getMacLength()) {
throw new InvalidMessageException("length(encrypted body + MAC) < length(MAC)");
}
byte[] encrypted = new byte[encryptedAndMac.length - hmac.getMacLength()];
System.arraycopy(encryptedAndMac, 0, encrypted, 0, encrypted.length);
byte[] remoteMac = new byte[hmac.getMacLength()];
System.arraycopy(encryptedAndMac, encryptedAndMac.length - remoteMac.length, remoteMac, 0, remoteMac.length);
byte[] localMac = hmac.doFinal(encrypted);
if (!Arrays.equals(remoteMac, localMac))
throw new InvalidMessageException("MAC doesen't match.");
return encrypted;
}
private byte[] getDecryptedBody(Cipher cipher, byte[] encryptedBody) throws IllegalBlockSizeException, BadPaddingException {
return cipher.doFinal(encryptedBody, cipher.getBlockSize(), encryptedBody.length - cipher.getBlockSize());
}
private byte[] getEncryptedBody(Cipher cipher, byte[] body) throws IllegalBlockSizeException, BadPaddingException {
byte[] encrypted = cipher.doFinal(body);
byte[] iv = cipher.getIV();
byte[] ivAndBody = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, ivAndBody, 0, iv.length);
System.arraycopy(encrypted, 0, ivAndBody, iv.length, encrypted.length);
return ivAndBody;
}
private Mac getMac(SecretKeySpec key) throws NoSuchAlgorithmException, InvalidKeyException {
// Mac hmac = Mac.getInstance("HmacSHA1");
hmac.init(key);
return hmac;
}
private byte[] getMacBody(Mac hmac, byte[] encryptedBody) {
byte[] mac = hmac.doFinal(encryptedBody);
byte[] encryptedAndMac = new byte[encryptedBody.length + mac.length];
System.arraycopy(encryptedBody, 0, encryptedAndMac, 0, encryptedBody.length);
System.arraycopy(mac, 0, encryptedAndMac, encryptedBody.length, mac.length);
return encryptedAndMac;
}
private Cipher getDecryptingCipher(SecretKeySpec key, byte[] encryptedBody) throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException {
// Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(encryptedBody, 0, decryptingCipher.getBlockSize());
decryptingCipher.init(Cipher.DECRYPT_MODE, key, iv);
return decryptingCipher;
}
private Cipher getEncryptingCipher(SecretKeySpec key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
// Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
encryptingCipher.init(Cipher.ENCRYPT_MODE, key);
return encryptingCipher;
}
}

View File

@ -1,119 +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 android.os.Parcel;
import android.os.Parcelable;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
/**
* When a user first initializes TextSecure, a few secrets
* are generated. These are:
*
* 1) A 128bit symmetric encryption key.
* 2) A 160bit symmetric MAC key.
* 3) An ECC keypair.
*
* The first two, along with the ECC keypair's private key, are
* then encrypted on disk using PBE.
*
* This class represents 1 and 2.
*
* @author Moxie Marlinspike
*/
public class MasterSecret implements Parcelable {
private final SecretKeySpec encryptionKey;
private final SecretKeySpec macKey;
public static final Parcelable.Creator<MasterSecret> CREATOR = new Parcelable.Creator<MasterSecret>() {
@Override
public MasterSecret createFromParcel(Parcel in) {
return new MasterSecret(in);
}
@Override
public MasterSecret[] newArray(int size) {
return new MasterSecret[size];
}
};
public MasterSecret(SecretKeySpec encryptionKey, SecretKeySpec macKey) {
this.encryptionKey = encryptionKey;
this.macKey = macKey;
}
private MasterSecret(Parcel in) {
byte[] encryptionKeyBytes = new byte[in.readInt()];
in.readByteArray(encryptionKeyBytes);
byte[] macKeyBytes = new byte[in.readInt()];
in.readByteArray(macKeyBytes);
this.encryptionKey = new SecretKeySpec(encryptionKeyBytes, "AES");
this.macKey = new SecretKeySpec(macKeyBytes, "HmacSHA1");
// SecretKeySpec does an internal copy in its constructor.
Arrays.fill(encryptionKeyBytes, (byte) 0x00);
Arrays.fill(macKeyBytes, (byte)0x00);
}
public SecretKeySpec getEncryptionKey() {
return this.encryptionKey;
}
public SecretKeySpec getMacKey() {
return this.macKey;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(encryptionKey.getEncoded().length);
out.writeByteArray(encryptionKey.getEncoded());
out.writeInt(macKey.getEncoded().length);
out.writeByteArray(macKey.getEncoded());
}
@Override
public int describeContents() {
return 0;
}
public MasterSecret parcelClone() {
Parcel thisParcel = Parcel.obtain();
Parcel thatParcel = Parcel.obtain();
byte[] bytes = null;
thisParcel.writeValue(this);
bytes = thisParcel.marshall();
thatParcel.unmarshall(bytes, 0, bytes.length);
thatParcel.setDataPosition(0);
MasterSecret that = (MasterSecret)thatParcel.readValue(MasterSecret.class.getClassLoader());
thisParcel.recycle();
thatParcel.recycle();
return that;
}
}

View File

@ -1,374 +0,0 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open 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 android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Util;
import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.ecc.Curve;
import org.session.libsignal.libsignal.ecc.ECKeyPair;
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
import org.session.libsignal.libsignal.ecc.ECPublicKey;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Helper class for generating and securely storing a MasterSecret.
*
* @author Moxie Marlinspike
*/
public class MasterSecretUtil {
public static final String UNENCRYPTED_PASSPHRASE = "unencrypted";
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
private static final String ASYMMETRIC_LOCAL_PUBLIC_DJB = "asymmetric_master_secret_curve25519_public";
private static final String ASYMMETRIC_LOCAL_PRIVATE_DJB = "asymmetric_master_secret_curve25519_private";
public static MasterSecret changeMasterSecretPassphrase(Context context,
MasterSecret masterSecret,
String newPassphrase)
{
try {
byte[] combinedSecrets = Util.combine(masterSecret.getEncryptionKey().getEncoded(),
masterSecret.getMacKey().getEncoded());
byte[] encryptionSalt = generateSalt();
int iterations = generateIterationCount(newPassphrase, encryptionSalt);
byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, combinedSecrets, newPassphrase);
byte[] macSalt = generateSalt();
byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, newPassphrase);
save(context, "encryption_salt", encryptionSalt);
save(context, "mac_salt", macSalt);
save(context, "passphrase_iterations", iterations);
save(context, "master_secret", encryptedAndMacdMasterSecret);
save(context, "passphrase_initialized", true);
return masterSecret;
} catch (GeneralSecurityException gse) {
throw new AssertionError(gse);
}
}
public static MasterSecret changeMasterSecretPassphrase(Context context,
String originalPassphrase,
String newPassphrase)
throws InvalidPassphraseException
{
MasterSecret masterSecret = getMasterSecret(context, originalPassphrase);
changeMasterSecretPassphrase(context, masterSecret, newPassphrase);
return masterSecret;
}
public static MasterSecret getMasterSecret(Context context, String passphrase)
throws InvalidPassphraseException
{
try {
byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");
byte[] macSalt = retrieve(context, "mac_salt");
int iterations = retrieve(context, "passphrase_iterations", 100);
byte[] encryptedMasterSecret = verifyMac(macSalt, iterations, encryptedAndMacdMasterSecret, passphrase);
byte[] encryptionSalt = retrieve(context, "encryption_salt");
byte[] combinedSecrets = decryptWithPassphrase(encryptionSalt, iterations, encryptedMasterSecret, passphrase);
byte[] encryptionSecret = Util.split(combinedSecrets, 16, 20)[0];
byte[] macSecret = Util.split(combinedSecrets, 16, 20)[1];
return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
new SecretKeySpec(macSecret, "HmacSHA1"));
} catch (GeneralSecurityException e) {
Log.w("keyutil", e);
return null; //XXX
} catch (IOException e) {
Log.w("keyutil", e);
return null; //XXX
}
}
public static AsymmetricMasterSecret getAsymmetricMasterSecret(@NonNull Context context,
@Nullable MasterSecret masterSecret)
{
try {
byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB);
byte[] djbPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_DJB);
ECPublicKey djbPublicKey = null;
ECPrivateKey djbPrivateKey = null;
if (djbPublicBytes != null) {
djbPublicKey = Curve.decodePoint(djbPublicBytes, 0);
}
if (masterSecret != null) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
if (djbPrivateBytes != null) {
djbPrivateKey = masterCipher.decryptKey(djbPrivateBytes);
}
}
return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey);
} catch (InvalidKeyException | IOException ike) {
throw new AssertionError(ike);
}
}
public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context,
MasterSecret masterSecret)
{
MasterCipher masterCipher = new MasterCipher(masterSecret);
ECKeyPair keyPair = Curve.generateKeyPair();
save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize());
save(context, ASYMMETRIC_LOCAL_PRIVATE_DJB, masterCipher.encryptKey(keyPair.getPrivateKey()));
return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey());
}
public static MasterSecret generateMasterSecret(Context context, String passphrase) {
try {
byte[] encryptionSecret = generateEncryptionSecret();
byte[] macSecret = generateMacSecret();
byte[] masterSecret = Util.combine(encryptionSecret, macSecret);
byte[] encryptionSalt = generateSalt();
int iterations = generateIterationCount(passphrase, encryptionSalt);
byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, masterSecret, passphrase);
byte[] macSalt = generateSalt();
byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, passphrase);
save(context, "encryption_salt", encryptionSalt);
save(context, "mac_salt", macSalt);
save(context, "passphrase_iterations", iterations);
save(context, "master_secret", encryptedAndMacdMasterSecret);
save(context, "passphrase_initialized", true);
return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
new SecretKeySpec(macSecret, "HmacSHA1"));
} catch (GeneralSecurityException e) {
Log.w("keyutil", e);
return null;
}
}
public static boolean hasAsymmericMasterSecret(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
return settings.contains(ASYMMETRIC_LOCAL_PUBLIC_DJB);
}
public static boolean isPassphraseInitialized(Context context) {
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, 0);
return preferences.getBoolean("passphrase_initialized", false);
}
public static void clear(Context context) {
context.getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
}
private static void save(Context context, String key, int value) {
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
.edit()
.putInt(key, value)
.commit())
{
throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
}
}
private static void save(Context context, String key, byte[] value) {
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
.edit()
.putString(key, Base64.encodeBytes(value))
.commit())
{
throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
}
}
private static void save(Context context, String key, boolean value) {
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
.edit()
.putBoolean(key, value)
.commit())
{
throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
}
}
private static byte[] retrieve(Context context, String key) throws IOException {
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
String encodedValue = settings.getString(key, "");
if (TextUtils.isEmpty(encodedValue)) return null;
else return Base64.decode(encodedValue);
}
private static int retrieve(Context context, String key, int defaultValue) throws IOException {
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
return settings.getInt(key, defaultValue);
}
private static byte[] generateEncryptionSecret() {
try {
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128);
SecretKey key = generator.generateKey();
return key.getEncoded();
} catch (NoSuchAlgorithmException ex) {
Log.w("keyutil", ex);
return null;
}
}
private static byte[] generateMacSecret() {
try {
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
return generator.generateKey().getEncoded();
} catch (NoSuchAlgorithmException e) {
Log.w("keyutil", e);
return null;
}
}
private static byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
return salt;
}
private static int generateIterationCount(String passphrase, byte[] salt) {
int TARGET_ITERATION_TIME = 50; //ms
int MINIMUM_ITERATION_COUNT = 100; //default for low-end devices
int BENCHMARK_ITERATION_COUNT = 10000; //baseline starting iteration count
try {
PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, BENCHMARK_ITERATION_COUNT);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
long startTime = System.currentTimeMillis();
skf.generateSecret(keyspec);
long finishTime = System.currentTimeMillis();
int scaledIterationTarget = (int) (((double)BENCHMARK_ITERATION_COUNT / (double)(finishTime - startTime)) * TARGET_ITERATION_TIME);
if (scaledIterationTarget < MINIMUM_ITERATION_COUNT) return MINIMUM_ITERATION_COUNT;
else return scaledIterationTarget;
} catch (NoSuchAlgorithmException e) {
Log.w("MasterSecretUtil", e);
return MINIMUM_ITERATION_COUNT;
} catch (InvalidKeySpecException e) {
Log.w("MasterSecretUtil", e);
return MINIMUM_ITERATION_COUNT;
}
}
private static SecretKey getKeyFromPassphrase(String passphrase, byte[] salt, int iterations)
throws GeneralSecurityException
{
PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, iterations);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
return skf.generateSecret(keyspec);
}
private static Cipher getCipherFromPassphrase(String passphrase, byte[] salt, int iterations, int opMode)
throws GeneralSecurityException
{
SecretKey key = getKeyFromPassphrase(passphrase, salt, iterations);
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
cipher.init(opMode, key, new PBEParameterSpec(salt, iterations));
return cipher;
}
private static byte[] encryptWithPassphrase(byte[] encryptionSalt, int iterations, byte[] data, String passphrase)
throws GeneralSecurityException
{
Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, iterations, Cipher.ENCRYPT_MODE);
return cipher.doFinal(data);
}
private static byte[] decryptWithPassphrase(byte[] encryptionSalt, int iterations, byte[] data, String passphrase)
throws GeneralSecurityException, IOException
{
Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, iterations, Cipher.DECRYPT_MODE);
return cipher.doFinal(data);
}
private static Mac getMacForPassphrase(String passphrase, byte[] salt, int iterations)
throws GeneralSecurityException
{
SecretKey key = getKeyFromPassphrase(passphrase, salt, iterations);
byte[] pbkdf2 = key.getEncoded();
SecretKeySpec hmacKey = new SecretKeySpec(pbkdf2, "HmacSHA1");
Mac hmac = Mac.getInstance("HmacSHA1");
hmac.init(hmacKey);
return hmac;
}
private static byte[] verifyMac(byte[] macSalt, int iterations, byte[] encryptedAndMacdData, String passphrase) throws InvalidPassphraseException, GeneralSecurityException, IOException {
Mac hmac = getMacForPassphrase(passphrase, macSalt, iterations);
byte[] encryptedData = new byte[encryptedAndMacdData.length - hmac.getMacLength()];
System.arraycopy(encryptedAndMacdData, 0, encryptedData, 0, encryptedData.length);
byte[] givenMac = new byte[hmac.getMacLength()];
System.arraycopy(encryptedAndMacdData, encryptedAndMacdData.length-hmac.getMacLength(), givenMac, 0, givenMac.length);
byte[] localMac = hmac.doFinal(encryptedData);
if (Arrays.equals(givenMac, localMac)) return encryptedData;
else throw new InvalidPassphraseException("MAC Error");
}
private static byte[] macWithPassphrase(byte[] macSalt, int iterations, byte[] data, String passphrase) throws GeneralSecurityException {
Mac hmac = getMacForPassphrase(passphrase, macSalt, iterations);
byte[] mac = hmac.doFinal(data);
byte[] result = new byte[data.length + mac.length];
System.arraycopy(data, 0, result, 0, data.length);
System.arraycopy(mac, 0, result, data.length, mac.length);
return result;
}
}

View File

@ -14,70 +14,79 @@ import org.session.libsignal.libsignal.state.SessionStore;
import org.session.libsignal.libsignal.state.SignalProtocolStore;
import org.session.libsignal.libsignal.state.SignedPreKeyRecord;
import org.session.libsignal.libsignal.state.SignedPreKeyStore;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import java.util.List;
public class SignalProtocolStoreImpl implements SignalProtocolStore {
// private final PreKeyStore preKeyStore;
// private final SignedPreKeyStore signedPreKeyStore;
private final IdentityKeyStore identityKeyStore;
private final Context context;
// private final PreKeyStore preKeyStore;
// private final SignedPreKeyStore signedPreKeyStore;
// private final IdentityKeyStore identityKeyStore;
private final SessionStore sessionStore;
public SignalProtocolStoreImpl(Context context) {
// this.preKeyStore = new TextSecurePreKeyStore(context);
// this.signedPreKeyStore = new TextSecurePreKeyStore(context);
this.identityKeyStore = new TextSecureIdentityKeyStore(context);
// this.identityKeyStore = new TextSecureIdentityKeyStore(context);
this.sessionStore = new TextSecureSessionStore(context);
this.context = context;
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
return identityKeyStore.getIdentityKeyPair();
return IdentityKeyUtil.getIdentityKeyPair(context);
// return identityKeyStore.getIdentityKeyPair();
// throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public int getLocalRegistrationId() {
return identityKeyStore.getLocalRegistrationId();
// return identityKeyStore.getLocalRegistrationId();
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
return identityKeyStore.saveIdentity(address, identityKey);
// return identityKeyStore.saveIdentity(address, identityKey);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
// return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
return identityKeyStore.getIdentity(address);
// return identityKeyStore.getIdentity(address);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
throw new UnsupportedOperationException("This method will be removed with refactor.");
// return preKeyStore.loadPreKey(preKeyId);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public void storePreKey(int preKeyId, PreKeyRecord record) {
throw new UnsupportedOperationException("This method will be removed with refactor.");
// preKeyStore.storePreKey(preKeyId, record);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public boolean containsPreKey(int preKeyId) {
throw new UnsupportedOperationException("This method will be removed with refactor.");
// return preKeyStore.containsPreKey(preKeyId);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public void removePreKey(int preKeyId) {
throw new UnsupportedOperationException("This method will be removed with refactor.");
// preKeyStore.removePreKey(preKeyId);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override

View File

@ -1,151 +0,0 @@
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.IdentityKeyPair;
import org.session.libsignal.libsignal.SignalProtocolAddress;
import org.session.libsignal.libsignal.state.IdentityKeyStore;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.util.concurrent.TimeUnit;
public class TextSecureIdentityKeyStore implements IdentityKeyStore {
private static final int TIMESTAMP_THRESHOLD_SECONDS = 5;
private static final String TAG = TextSecureIdentityKeyStore.class.getSimpleName();
private static final Object LOCK = new Object();
private final Context context;
public TextSecureIdentityKeyStore(Context context) {
this.context = context;
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
return IdentityKeyUtil.getIdentityKeyPair(context);
}
@Override
public int getLocalRegistrationId() {
return TextSecurePreferences.getLocalRegistrationId(context);
}
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) {
synchronized (LOCK) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
Address signalAddress = Address.fromSerialized(address.getName());
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(signalAddress);
if (!identityRecord.isPresent()) {
Log.i(TAG, "Saving new identity...");
identityDatabase.saveIdentity(signalAddress, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval);
return false;
}
if (!identityRecord.get().getIdentityKey().equals(identityKey)) {
Log.i(TAG, "Replacing existing identity...");
VerifiedStatus verifiedStatus;
if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED ||
identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED)
{
verifiedStatus = VerifiedStatus.UNVERIFIED;
} else {
verifiedStatus = VerifiedStatus.DEFAULT;
}
identityDatabase.saveIdentity(signalAddress, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval);
IdentityUtil.markIdentityUpdate(context, Recipient.from(context, signalAddress, true));
SessionUtil.archiveSiblingSessions(context, address);
return true;
}
if (isNonBlockingApprovalRequired(identityRecord.get())) {
Log.i(TAG, "Setting approval status...");
identityDatabase.setApproval(signalAddress, nonBlockingApproval);
return false;
}
return false;
}
}
@Override
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
return saveIdentity(address, identityKey, false);
}
@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
synchronized (LOCK) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
String ourNumber = TextSecurePreferences.getLocalNumber(context);
Address theirAddress = Address.fromSerialized(address.getName());
if (ourNumber.equals(address.getName()) || Address.fromSerialized(ourNumber).equals(theirAddress)) {
return identityKey.equals(IdentityKeyUtil.getIdentityKey(context));
}
switch (direction) {
case SENDING: return isTrustedForSending(identityKey, identityDatabase.getIdentity(theirAddress));
case RECEIVING: return true;
default: throw new AssertionError("Unknown direction: " + direction);
}
}
}
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
Optional<IdentityRecord> record = DatabaseFactory.getIdentityDatabase(context).getIdentity(Address.fromSerialized(address.getName()));
if (record.isPresent()) {
return record.get().getIdentityKey();
} else {
return null;
}
}
private boolean isTrustedForSending(IdentityKey identityKey, Optional<IdentityRecord> identityRecord) {
if (!identityRecord.isPresent()) {
Log.w(TAG, "Nothing here, returning true...");
return true;
}
if (!identityKey.equals(identityRecord.get().getIdentityKey())) {
Log.w(TAG, "Identity keys don't match...");
return false;
}
if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
Log.w(TAG, "Needs unverified approval!");
return false;
}
if (isNonBlockingApprovalRequired(identityRecord.get())) {
Log.w(TAG, "Needs non-blocking approval!");
return false;
}
return true;
}
private boolean isNonBlockingApprovalRequired(IdentityRecord identityRecord) {
return !identityRecord.isFirstUse() &&
System.currentTimeMillis() - identityRecord.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS) &&
!identityRecord.isApprovedNonBlocking();
}
}

View File

@ -17,18 +17,15 @@
package org.thoughtcrime.securesms.database;
import android.content.Context;
import androidx.annotation.NonNull;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.helpers.ClassicOpenHelper;
import org.thoughtcrime.securesms.database.helpers.SQLCipherMigrationHelper;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
@ -36,7 +33,6 @@ import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class DatabaseFactory {
@ -51,7 +47,6 @@ public class DatabaseFactory {
private final MediaDatabase media;
private final ThreadDatabase thread;
private final MmsSmsDatabase mmsSmsDatabase;
private final IdentityDatabase identityDatabase;
private final DraftDatabase draftDatabase;
private final PushDatabase pushDatabase;
private final GroupDatabase groupDatabase;
@ -107,10 +102,6 @@ public class DatabaseFactory {
return getInstance(context).media;
}
public static IdentityDatabase getIdentityDatabase(Context context) {
return getInstance(context).identityDatabase;
}
public static DraftDatabase getDraftDatabase(Context context) {
return getInstance(context).draftDatabase;
}
@ -211,7 +202,6 @@ public class DatabaseFactory {
this.media = new MediaDatabase(context, databaseHelper);
this.thread = new ThreadDatabase(context, databaseHelper);
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
this.draftDatabase = new DraftDatabase(context, databaseHelper);
this.pushDatabase = new PushDatabase(context, databaseHelper);
this.groupDatabase = new GroupDatabase(context, databaseHelper);
@ -233,28 +223,4 @@ public class DatabaseFactory {
this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper);
}
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,
int fromVersion, DatabaseUpgradeActivity.DatabaseUpgradeListener listener)
{
databaseHelper.getWritableDatabase();
ClassicOpenHelper legacyOpenHelper = null;
if (fromVersion < DatabaseUpgradeActivity.ASYMMETRIC_MASTER_SECRET_FIX_VERSION) {
legacyOpenHelper = new ClassicOpenHelper(context);
legacyOpenHelper.onApplicationLevelUpgrade(context, masterSecret, fromVersion, listener);
}
if (fromVersion < DatabaseUpgradeActivity.SQLCIPHER && TextSecurePreferences.getNeedsSqlCipherMigration(context)) {
if (legacyOpenHelper == null) {
legacyOpenHelper = new ClassicOpenHelper(context);
}
SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret,
legacyOpenHelper.getWritableDatabase(),
databaseHelper.getWritableDatabase(),
listener);
}
}
}

View File

@ -1,243 +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.ContentValues;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.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.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.io.IOException;
public class IdentityDatabase extends Database {
@SuppressWarnings("unused")
private static final String TAG = IdentityDatabase.class.getSimpleName();
private static final String TABLE_NAME = "identities";
private static final String ID = "_id";
private static final String ADDRESS = "address";
private static final String IDENTITY_KEY = "key";
private static final String TIMESTAMP = "timestamp";
private static final String FIRST_USE = "first_use";
private static final String NONBLOCKING_APPROVAL = "nonblocking_approval";
private static final String VERIFIED = "verified";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
" (" + ID + " INTEGER PRIMARY KEY, " +
ADDRESS + " TEXT UNIQUE, " +
IDENTITY_KEY + " TEXT, " +
FIRST_USE + " INTEGER DEFAULT 0, " +
TIMESTAMP + " INTEGER DEFAULT 0, " +
VERIFIED + " INTEGER DEFAULT 0, " +
NONBLOCKING_APPROVAL + " INTEGER DEFAULT 0);";
public enum VerifiedStatus {
DEFAULT, VERIFIED, UNVERIFIED;
public int toInt() {
if (this == DEFAULT) return 0;
else if (this == VERIFIED) return 1;
else if (this == UNVERIFIED) return 2;
else throw new AssertionError();
}
public static VerifiedStatus forState(int state) {
if (state == 0) return DEFAULT;
else if (state == 1) return VERIFIED;
else if (state == 2) return UNVERIFIED;
else throw new AssertionError("No such state: " + state);
}
}
IdentityDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public Cursor getIdentities() {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
return database.query(TABLE_NAME, null, null, null, null, null, null);
}
public @Nullable IdentityReader readerFor(@Nullable Cursor cursor) {
if (cursor == null) return null;
return new IdentityReader(cursor);
}
public Optional<IdentityRecord> getIdentity(Address address) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, ADDRESS + " = ?",
new String[] {address.serialize()}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return Optional.of(getIdentityRecord(cursor));
}
} catch (InvalidKeyException | IOException e) {
throw new AssertionError(e);
} finally {
if (cursor != null) cursor.close();
}
return Optional.absent();
}
public void saveIdentity(Address address, IdentityKey identityKey, VerifiedStatus verifiedStatus,
boolean firstUse, long timestamp, boolean nonBlockingApproval)
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
ContentValues contentValues = new ContentValues();
contentValues.put(ADDRESS, address.serialize());
contentValues.put(IDENTITY_KEY, identityKeyString);
contentValues.put(TIMESTAMP, timestamp);
contentValues.put(VERIFIED, verifiedStatus.toInt());
contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval ? 1 : 0);
contentValues.put(FIRST_USE, firstUse ? 1 : 0);
database.replace(TABLE_NAME, null, contentValues);
EventBus.getDefault().post(new IdentityRecord(address, identityKey, verifiedStatus,
firstUse, timestamp, nonBlockingApproval));
}
public void setApproval(Address address, boolean nonBlockingApproval) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(2);
contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval);
database.update(TABLE_NAME, contentValues, ADDRESS + " = ?", new String[] {address.serialize()});
}
public void setVerified(Address address, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1);
contentValues.put(VERIFIED, verifiedStatus.toInt());
int updated = database.update(TABLE_NAME, contentValues, ADDRESS + " = ? AND " + IDENTITY_KEY + " = ?",
new String[] {address.serialize(), Base64.encodeBytes(identityKey.serialize())});
if (updated > 0) {
Optional<IdentityRecord> record = getIdentity(address);
if (record.isPresent()) EventBus.getDefault().post(record.get());
}
}
private IdentityRecord getIdentityRecord(@NonNull Cursor cursor) throws IOException, InvalidKeyException {
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
int verifiedStatus = cursor.getInt(cursor.getColumnIndexOrThrow(VERIFIED));
boolean nonblockingApproval = cursor.getInt(cursor.getColumnIndexOrThrow(NONBLOCKING_APPROVAL)) == 1;
boolean firstUse = cursor.getInt(cursor.getColumnIndexOrThrow(FIRST_USE)) == 1;
IdentityKey identity = new IdentityKey(Base64.decode(serializedIdentity), 0);
return new IdentityRecord(Address.fromSerialized(address), identity, VerifiedStatus.forState(verifiedStatus), firstUse, timestamp, nonblockingApproval);
}
public static class IdentityRecord {
private final Address address;
private final IdentityKey identitykey;
private final VerifiedStatus verifiedStatus;
private final boolean firstUse;
private final long timestamp;
private final boolean nonblockingApproval;
private IdentityRecord(Address address,
IdentityKey identitykey, VerifiedStatus verifiedStatus,
boolean firstUse, long timestamp, boolean nonblockingApproval)
{
this.address = address;
this.identitykey = identitykey;
this.verifiedStatus = verifiedStatus;
this.firstUse = firstUse;
this.timestamp = timestamp;
this.nonblockingApproval = nonblockingApproval;
}
public Address getAddress() {
return address;
}
public IdentityKey getIdentityKey() {
return identitykey;
}
public long getTimestamp() {
return timestamp;
}
public VerifiedStatus getVerifiedStatus() {
return verifiedStatus;
}
public boolean isApprovedNonBlocking() {
return nonblockingApproval;
}
public boolean isFirstUse() {
return firstUse;
}
@Override
public @NonNull String toString() {
return "{address: " + address + ", identityKey: " + identitykey + ", verifiedStatus: " + verifiedStatus + ", firstUse: " + firstUse + "}";
}
}
public class IdentityReader {
private final Cursor cursor;
IdentityReader(@NonNull Cursor cursor) {
this.cursor = cursor;
}
public @Nullable IdentityRecord getNext() {
if (cursor.moveToNext()) {
try {
return getIdentityRecord(cursor);
} catch (IOException | InvalidKeyException e) {
throw new AssertionError(e);
}
}
return null;
}
public void close() {
cursor.close();
}
}
}

View File

@ -1,254 +0,0 @@
package org.thoughtcrime.securesms.database.helpers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import org.thoughtcrime.securesms.logging.Log;
import android.util.Pair;
import com.annimon.stream.function.BiFunction;
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
import network.loki.messenger.R;
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.service.GenericForegroundService;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.libsignal.InvalidMessageException;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
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 Context context,
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
{
modernDb.beginTransaction();
try {
GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database));
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();
GenericForegroundService.stopForegroundTask(context);
}
}
public static void migrateCiphertext(@NonNull Context context,
@NonNull MasterSecret masterSecret,
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb,
@Nullable DatabaseUpgradeActivity.DatabaseUpgradeListener listener)
{
MasterCipher legacyCipher = new MasterCipher(masterSecret);
AsymmetricMasterCipher legacyAsymmetricCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
modernDb.beginTransaction();
try {
GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database));
int total = 5000;
copyTable("sms", legacyDb, modernDb, (row, progress) -> {
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
row.getAsLong("type"),
row.getAsString("body"));
row.put("body", plaintext.second);
row.put("type", plaintext.first);
if (listener != null && (progress.first % 1000 == 0)) {
listener.setProgress(getTotalProgress(0, progress.first, progress.second), total);
}
return row;
});
copyTable("mms", legacyDb, modernDb, (row, progress) -> {
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);
if (listener != null && (progress.first % 1000 == 0)) {
listener.setProgress(getTotalProgress(1000, progress.first, progress.second), total);
}
return row;
});
copyTable("part", legacyDb, modernDb, (row, progress) -> {
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);
}
if (listener != null && (progress.first % 1000 == 0)) {
listener.setProgress(getTotalProgress(2000, progress.first, progress.second), total);
}
return row;
});
copyTable("thread", legacyDb, modernDb, (row, progress) -> {
Long snippetType = row.getAsLong("snippet_type");
if (snippetType == null) snippetType = 0L;
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
snippetType, row.getAsString("snippet"));
row.put("snippet", plaintext.second);
row.put("snippet_type", plaintext.first);
if (listener != null && (progress.first % 1000 == 0)) {
listener.setProgress(getTotalProgress(3000, progress.first, progress.second), total);
}
return row;
});
copyTable("drafts", legacyDb, modernDb, (row, progress) -> {
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);
}
if (listener != null && (progress.first % 1000 == 0)) {
listener.setProgress(getTotalProgress(4000, progress.first, progress.second), total);
}
return row;
});
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
modernDb.setTransactionSuccessful();
} finally {
modernDb.endTransaction();
GenericForegroundService.stopForegroundTask(context);
}
}
private static void copyTable(@NonNull String tableName,
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb,
@Nullable BiFunction<ContentValues, Pair<Integer, Integer>, ContentValues> transformer)
{
Set<String> destinationColumns = getTableColumns(tableName, modernDb);
try (Cursor cursor = legacyDb.query(tableName, null, null, null, null, null, null)) {
int count = (cursor != null) ? cursor.getCount() : 0;
int progress = 1;
while (cursor != null && cursor.moveToNext()) {
ContentValues row = new ContentValues();
for (int i=0;i<cursor.getColumnCount();i++) {
String columnName = cursor.getColumnName(i);
if (destinationColumns.contains(columnName)) {
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, new Pair<>(progress++, count));
}
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);
}
private static Set<String> getTableColumns(String tableName, net.sqlcipher.database.SQLiteDatabase database) {
Set<String> results = new HashSet<>();
try (Cursor cursor = database.rawQuery("PRAGMA table_info(" + tableName + ")", null)) {
while (cursor != null && cursor.moveToNext()) {
results.add(cursor.getString(1));
}
}
return results;
}
private static int getTotalProgress(int sectionOffset, int sectionProgress, int sectionTotal) {
double percentOfSectionComplete = ((double)sectionProgress) / ((double)sectionTotal);
return sectionOffset + (int)(((double)1000) * percentOfSectionComplete);
}
}

View File

@ -1,12 +1,8 @@
package org.thoughtcrime.securesms.database.helpers;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.SystemClock;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@ -14,15 +10,11 @@ import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteDatabaseHook;
import net.sqlcipher.database.SQLiteOpenHelper;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.Address;
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.JobDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
@ -41,44 +33,14 @@ import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.File;
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
@SuppressWarnings("unused")
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
private static final int RECIPIENT_CALL_RINGTONE_VERSION = 2;
private static final int MIGRATE_PREKEYS_VERSION = 3;
private static final int MIGRATE_SESSIONS_VERSION = 4;
private static final int NO_MORE_IMAGE_THUMBNAILS_VERSION = 5;
private static final int ATTACHMENT_DIMENSIONS = 6;
private static final int QUOTED_REPLIES = 7;
private static final int SHARED_CONTACTS = 8;
private static final int FULL_TEXT_SEARCH = 9;
private static final int BAD_IMPORT_CLEANUP = 10;
private static final int QUOTE_MISSING = 11;
private static final int NOTIFICATION_CHANNELS = 12;
private static final int SECRET_SENDER = 13;
private static final int ATTACHMENT_CAPTIONS = 14;
private static final int ATTACHMENT_CAPTIONS_FIX = 15;
private static final int PREVIEWS = 16;
private static final int CONVERSATION_SEARCH = 17;
private static final int SELF_ATTACHMENT_CLEANUP = 18;
private static final int RECIPIENT_FORCE_SMS_SELECTION = 19;
private static final int JOBMANAGER_STRIKES_BACK = 20;
private static final int STICKERS = 21;
private static final int lokiV1 = 22;
private static final int lokiV2 = 23;
private static final int lokiV3 = 24;
private static final int lokiV4 = 25;
private static final int lokiV5 = 26;
private static final int lokiV6 = 27;
// First public release (1.0.0) DB version was 27.
// So we have to keep the migrations onwards.
private static final int lokiV7 = 28;
private static final int lokiV8 = 29;
private static final int lokiV9 = 30;
@ -91,9 +53,13 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV16 = 37;
private static final int lokiV17 = 38;
private static final int lokiV18_CLEAR_BG_POLL_JOBS = 39;
private static final int lokiV19_OLD_CODE_CLEANUP = 40; //TODO Change back to 40 when the refactoring is over.
//TODO Merge all "refactor" migrations to one before pushing to the main repo.
private static final int lokiV19_REFACTOR0 = 40;
private static final int lokiV19_REFACTOR1 = 41;
private static final int lokiV19_REFACTOR2 = 42;
private static final int DATABASE_VERSION = lokiV19_OLD_CODE_CLEANUP; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV19_REFACTOR1;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@ -124,7 +90,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
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);
@ -176,20 +141,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
executeStatements(db, GroupDatabase.CREATE_INDEXS);
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
executeStatements(db, StickerDatabase.CREATE_INDEXES);
if (context.getDatabasePath(ClassicOpenHelper.NAME).exists()) {
ClassicOpenHelper legacyHelper = new ClassicOpenHelper(context);
android.database.sqlite.SQLiteDatabase legacyDb = legacyHelper.getWritableDatabase();
SQLCipherMigrationHelper.migratePlaintext(context, legacyDb, db);
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db, null);
else TextSecurePreferences.setNeedsSqlCipherMigration(context, true);
SessionStoreMigrationHelper.migrateSessions(context, db);
}
}
@Override
@ -209,379 +160,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
try {
if (oldVersion < RECIPIENT_CALL_RINGTONE_VERSION) {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_ringtone TEXT DEFAULT NULL");
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_vibrate INTEGER DEFAULT " + RecipientDatabase.VibrateState.DEFAULT.getId());
}
if (oldVersion < MIGRATE_PREKEYS_VERSION) {
db.execSQL("CREATE TABLE signed_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL, signature TEXT NOT NULL, timestamp INTEGER DEFAULT 0)");
db.execSQL("CREATE TABLE one_time_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL)");
}
if (oldVersion < MIGRATE_SESSIONS_VERSION) {
db.execSQL("CREATE TABLE sessions (_id INTEGER PRIMARY KEY, address TEXT NOT NULL, device INTEGER NOT NULL, record BLOB NOT NULL, UNIQUE(address, device) ON CONFLICT REPLACE)");
SessionStoreMigrationHelper.migrateSessions(context, db);
}
if (oldVersion < NO_MORE_IMAGE_THUMBNAILS_VERSION) {
ContentValues update = new ContentValues();
update.put("thumbnail", (String)null);
update.put("aspect_ratio", (String)null);
update.put("thumbnail_random", (String)null);
try (Cursor cursor = db.query("part", new String[] {"_id", "ct", "thumbnail"}, "thumbnail IS NOT NULL", null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
String contentType = cursor.getString(cursor.getColumnIndexOrThrow("ct"));
if (contentType != null && !contentType.startsWith("video")) {
String thumbnailPath = cursor.getString(cursor.getColumnIndexOrThrow("thumbnail"));
File thumbnailFile = new File(thumbnailPath);
thumbnailFile.delete();
db.update("part", update, "_id = ?", new String[] {String.valueOf(id)});
}
}
}
}
if (oldVersion < ATTACHMENT_DIMENSIONS) {
db.execSQL("ALTER TABLE part ADD COLUMN width INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE part ADD COLUMN height INTEGER DEFAULT 0");
}
if (oldVersion < QUOTED_REPLIES) {
db.execSQL("ALTER TABLE mms ADD COLUMN quote_id INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE mms ADD COLUMN quote_author TEXT");
db.execSQL("ALTER TABLE mms ADD COLUMN quote_body TEXT");
db.execSQL("ALTER TABLE mms ADD COLUMN quote_attachment INTEGER DEFAULT -1");
db.execSQL("ALTER TABLE part ADD COLUMN quote INTEGER DEFAULT 0");
}
if (oldVersion < SHARED_CONTACTS) {
db.execSQL("ALTER TABLE mms ADD COLUMN shared_contacts TEXT");
}
if (oldVersion < FULL_TEXT_SEARCH) {
db.execSQL("CREATE VIRTUAL TABLE sms_fts USING fts5(body, content=sms, content_rowid=_id)");
db.execSQL("CREATE TRIGGER sms_ai AFTER INSERT ON sms BEGIN\n" +
" INSERT INTO sms_fts(rowid, body) VALUES (new._id, new.body);\n" +
"END;");
db.execSQL("CREATE TRIGGER sms_ad AFTER DELETE ON sms BEGIN\n" +
" INSERT INTO sms_fts(sms_fts, rowid, body) VALUES('delete', old._id, old.body);\n" +
"END;\n");
db.execSQL("CREATE TRIGGER sms_au AFTER UPDATE ON sms BEGIN\n" +
" INSERT INTO sms_fts(sms_fts, rowid, body) VALUES('delete', old._id, old.body);\n" +
" INSERT INTO sms_fts(rowid, body) VALUES(new._id, new.body);\n" +
"END;");
db.execSQL("CREATE VIRTUAL TABLE mms_fts USING fts5(body, content=mms, content_rowid=_id)");
db.execSQL("CREATE TRIGGER mms_ai AFTER INSERT ON mms BEGIN\n" +
" INSERT INTO mms_fts(rowid, body) VALUES (new._id, new.body);\n" +
"END;");
db.execSQL("CREATE TRIGGER mms_ad AFTER DELETE ON mms BEGIN\n" +
" INSERT INTO mms_fts(mms_fts, rowid, body) VALUES('delete', old._id, old.body);\n" +
"END;\n");
db.execSQL("CREATE TRIGGER mms_au AFTER UPDATE ON mms BEGIN\n" +
" INSERT INTO mms_fts(mms_fts, rowid, body) VALUES('delete', old._id, old.body);\n" +
" INSERT INTO mms_fts(rowid, body) VALUES(new._id, new.body);\n" +
"END;");
Log.i(TAG, "Beginning to build search index.");
long start = SystemClock.elapsedRealtime();
db.execSQL("INSERT INTO sms_fts (rowid, body) SELECT _id, body FROM sms");
long smsFinished = SystemClock.elapsedRealtime();
Log.i(TAG, "Indexing SMS completed in " + (smsFinished - start) + " ms");
db.execSQL("INSERT INTO mms_fts (rowid, body) SELECT _id, body FROM mms");
long mmsFinished = SystemClock.elapsedRealtime();
Log.i(TAG, "Indexing MMS completed in " + (mmsFinished - smsFinished) + " ms");
Log.i(TAG, "Indexing finished. Total time: " + (mmsFinished - start) + " ms");
}
if (oldVersion < BAD_IMPORT_CLEANUP) {
String trimmedCondition = " NOT IN (SELECT _id FROM mms)";
db.delete("group_receipts", "mms_id" + trimmedCondition, null);
String[] columns = new String[] { "_id", "unique_id", "_data", "thumbnail"};
try (Cursor cursor = db.query("part", columns, "mid" + trimmedCondition, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
db.delete("part", "_id = ? AND unique_id = ?", new String[] { String.valueOf(cursor.getLong(0)), String.valueOf(cursor.getLong(1)) });
String data = cursor.getString(2);
String thumbnail = cursor.getString(3);
if (!TextUtils.isEmpty(data)) {
new File(data).delete();
}
if (!TextUtils.isEmpty(thumbnail)) {
new File(thumbnail).delete();
}
}
}
}
// Note: This column only being checked due to upgrade issues as described in #8184
if (oldVersion < QUOTE_MISSING && !columnExists(db, "mms", "quote_missing")) {
db.execSQL("ALTER TABLE mms ADD COLUMN quote_missing INTEGER DEFAULT 0");
}
// Note: The column only being checked due to upgrade issues as described in #8184
if (oldVersion < NOTIFICATION_CHANNELS && !columnExists(db, "recipient_preferences", "notification_channel")) {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN notification_channel TEXT DEFAULT NULL");
NotificationChannels.create(context);
try (Cursor cursor = db.rawQuery("SELECT recipient_ids, system_display_name, signal_profile_name, notification, vibrate FROM recipient_preferences WHERE notification NOT NULL OR vibrate != 0", null)) {
while (cursor != null && cursor.moveToNext()) {
String addressString = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
Address address = Address.fromExternal(context, addressString);
String systemName = cursor.getString(cursor.getColumnIndexOrThrow("system_display_name"));
String profileName = cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_name"));
String messageSound = cursor.getString(cursor.getColumnIndexOrThrow("notification"));
Uri messageSoundUri = messageSound != null ? Uri.parse(messageSound) : null;
int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow("vibrate"));
String displayName = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, address);
boolean vibrateEnabled = vibrateState == 0 ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == 1;
if (address.isGroup()) {
try(Cursor groupCursor = db.rawQuery("SELECT title FROM groups WHERE group_id = ?", new String[] { address.toGroupString() })) {
if (groupCursor != null && groupCursor.moveToFirst()) {
String title = groupCursor.getString(groupCursor.getColumnIndexOrThrow("title"));
if (!TextUtils.isEmpty(title)) {
displayName = title;
}
}
}
}
String channelId = NotificationChannels.createChannelFor(context, address, displayName, messageSoundUri, vibrateEnabled);
ContentValues values = new ContentValues(1);
values.put("notification_channel", channelId);
db.update("recipient_preferences", values, "recipient_ids = ?", new String[] { addressString });
}
}
}
if (oldVersion < SECRET_SENDER) {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN unidentified_access_mode INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE push ADD COLUMN server_timestamp INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE push ADD COLUMN server_guid TEXT DEFAULT NULL");
db.execSQL("ALTER TABLE group_receipts ADD COLUMN unidentified INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE mms ADD COLUMN unidentified INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE sms ADD COLUMN unidentified INTEGER DEFAULT 0");
}
if (oldVersion < ATTACHMENT_CAPTIONS) {
db.execSQL("ALTER TABLE part ADD COLUMN caption TEXT DEFAULT NULL");
}
// 4.30.8 included a migration, but not a correct CREATE_TABLE statement, so we need to add
// this column if it isn't present.
if (oldVersion < ATTACHMENT_CAPTIONS_FIX) {
if (!columnExists(db, "part", "caption")) {
db.execSQL("ALTER TABLE part ADD COLUMN caption TEXT DEFAULT NULL");
}
}
if (oldVersion < PREVIEWS) {
db.execSQL("ALTER TABLE mms ADD COLUMN previews TEXT");
}
if (oldVersion < CONVERSATION_SEARCH) {
db.execSQL("DROP TABLE sms_fts");
db.execSQL("DROP TABLE mms_fts");
db.execSQL("DROP TRIGGER sms_ai");
db.execSQL("DROP TRIGGER sms_au");
db.execSQL("DROP TRIGGER sms_ad");
db.execSQL("DROP TRIGGER mms_ai");
db.execSQL("DROP TRIGGER mms_au");
db.execSQL("DROP TRIGGER mms_ad");
db.execSQL("CREATE VIRTUAL TABLE sms_fts USING fts5(body, thread_id UNINDEXED, content=sms, content_rowid=_id)");
db.execSQL("CREATE TRIGGER sms_ai AFTER INSERT ON sms BEGIN\n" +
" INSERT INTO sms_fts(rowid, body, thread_id) VALUES (new._id, new.body, new.thread_id);\n" +
"END;");
db.execSQL("CREATE TRIGGER sms_ad AFTER DELETE ON sms BEGIN\n" +
" INSERT INTO sms_fts(sms_fts, rowid, body, thread_id) VALUES('delete', old._id, old.body, old.thread_id);\n" +
"END;\n");
db.execSQL("CREATE TRIGGER sms_au AFTER UPDATE ON sms BEGIN\n" +
" INSERT INTO sms_fts(sms_fts, rowid, body, thread_id) VALUES('delete', old._id, old.body, old.thread_id);\n" +
" INSERT INTO sms_fts(rowid, body, thread_id) VALUES(new._id, new.body, new.thread_id);\n" +
"END;");
db.execSQL("CREATE VIRTUAL TABLE mms_fts USING fts5(body, thread_id UNINDEXED, content=mms, content_rowid=_id)");
db.execSQL("CREATE TRIGGER mms_ai AFTER INSERT ON mms BEGIN\n" +
" INSERT INTO mms_fts(rowid, body, thread_id) VALUES (new._id, new.body, new.thread_id);\n" +
"END;");
db.execSQL("CREATE TRIGGER mms_ad AFTER DELETE ON mms BEGIN\n" +
" INSERT INTO mms_fts(mms_fts, rowid, body, thread_id) VALUES('delete', old._id, old.body, old.thread_id);\n" +
"END;\n");
db.execSQL("CREATE TRIGGER mms_au AFTER UPDATE ON mms BEGIN\n" +
" INSERT INTO mms_fts(mms_fts, rowid, body, thread_id) VALUES('delete', old._id, old.body, old.thread_id);\n" +
" INSERT INTO mms_fts(rowid, body, thread_id) VALUES(new._id, new.body, new.thread_id);\n" +
"END;");
Log.i(TAG, "Beginning to build search index.");
long start = SystemClock.elapsedRealtime();
db.execSQL("INSERT INTO sms_fts (rowid, body, thread_id) SELECT _id, body, thread_id FROM sms");
long smsFinished = SystemClock.elapsedRealtime();
Log.i(TAG, "Indexing SMS completed in " + (smsFinished - start) + " ms");
db.execSQL("INSERT INTO mms_fts (rowid, body, thread_id) SELECT _id, body, thread_id FROM mms");
long mmsFinished = SystemClock.elapsedRealtime();
Log.i(TAG, "Indexing MMS completed in " + (mmsFinished - smsFinished) + " ms");
Log.i(TAG, "Indexing finished. Total time: " + (mmsFinished - start) + " ms");
}
if (oldVersion < SELF_ATTACHMENT_CLEANUP) {
String localNumber = TextSecurePreferences.getLocalNumber(context);
if (!TextUtils.isEmpty(localNumber)) {
try (Cursor threadCursor = db.rawQuery("SELECT _id FROM thread WHERE recipient_ids = ?", new String[]{ localNumber })) {
if (threadCursor != null && threadCursor.moveToFirst()) {
long threadId = threadCursor.getLong(0);
ContentValues updateValues = new ContentValues(1);
updateValues.put("pending_push", 0);
int count = db.update("part", updateValues, "mid IN (SELECT _id FROM mms WHERE thread_id = ?)", new String[]{ String.valueOf(threadId) });
Log.i(TAG, "Updated " + count + " self-sent attachments.");
}
}
}
}
if (oldVersion < RECIPIENT_FORCE_SMS_SELECTION) {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN force_sms_selection INTEGER DEFAULT 0");
}
if (oldVersion < JOBMANAGER_STRIKES_BACK) {
db.execSQL("CREATE TABLE job_spec(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"job_spec_id TEXT UNIQUE, " +
"factory_key TEXT, " +
"queue_key TEXT, " +
"create_time INTEGER, " +
"next_run_attempt_time INTEGER, " +
"run_attempt INTEGER, " +
"max_attempts INTEGER, " +
"max_backoff INTEGER, " +
"max_instances INTEGER, " +
"lifespan INTEGER, " +
"serialized_data TEXT, " +
"is_running INTEGER)");
db.execSQL("CREATE TABLE constraint_spec(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"job_spec_id TEXT, " +
"factory_key TEXT, " +
"UNIQUE(job_spec_id, factory_key))");
db.execSQL("CREATE TABLE dependency_spec(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"job_spec_id TEXT, " +
"depends_on_job_spec_id TEXT, " +
"UNIQUE(job_spec_id, depends_on_job_spec_id))");
}
if (oldVersion < STICKERS) {
db.execSQL("CREATE TABLE sticker (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"pack_id TEXT NOT NULL, " +
"pack_key TEXT NOT NULL, " +
"pack_title TEXT NOT NULL, " +
"pack_author TEXT NOT NULL, " +
"sticker_id INTEGER, " +
"cover INTEGER, " +
"emoji TEXT NOT NULL, " +
"last_used INTEGER, " +
"installed INTEGER," +
"file_path TEXT NOT NULL, " +
"file_length INTEGER, " +
"file_random BLOB, " +
"UNIQUE(pack_id, sticker_id, cover) ON CONFLICT IGNORE)");
db.execSQL("CREATE INDEX IF NOT EXISTS sticker_pack_id_index ON sticker (pack_id);");
db.execSQL("CREATE INDEX IF NOT EXISTS sticker_sticker_id_index ON sticker (sticker_id);");
db.execSQL("ALTER TABLE part ADD COLUMN sticker_pack_id TEXT");
db.execSQL("ALTER TABLE part ADD COLUMN sticker_pack_key TEXT");
db.execSQL("ALTER TABLE part ADD COLUMN sticker_id INTEGER DEFAULT -1");
db.execSQL("CREATE INDEX IF NOT EXISTS part_sticker_pack_id_index ON part (sticker_pack_id)");
}
if (oldVersion < lokiV1) {
db.execSQL(LokiAPIDatabase.getCreateOpenGroupAuthTokenTableCommand());
db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand());
db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand());
}
if (oldVersion < lokiV2) {
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
}
if (oldVersion < lokiV3) {
db.execSQL(LokiAPIDatabase.getCreateDeviceLinkCacheCommand());
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
db.execSQL("ALTER TABLE groups ADD COLUMN avatar_url TEXT");
db.execSQL("ALTER TABLE part ADD COLUMN url TEXT");
}
if (oldVersion < lokiV4) {
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
}
if (oldVersion < lokiV5) {
db.execSQL(LokiAPIDatabase.getCreateUserCountTableCommand());
}
if (oldVersion < lokiV6) {
// Migrate public chats from __textsecure_group__ to __loki_public_chat_group__
try (Cursor lokiPublicChatCursor = db.rawQuery("SELECT public_chat FROM loki_public_chat_database", null)) {
while (lokiPublicChatCursor != null && lokiPublicChatCursor.moveToNext()) {
String chatString = lokiPublicChatCursor.getString(0);
PublicChat publicChat = PublicChat.fromJSON(chatString);
if (publicChat != null) {
byte[] groupId = publicChat.getId().getBytes();
String oldId = GroupUtil.getEncodedId(groupId, false);
String newId = GroupUtil.getEncodedOpenGroupId(groupId);
ContentValues threadUpdate = new ContentValues();
threadUpdate.put("recipient_ids", newId);
db.update("thread", threadUpdate, "recipient_ids = ?", new String[]{ oldId });
ContentValues groupUpdate = new ContentValues();
groupUpdate.put("group_id", newId);
db.update("groups", groupUpdate,"group_id = ?", new String[] { oldId });
}
}
}
// Migrate RSS feeds from __textsecure_group__ to __loki_rss_feed_group__
String[] rssFeedIds = new String[] { "loki.network.feed", "loki.network.messenger-updates.feed" };
for (String groupId : rssFeedIds) {
String oldId = GroupUtil.getEncodedId(groupId.getBytes(), false);
String newId = GroupUtil.getEncodedRSSFeedId(groupId.getBytes());
ContentValues threadUpdate = new ContentValues();
threadUpdate.put("recipient_ids", newId);
db.update("thread", threadUpdate, "recipient_ids = ?", new String[]{ oldId });
ContentValues groupUpdate = new ContentValues();
groupUpdate.put("group_id", newId);
db.update("groups", groupUpdate,"group_id = ?", new String[] { oldId });
}
// Add admin field in groups
db.execSQL("ALTER TABLE groups ADD COLUMN admins TEXT");
}
if (oldVersion < lokiV7) {
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
}
@ -637,26 +215,28 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL("DELETE FROM constraint_spec WHERE factory_key = 'BackgroundPollJob'");
}
if (oldVersion < lokiV19_OLD_CODE_CLEANUP) {
// Many classes were removed. We need to update D structure and data to match the code changes.
String[] deletedJobKeys = {
"ServiceOutageDetectionJob",
};
for (String jobKey : deletedJobKeys) {
db.execSQL("DELETE FROM job_spec WHERE factory_key = ?", new String[]{jobKey});
db.execSQL("DELETE FROM constraint_spec WHERE factory_key = ?", new String[]{jobKey});
}
// Many classes were removed. We need to update DB structure and data to match the code changes.
//TODO Merge "refactor" changes in one migration.
if (oldVersion < lokiV19_REFACTOR0) {
deleteJobRecords(db, "ServiceOutageDetectionJob");
}
if (oldVersion < lokiV19_REFACTOR1) {
db.execSQL("DROP TABLE identities");
deleteJobRecords(db, "RetrieveProfileJob");
}
if (oldVersion < lokiV19_REFACTOR2) {
deleteJobRecords(db,
"RefreshAttributesJob",
"RotateProfileKeyJob",
"RefreshUnidentifiedDeliveryAbilityJob",
"RotateCertificateJob"
);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (oldVersion < MIGRATE_PREKEYS_VERSION) {
// PreKeyMigrationHelper.cleanUpPreKeys(context);
}
}
public SQLiteDatabase getReadableDatabase() {
@ -691,4 +271,15 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
return false;
}
/**
* Cleans up all the records related to the job keys specified.
* This method should be called once the Signal job class is deleted from the project.
*/
private static void deleteJobRecords(SQLiteDatabase db, String... jobKeys) {
for (String jobKey : jobKeys) {
db.execSQL("DELETE FROM job_spec WHERE factory_key = ?", new String[]{jobKey});
db.execSQL("DELETE FROM constraint_spec WHERE factory_key = ?", new String[]{jobKey});
}
}
}

View File

@ -1,109 +0,0 @@
package org.thoughtcrime.securesms.database.helpers;
import android.content.ContentValues;
import android.content.Context;
import org.thoughtcrime.securesms.logging.Log;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.SessionDatabase;
import org.thoughtcrime.securesms.util.Conversions;
import org.session.libsignal.libsignal.state.SessionRecord;
import org.session.libsignal.libsignal.state.SessionState;
import org.session.libsignal.libsignal.state.StorageProtos.SessionStructure;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
class SessionStoreMigrationHelper {
private static final String TAG = SessionStoreMigrationHelper.class.getSimpleName();
private static final String SESSIONS_DIRECTORY_V2 = "sessions-v2";
private static final Object FILE_LOCK = new Object();
private static final int SINGLE_STATE_VERSION = 1;
private static final int ARCHIVE_STATES_VERSION = 2;
private static final int PLAINTEXT_VERSION = 3;
private static final int CURRENT_VERSION = 3;
static void migrateSessions(Context context, SQLiteDatabase database) {
File directory = new File(context.getFilesDir(), SESSIONS_DIRECTORY_V2);
if (directory.exists()) {
File[] sessionFiles = directory.listFiles();
if (sessionFiles != null) {
for (File sessionFile : sessionFiles) {
try {
String[] parts = sessionFile.getName().split("[.]");
Address address = Address.fromSerialized(parts[0]);
int deviceId;
if (parts.length > 1) deviceId = Integer.parseInt(parts[1]);
else deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
FileInputStream in = new FileInputStream(sessionFile);
int versionMarker = readInteger(in);
if (versionMarker > CURRENT_VERSION) {
throw new AssertionError("Unknown version: " + versionMarker + ", " + sessionFile.getAbsolutePath());
}
byte[] serialized = readBlob(in);
in.close();
if (versionMarker < PLAINTEXT_VERSION) {
throw new AssertionError("Not plaintext: " + versionMarker + ", " + sessionFile.getAbsolutePath());
}
SessionRecord sessionRecord;
if (versionMarker == SINGLE_STATE_VERSION) {
Log.i(TAG, "Migrating single state version: " + sessionFile.getAbsolutePath());
SessionStructure sessionStructure = SessionStructure.parseFrom(serialized);
SessionState sessionState = new SessionState(sessionStructure);
sessionRecord = new SessionRecord(sessionState);
} else if (versionMarker >= ARCHIVE_STATES_VERSION) {
Log.i(TAG, "Migrating session: " + sessionFile.getAbsolutePath());
sessionRecord = new SessionRecord(serialized);
} else {
throw new AssertionError("Unknown version: " + versionMarker + ", " + sessionFile.getAbsolutePath());
}
ContentValues contentValues = new ContentValues();
contentValues.put(SessionDatabase.ADDRESS, address.serialize());
contentValues.put(SessionDatabase.DEVICE, deviceId);
contentValues.put(SessionDatabase.RECORD, sessionRecord.serialize());
database.insert(SessionDatabase.TABLE_NAME, null, contentValues);
} catch (NumberFormatException | IOException e) {
Log.w(TAG, e);
}
}
}
}
}
private static byte[] readBlob(FileInputStream in) throws IOException {
int length = readInteger(in);
byte[] blobBytes = new byte[length];
in.read(blobBytes, 0, blobBytes.length);
return blobBytes;
}
private static int readInteger(FileInputStream in) throws IOException {
byte[] integer = new byte[4];
in.read(integer, 0, integer.length);
return Conversions.byteArrayToInt(integer);
}
}

View File

@ -1,115 +0,0 @@
package org.thoughtcrime.securesms.database.identity;
import android.content.Context;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class IdentityRecordList {
private static final String TAG = IdentityRecordList.class.getSimpleName();
private final List<IdentityRecord> identityRecords = new LinkedList<>();
public void add(Optional<IdentityRecord> identityRecord) {
if (identityRecord.isPresent()) {
identityRecords.add(identityRecord.get());
}
}
public void replaceWith(IdentityRecordList identityRecordList) {
identityRecords.clear();
identityRecords.addAll(identityRecordList.identityRecords);
}
public boolean isVerified() {
for (IdentityRecord identityRecord : identityRecords) {
if (identityRecord.getVerifiedStatus() != VerifiedStatus.VERIFIED) {
return false;
}
}
return identityRecords.size() > 0;
}
public boolean isUnverified() {
for (IdentityRecord identityRecord : identityRecords) {
if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
return true;
}
}
return false;
}
public boolean isUntrusted() {
for (IdentityRecord identityRecord : identityRecords) {
if (isUntrusted(identityRecord)) {
return true;
}
}
return false;
}
public List<IdentityRecord> getUntrustedRecords() {
List<IdentityRecord> results = new LinkedList<>();
for (IdentityRecord identityRecord : identityRecords) {
if (isUntrusted(identityRecord)) {
results.add(identityRecord);
}
}
return results;
}
public List<Recipient> getUntrustedRecipients(Context context) {
List<Recipient> untrusted = new LinkedList<>();
for (IdentityRecord identityRecord : identityRecords) {
if (isUntrusted(identityRecord)) {
untrusted.add(Recipient.from(context, identityRecord.getAddress(), false));
}
}
return untrusted;
}
public List<IdentityRecord> getUnverifiedRecords() {
List<IdentityRecord> results = new LinkedList<>();
for (IdentityRecord identityRecord : identityRecords) {
if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
results.add(identityRecord);
}
}
return results;
}
public List<Recipient> getUnverifiedRecipients(Context context) {
List<Recipient> unverified = new LinkedList<>();
for (IdentityRecord identityRecord : identityRecords) {
if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
unverified.add(Recipient.from(context, identityRecord.getAddress(), false));
}
}
return unverified;
}
private boolean isUntrusted(IdentityRecord identityRecord) {
return !identityRecord.isApprovedNonBlocking() &&
System.currentTimeMillis() - identityRecord.getTimestamp() < TimeUnit.SECONDS.toMillis(5);
}
}

View File

@ -22,13 +22,8 @@ import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.jobs.StickerDownloadJob;
@ -36,6 +31,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
@ -56,18 +52,13 @@ import network.loki.messenger.BuildConfig;
AttachmentDownloadJob.class,
IncomingMessageObserver.class,
PushNotificationReceiveJob.class,
RefreshAttributesJob.class,
RequestGroupInfoJob.class,
PushGroupUpdateJob.class,
AvatarDownloadJob.class,
RetrieveProfileJob.class,
RetrieveProfileAvatarJob.class,
SendReadReceiptJob.class,
AppProtectionPreferenceFragment.class,
RotateCertificateJob.class,
SendDeliveryReceiptJob.class,
RotateProfileKeyJob.class,
RefreshUnidentifiedDeliveryAbilityJob.class,
TypingSendJob.class,
AttachmentUploadJob.class,
PushDecryptJob.class,
@ -94,17 +85,6 @@ public class SignalCommunicationModule {
}
@Provides
synchronized SignalServiceAccountManager provideSignalAccountManager() {
if (this.accountManager == null) {
this.accountManager = new SignalServiceAccountManager(networkAccess.getConfiguration(context),
new DynamicCredentialsProvider(context),
BuildConfig.USER_AGENT);
}
return this.accountManager;
}
@Provides
public synchronized SignalServiceMessageSender provideSignalMessageSender() {
if (this.messageSender == null) {
@ -121,8 +101,8 @@ public class SignalCommunicationModule {
DatabaseFactory.getSSKDatabase(context),
DatabaseFactory.getLokiThreadDatabase(context),
DatabaseFactory.getLokiMessageDatabase(context),
// DatabaseFactory.getLokiPreKeyBundleDatabase(context),
null,
null, // DatabaseFactory.getLokiPreKeyBundleDatabase(context)
new SessionProtocolImpl(context),
new SessionResetImplementation(context),
DatabaseFactory.getLokiUserDatabase(context),
DatabaseFactory.getGroupDatabase(context),

View File

@ -21,7 +21,6 @@ import com.google.android.material.tabs.TabLayout;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
@ -42,19 +41,12 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
public static final String EXTRA_WIDTH = "extra_width";
public static final String EXTRA_HEIGHT = "extra_height";
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private GiphyGifFragment gifFragment;
private GiphyStickerFragment stickerFragment;
private boolean forMms;
private GiphyAdapter.GiphyViewHolder finishingImage;
@Override
public void onPreCreate() {
dynamicLanguage.onCreate(this);
}
@Override
public void onCreate(Bundle bundle, boolean ready) {
setContentView(R.layout.giphy_activity);

View File

@ -1,85 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migration;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
import org.thoughtcrime.securesms.jobs.LocalBackupJob;
import org.thoughtcrime.securesms.jobs.MmsDownloadJob;
import org.thoughtcrime.securesms.jobs.MmsReceiveJob;
import org.thoughtcrime.securesms.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob;
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.jobs.SmsReceiveJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.jobs.SmsSentJob;
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.jobs.UpdateApkJob;
import org.thoughtcrime.securesms.loki.api.PrepareAttachmentAudioExtrasJob;
import org.thoughtcrime.securesms.loki.api.ResetThreadSessionJob;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJob;
import org.thoughtcrime.securesms.loki.protocol.NullMessageSendJob;
import java.util.HashMap;
import java.util.Map;
//TODO AC: Looks like we don't use it anymore. Make sure it's abandoned and delete.
public class WorkManagerFactoryMappings {
private static final Map<String, String> FACTORY_MAP = new HashMap<String, String>() {{
put(AttachmentDownloadJob.class.getName(), AttachmentDownloadJob.KEY);
put(AttachmentUploadJob.class.getName(), AttachmentUploadJob.KEY);
put(AvatarDownloadJob.class.getName(), AvatarDownloadJob.KEY);
put(ClosedGroupUpdateMessageSendJob.class.getName(), ClosedGroupUpdateMessageSendJob.KEY);
put(LocalBackupJob.class.getName(), LocalBackupJob.KEY);
put(MmsDownloadJob.class.getName(), MmsDownloadJob.KEY);
put(MmsReceiveJob.class.getName(), MmsReceiveJob.KEY);
put(MmsSendJob.class.getName(), MmsSendJob.KEY);
put(NullMessageSendJob.class.getName(), NullMessageSendJob.KEY);
put(PushContentReceiveJob.class.getName(), PushContentReceiveJob.KEY);
put(PushDecryptJob.class.getName(), PushDecryptJob.KEY);
put(PushGroupSendJob.class.getName(), PushGroupSendJob.KEY);
put(PushGroupUpdateJob.class.getName(), PushGroupUpdateJob.KEY);
put(PushMediaSendJob.class.getName(), PushMediaSendJob.KEY);
put(PushNotificationReceiveJob.class.getName(), PushNotificationReceiveJob.KEY);
put(PushTextSendJob.class.getName(), PushTextSendJob.KEY);
put(RefreshAttributesJob.class.getName(), RefreshAttributesJob.KEY);
put(RefreshUnidentifiedDeliveryAbilityJob.class.getName(), RefreshUnidentifiedDeliveryAbilityJob.KEY);
put(RequestGroupInfoJob.class.getName(), RequestGroupInfoJob.KEY);
put(RetrieveProfileAvatarJob.class.getName(), RetrieveProfileAvatarJob.KEY);
put(RetrieveProfileJob.class.getName(), RetrieveProfileJob.KEY);
put(RotateCertificateJob.class.getName(), RotateCertificateJob.KEY);
put(RotateProfileKeyJob.class.getName(), RotateProfileKeyJob.KEY);
put(SendDeliveryReceiptJob.class.getName(), SendDeliveryReceiptJob.KEY);
put(SendReadReceiptJob.class.getName(), SendReadReceiptJob.KEY);
put(SmsReceiveJob.class.getName(), SmsReceiveJob.KEY);
put(SmsSendJob.class.getName(), SmsSendJob.KEY);
put(SmsSentJob.class.getName(), SmsSentJob.KEY);
put(TrimThreadJob.class.getName(), TrimThreadJob.KEY);
put(TypingSendJob.class.getName(), TypingSendJob.KEY);
put(UpdateApkJob.class.getName(), UpdateApkJob.KEY);
put(PrepareAttachmentAudioExtrasJob.class.getName(), PrepareAttachmentAudioExtrasJob.KEY);
put(ResetThreadSessionJob.class.getName(), ResetThreadSessionJob.KEY);
}};
public static @Nullable String getFactoryKey(@NonNull String workManagerClass) {
return FACTORY_MAP.get(workManagerClass);
}
}

View File

@ -6,6 +6,13 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import org.greenrobot.eventbus.EventBus;
import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import org.session.libsignal.service.loki.utilities.DownloadUtilities;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
@ -23,19 +30,12 @@ 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.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.inject.Inject;
public class AttachmentDownloadJob extends BaseJob implements InjectableType {
public static final String KEY = "AttachmentDownloadJob";
@ -48,8 +48,6 @@ public class AttachmentDownloadJob extends BaseJob implements InjectableType {
private static final String KEY_PAR_UNIQUE_ID = "part_unique_id";
private static final String KEY_MANUAL = "part_manual";
@Inject SignalServiceMessageReceiver messageReceiver;
private long messageId;
private long partRowId;
private long partUniqueId;
@ -166,7 +164,21 @@ public class AttachmentDownloadJob extends BaseJob implements InjectableType {
attachmentFile = createTempFile();
SignalServiceAttachmentPointer pointer = createAttachmentPointer(attachment);
InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress)));
// InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress)));
if (pointer.getUrl().isEmpty()) throw new InvalidMessageException("Missing attachment URL.");
DownloadUtilities.downloadFile(attachmentFile, pointer.getUrl(), MAX_ATTACHMENT_SIZE, (total, progress) -> {
EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress));
});
final InputStream stream;
// Assume we're retrieving an attachment for an open group server if the digest is not set
if (!pointer.getDigest().isPresent()) {
stream = new FileInputStream(attachmentFile);
} else {
stream = AttachmentCipherInputStream.createForAttachment(attachmentFile, pointer.getSize().or(0), pointer.getKey(), pointer.getDigest().get());
}
database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream);
} catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) {

View File

@ -44,13 +44,8 @@ public final class JobManagerFactories {
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
put(RefreshUnidentifiedDeliveryAbilityJob.KEY, new RefreshUnidentifiedDeliveryAbilityJob.Factory());
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application));
put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory(application));
put(RotateCertificateJob.KEY, new RotateCertificateJob.Factory());
put(RotateProfileKeyJob.KEY, new RotateProfileKeyJob.Factory());
put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory());
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory());
put(SmsReceiveJob.KEY, new SmsReceiveJob.Factory());

View File

@ -5,7 +5,6 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -64,6 +63,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
@ -92,7 +92,6 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.libsignal.InvalidMessageException;
@ -107,10 +106,7 @@ import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
import org.session.libsignal.service.api.messages.multidevice.ReadMessage;
import org.session.libsignal.service.api.messages.multidevice.RequestMessage;
import org.session.libsignal.service.api.messages.multidevice.SentTranscriptMessage;
import org.session.libsignal.service.api.messages.multidevice.SignalServiceSyncMessage;
import org.session.libsignal.service.api.messages.multidevice.StickerPackOperationMessage;
import org.session.libsignal.service.api.messages.multidevice.VerifiedMessage;
import org.session.libsignal.service.api.messages.shared.SharedContact;
@ -249,7 +245,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), new SessionProtocolImpl(context), sessionResetProtocol, UnidentifiedAccessUtil.getCertificateValidator());
SignalServiceContent content = cipher.decrypt(envelope);
@ -479,10 +475,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
private void handleSynchronizeVerifiedMessage(@NonNull VerifiedMessage verifiedMessage) {
IdentityUtil.processVerifiedMessage(context, verifiedMessage);
}
private void handleSynchronizeStickerPackOperation(@NonNull List<StickerPackOperationMessage> stickerPackOperations) {
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();

View File

@ -1,82 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.push.exceptions.NetworkFailureException;
import java.io.IOException;
import javax.inject.Inject;
public class RefreshAttributesJob extends BaseJob implements InjectableType {
public static final String KEY = "RefreshAttributesJob";
private static final String TAG = RefreshAttributesJob.class.getSimpleName();
@Inject SignalServiceAccountManager signalAccountManager;
public RefreshAttributesJob() {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setQueue("RefreshAttributesJob")
.build());
}
private RefreshAttributesJob(@NonNull Job.Parameters parameters) {
super(parameters);
}
@Override
public @NonNull Data serialize() {
return Data.EMPTY;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException {
int registrationId = TextSecurePreferences.getLocalRegistrationId(context);
boolean fetchesMessages = TextSecurePreferences.isFcmDisabled(context);
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(context);
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
signalAccountManager.setAccountAttributes(null, registrationId, fetchesMessages, "",
unidentifiedAccessKey, universalUnidentifiedAccess);
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RefreshUnidentifiedDeliveryAbilityJob());
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
return e instanceof NetworkFailureException;
}
@Override
public void onCanceled() {
Log.w(TAG, "Failed to update account attributes!");
}
public static class Factory implements Job.Factory<RefreshAttributesJob> {
@Override
public @NonNull RefreshAttributesJob create(@NonNull Parameters parameters, @NonNull org.thoughtcrime.securesms.jobmanager.Data data) {
return new RefreshAttributesJob(parameters);
}
}
}

View File

@ -1,105 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessagePipe;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.crypto.ProfileCipher;
import org.session.libsignal.service.api.profiles.SignalServiceProfile;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import javax.inject.Inject;
public class RefreshUnidentifiedDeliveryAbilityJob extends BaseJob implements InjectableType {
public static final String KEY = "RefreshUnidentifiedDeliveryAbilityJob";
private static final String TAG = RefreshUnidentifiedDeliveryAbilityJob.class.getSimpleName();
@Inject SignalServiceMessageReceiver receiver;
public RefreshUnidentifiedDeliveryAbilityJob() {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(10)
.build());
}
private RefreshUnidentifiedDeliveryAbilityJob(@NonNull Job.Parameters parameters) {
super(parameters);
}
@Override
public @NonNull Data serialize() {
return Data.EMPTY;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws Exception {
byte[] profileKey = ProfileKeyUtil.getProfileKey(context);
SignalServiceProfile profile = retrieveProfile(TextSecurePreferences.getLocalNumber(context));
boolean enabled = profile.getUnidentifiedAccess() != null && isValidVerifier(profileKey, profile.getUnidentifiedAccess());
TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, enabled);
Log.i(TAG, "Set UD status to: " + enabled);
}
@Override
public void onCanceled() {
}
@Override
protected boolean onShouldRetry(@NonNull Exception exception) {
return exception instanceof PushNetworkException;
}
private SignalServiceProfile retrieveProfile(@NonNull String number) throws IOException {
SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe();
if (pipe != null) {
try {
return pipe.getProfile(new SignalServiceAddress(number), Optional.absent());
} catch (IOException e) {
Log.w(TAG, e);
}
}
return receiver.retrieveProfile(new SignalServiceAddress(number), Optional.absent());
}
private boolean isValidVerifier(@NonNull byte[] profileKey, @NonNull String verifier) {
ProfileCipher profileCipher = new ProfileCipher(profileKey);
try {
return profileCipher.verifyUnidentifiedAccess(Base64.decode(verifier));
} catch (IOException e) {
Log.w(TAG, e);
return false;
}
}
public static class Factory implements Job.Factory<RefreshUnidentifiedDeliveryAbilityJob> {
@Override
public @NonNull RefreshUnidentifiedDeliveryAbilityJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new RefreshUnidentifiedDeliveryAbilityJob(parameters);
}
}
}

View File

@ -1,255 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.app.Application;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.Util;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessagePipe;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.crypto.InvalidCiphertextException;
import org.session.libsignal.service.api.crypto.ProfileCipher;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.session.libsignal.service.api.crypto.UnidentifiedAccessPair;
import org.session.libsignal.service.api.profiles.SignalServiceProfile;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.session.libsignal.service.api.util.InvalidNumberException;
import java.io.IOException;
import java.util.List;
import javax.inject.Inject;
public class RetrieveProfileJob extends BaseJob implements InjectableType {
public static final String KEY = "RetrieveProfileJob";
private static final String TAG = RetrieveProfileJob.class.getSimpleName();
private static final String KEY_ADDRESS = "address";
@Inject SignalServiceMessageReceiver receiver;
private Recipient recipient;
public RetrieveProfileJob(@NonNull Recipient recipient) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(3)
.build(),
recipient);
}
private RetrieveProfileJob(@NonNull Job.Parameters parameters, @NonNull Recipient recipient) {
super(parameters);
this.recipient = recipient;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_ADDRESS, recipient.getAddress().serialize()).build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException, InvalidKeyException {
// Loki - Do nothing
/*
try {
if (recipient.isGroupRecipient()) handleGroupRecipient(recipient);
else handleIndividualRecipient(recipient);
} catch (InvalidNumberException e) {
Log.w(TAG, e);
}
*/
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
return false;
}
@Override
public void onCanceled() {}
private void handleIndividualRecipient(Recipient recipient)
throws IOException, InvalidKeyException, InvalidNumberException
{
String number = recipient.getAddress().toPhoneString();
Optional<UnidentifiedAccess> unidentifiedAccess = getUnidentifiedAccess(recipient);
SignalServiceProfile profile;
try {
profile = retrieveProfile(number, unidentifiedAccess);
} catch (NonSuccessfulResponseCodeException e) {
if (unidentifiedAccess.isPresent()) {
profile = retrieveProfile(number, Optional.absent());
} else {
throw e;
}
}
setIdentityKey(recipient, profile.getIdentityKey());
setProfileName(recipient, profile.getName());
setProfileAvatar(recipient, profile.getAvatar());
setUnidentifiedAccessMode(recipient, profile.getUnidentifiedAccess(), profile.isUnrestrictedUnidentifiedAccess());
}
private void handleGroupRecipient(Recipient group)
throws IOException, InvalidKeyException, InvalidNumberException
{
List<Recipient> recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(group.getAddress().toGroupString(), false);
for (Recipient recipient : recipients) {
handleIndividualRecipient(recipient);
}
}
private SignalServiceProfile retrieveProfile(@NonNull String number, Optional<UnidentifiedAccess> unidentifiedAccess)
throws IOException
{
SignalServiceMessagePipe authPipe = IncomingMessageObserver.getPipe();
SignalServiceMessagePipe unidentifiedPipe = IncomingMessageObserver.getUnidentifiedPipe();
SignalServiceMessagePipe pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent() ? unidentifiedPipe
: authPipe;
if (pipe != null) {
try {
return pipe.getProfile(new SignalServiceAddress(number), unidentifiedAccess);
} catch (IOException e) {
Log.w(TAG, e);
}
}
return receiver.retrieveProfile(new SignalServiceAddress(number), unidentifiedAccess);
}
private void setIdentityKey(Recipient recipient, String identityKeyValue) {
try {
if (TextUtils.isEmpty(identityKeyValue)) {
Log.w(TAG, "Identity key is missing on profile!");
return;
}
IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyValue), 0);
if (!DatabaseFactory.getIdentityDatabase(context)
.getIdentity(recipient.getAddress())
.isPresent())
{
Log.w(TAG, "Still first use...");
return;
}
IdentityUtil.saveIdentity(context, recipient.getAddress().toPhoneString(), identityKey);
} catch (InvalidKeyException | IOException e) {
Log.w(TAG, e);
}
}
private void setUnidentifiedAccessMode(Recipient recipient, String unidentifiedAccessVerifier, boolean unrestrictedUnidentifiedAccess) {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
byte[] profileKey = recipient.getProfileKey();
if (unrestrictedUnidentifiedAccess && unidentifiedAccessVerifier != null) {
Log.i(TAG, "Marking recipient UD status as unrestricted.");
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
Log.i(TAG, "Marking recipient UD status as disabled.");
recipientDatabase.setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
} else {
ProfileCipher profileCipher = new ProfileCipher(profileKey);
boolean verifiedUnidentifiedAccess;
try {
verifiedUnidentifiedAccess = profileCipher.verifyUnidentifiedAccess(Base64.decode(unidentifiedAccessVerifier));
} catch (IOException e) {
Log.w(TAG, e);
verifiedUnidentifiedAccess = false;
}
UnidentifiedAccessMode mode = verifiedUnidentifiedAccess ? UnidentifiedAccessMode.ENABLED : UnidentifiedAccessMode.DISABLED;
Log.i(TAG, "Marking recipient UD status as " + mode.name() + " after verification.");
recipientDatabase.setUnidentifiedAccessMode(recipient, mode);
}
}
private void setProfileName(Recipient recipient, String profileName) {
try {
byte[] profileKey = recipient.getProfileKey();
if (profileKey == null) return;
String plaintextProfileName = null;
if (profileName != null) {
ProfileCipher profileCipher = new ProfileCipher(profileKey);
plaintextProfileName = new String(profileCipher.decryptName(Base64.decode(profileName)));
}
if (!Util.equals(plaintextProfileName, recipient.getProfileName())) {
DatabaseFactory.getRecipientDatabase(context).setProfileName(recipient, plaintextProfileName);
}
} catch (InvalidCiphertextException | IOException e) {
Log.w(TAG, e);
}
}
private void setProfileAvatar(Recipient recipient, String profileAvatar) {
if (recipient.getProfileKey() == null) return;
if (!Util.equals(profileAvatar, recipient.getProfileAvatar())) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RetrieveProfileAvatarJob(recipient, profileAvatar));
}
}
private Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Recipient recipient) {
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
if (unidentifiedAccess.isPresent()) {
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
}
return Optional.absent();
}
public static final class Factory implements Job.Factory<RetrieveProfileJob> {
private final Application application;
public Factory(Application application) {
this.application = application;
}
@Override
public @NonNull RetrieveProfileJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new RetrieveProfileJob(parameters, Recipient.from(application, Address.fromSerialized(data.getString(KEY_ADDRESS)), true));
}
}
}

View File

@ -1,83 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
@SuppressWarnings("WeakerAccess")
public class RotateCertificateJob extends BaseJob implements InjectableType {
public static final String KEY = "RotateCertificateJob";
private static final String TAG = RotateCertificateJob.class.getSimpleName();
@Inject SignalServiceAccountManager accountManager;
public RotateCertificateJob(Context context) {
this(new Job.Parameters.Builder()
.setQueue("__ROTATE_SENDER_CERTIFICATE__")
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build());
setContext(context);
}
private RotateCertificateJob(@NonNull Job.Parameters parameters) {
super(parameters);
}
@Override
public @NonNull Data serialize() {
return Data.EMPTY;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onAdded() {}
@Override
public void onRun() throws IOException {
// Loki - Do nothing
/*
synchronized (RotateCertificateJob.class) {
byte[] certificate = accountManager.getSenderCertificate();
TextSecurePreferences.setUnidentifiedAccessCertificate(context, certificate);
}
*/
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
return e instanceof PushNetworkException;
}
@Override
public void onCanceled() {
Log.w(TAG, "Failed to rotate sender certificate!");
}
public static final class Factory implements Job.Factory<RotateCertificateJob> {
@Override
public @NonNull RotateCertificateJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new RotateCertificateJob(parameters);
}
}
}

View File

@ -1,96 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import org.session.libsignal.service.api.util.StreamDetails;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.inject.Inject;
public class RotateProfileKeyJob extends BaseJob implements InjectableType {
public static String KEY = "RotateProfileKeyJob";
@Inject SignalServiceAccountManager accountManager;
public RotateProfileKeyJob() {
this(new Job.Parameters.Builder()
.setQueue("__ROTATE_PROFILE_KEY__")
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(25)
.setMaxInstances(1)
.build());
}
private RotateProfileKeyJob(@NonNull Job.Parameters parameters) {
super(parameters);
}
@Override
public @NonNull Data serialize() {
return Data.EMPTY;
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws Exception {
byte[] profileKey = ProfileKeyUtil.rotateProfileKey(context);
accountManager.setProfileName(profileKey, TextSecurePreferences.getProfileName(context));
accountManager.setProfileAvatar(profileKey, getProfileAvatar());
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RefreshAttributesJob());
}
@Override
public void onCanceled() {
}
@Override
protected boolean onShouldRetry(@NonNull Exception exception) {
return exception instanceof PushNetworkException;
}
private @Nullable StreamDetails getProfileAvatar() {
try {
Address localAddress = Address.fromSerialized(TextSecurePreferences.getLocalNumber(context));
File avatarFile = AvatarHelper.getAvatarFile(context, localAddress);
if (avatarFile.exists()) {
return new StreamDetails(new FileInputStream(avatarFile), "image/jpeg", avatarFile.length());
}
} catch (IOException e) {
return null;
}
return null;
}
public static final class Factory implements Job.Factory<RotateProfileKeyJob> {
@Override
public @NonNull RotateProfileKeyJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new RotateProfileKeyJob(parameters);
}
}
}

View File

@ -189,7 +189,6 @@ class BackupRestoreViewModel(application: Application): AndroidViewModel(applica
TextSecurePreferences.setRestorationTime(context, System.currentTimeMillis())
TextSecurePreferences.setHasViewedSeed(context, true)
TextSecurePreferences.setHasSeenWelcomeScreen(context, true)
TextSecurePreferences.setPromptedPushRegistration(context, true)
TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(context)
TextSecurePreferences.setHasSeenLightThemeIntroSheet(context)
val application = ApplicationContext.getInstance(context)

View File

@ -204,7 +204,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
// Clear all data if this is a secondary device
if (TextSecurePreferences.getMasterHexEncodedPublicKey(this) != null) {
TextSecurePreferences.setWasUnlinked(this, true)
ApplicationContext.getInstance(this).clearData()
ApplicationContext.getInstance(this).clearAllData()
}
// Perform chat sessions reset if requested (usually happens after backup restoration).
@ -220,36 +220,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
if (hasViewedSeed || !isMasterDevice) {
seedReminderView.visibility = View.GONE
}
// Multi device removal sheet
if (!TextSecurePreferences.getHasSeenMultiDeviceRemovalSheet(this)) {
TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(this)
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
val deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey)
if (deviceLinks.isNotEmpty()) {
val bottomSheet = MultiDeviceRemovalBottomSheet()
bottomSheet.onOKTapped = {
bottomSheet.dismiss()
}
bottomSheet.onLinkTapped = {
bottomSheet.dismiss()
val url = "https://getsession.org/faq"
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
}
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
return
}
}
// Light theme introduction sheet
if (!TextSecurePreferences.hasSeenLightThemeIntroSheet(this) &&
UiModeUtilities.isDayUiMode(this)) {
TextSecurePreferences.setHasSeenLightThemeIntroSheet(this)
val bottomSheet = LightThemeFeatureIntroBottomSheet()
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
return
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View File

@ -20,6 +20,7 @@ import kotlinx.coroutines.withContext
import network.loki.messenger.R
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
@ -73,6 +74,7 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
try {
OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, url, channel)
} catch (e: Exception) {
Log.e("JoinPublicChatActivity", "Fialed to join open group.", e)
withContext(Dispatchers.Main) {
hideLoader()
Toast.makeText(this@JoinPublicChatActivity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()

View File

@ -2,15 +2,18 @@ package org.thoughtcrime.securesms.loki.activities
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_landing.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.views.FakeChatView
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
class LandingActivity : BaseActionBarActivity() {
@ -18,13 +21,26 @@ class LandingActivity : BaseActionBarActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_landing)
setUpActionBarSessionLogo(true)
fakeChatView.startAnimating()
registerButton.setOnClickListener { register() }
restoreButton.setOnClickListener { restoreFromRecoveryPhrase() }
restoreBackupButton.setOnClickListener { restoreFromBackup() }
findViewById<FakeChatView>(R.id.fakeChatView).startAnimating()
findViewById<View>(R.id.registerButton).setOnClickListener { register() }
findViewById<View>(R.id.restoreButton).setOnClickListener { restoreFromRecoveryPhrase() }
findViewById<View>(R.id.restoreBackupButton).setOnClickListener { restoreFromBackup() }
if (TextSecurePreferences.getWasUnlinked(this)) {
Toast.makeText(this, R.string.activity_landing_device_unlinked_dialog_title, Toast.LENGTH_LONG).show()
}
// Setup essentials for a new user.
IdentityKeyUtil.generateIdentityKeyPair(this)
TextSecurePreferences.setLastExperienceVersionCode(this, Util.getCanonicalVersionCode())
TextSecurePreferences.setPasswordDisabled(this, true)
TextSecurePreferences.setReadReceiptsEnabled(this, true)
TextSecurePreferences.setTypingIndicatorsEnabled(this, true)
//AC: This is a temporary workaround to trick the old code that the screen is unlocked.
KeyCachingService.setMasterSecret(applicationContext, Object())
}
private fun register() {
@ -46,7 +62,6 @@ class LandingActivity : BaseActionBarActivity() {
IdentityKeyUtil.delete(this, IdentityKeyUtil.LOKI_SEED)
TextSecurePreferences.removeLocalNumber(this)
TextSecurePreferences.setHasSeenWelcomeScreen(this, false)
TextSecurePreferences.setPromptedPushRegistration(this, false)
val application = ApplicationContext.getInstance(this)
application.stopPolling()
}

View File

@ -151,7 +151,6 @@ class PNModeActivity : BaseActionBarActivity() {
return
}
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setPromptedPushRegistration(this, true)
TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView))
TextSecurePreferences.setHasSeenMultiDeviceRemovalSheet(this)
TextSecurePreferences.setHasSeenLightThemeIntroSheet(this)

View File

@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.loki.utilities.push
@ -72,9 +71,6 @@ class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(this, registrationID)
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey),
IdentityKeyUtil.getIdentityKeyPair(this).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
true, System.currentTimeMillis(), true)
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
TextSecurePreferences.setRestorationTime(this, System.currentTimeMillis())
TextSecurePreferences.setHasViewedSeed(this, true)

View File

@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
@ -109,9 +108,6 @@ class RegisterActivity : BaseActionBarActivity() {
val userHexEncodedPublicKey = x25519KeyPair!!.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(this, registrationID)
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey),
IdentityKeyUtil.getIdentityKeyPair(this).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
true, System.currentTimeMillis(), true)
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
TextSecurePreferences.setRestorationTime(this, 0)
TextSecurePreferences.setHasViewedSeed(this, false)

View File

@ -29,7 +29,7 @@ class ClosedGroupPoller private constructor(private val context: Context, privat
// region Settings
companion object {
private val pollInterval: Long = 2 * 1000
private val pollInterval: Long = 4 * 1000
public lateinit var shared: ClosedGroupPoller

View File

@ -0,0 +1,99 @@
package org.thoughtcrime.securesms.loki.api
import android.content.Context
import android.util.Log
import com.goterl.lazycode.lazysodium.LazySodiumAndroid
import com.goterl.lazycode.lazysodium.SodiumAndroid
import com.goterl.lazycode.lazysodium.interfaces.Box
import com.goterl.lazycode.lazysodium.interfaces.Sign
import org.session.libsignal.libsignal.util.Hex
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
import org.session.libsignal.service.internal.push.SignalServiceProtos.Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE
import org.session.libsignal.service.internal.push.SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
import org.thoughtcrime.securesms.util.TextSecurePreferences
class SessionProtocolImpl(private val context: Context) : SessionProtocol {
override fun encrypt(plaintext: ByteArray, recipientHexEncodedX25519PublicKey: String): ByteArray {
val userED25519KeyPair = KeyPairUtilities.getUserED25519KeyPair(context) ?: throw SessionProtocol.Exception.NoUserED25519KeyPair
val recipientX25519PublicKey = Hex.fromStringCondensed(recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded())
val sodium = LazySodiumAndroid(SodiumAndroid())
val verificationData = plaintext + userED25519KeyPair.publicKey.asBytes + recipientX25519PublicKey
val signature = ByteArray(Sign.BYTES)
try {
sodium.cryptoSignDetached(signature, verificationData, verificationData.size.toLong(), userED25519KeyPair.secretKey.asBytes)
} catch (exception: Exception) {
Log.d("Loki", "Couldn't sign message due to error: $exception.")
throw SessionProtocol.Exception.SigningFailed
}
val plaintextWithMetadata = plaintext + userED25519KeyPair.publicKey.asBytes + signature
val ciphertext = ByteArray(plaintextWithMetadata.size + Box.SEALBYTES)
try {
sodium.cryptoBoxSeal(ciphertext, plaintextWithMetadata, plaintextWithMetadata.size.toLong(), recipientX25519PublicKey)
} catch (exception: Exception) {
Log.d("Loki", "Couldn't encrypt message due to error: $exception.")
throw SessionProtocol.Exception.EncryptionFailed
}
return ciphertext
}
override fun decrypt(envelope: SignalServiceEnvelope): Pair<ByteArray, String> {
val ciphertext = envelope.content ?: throw SessionProtocol.Exception.NoData
val recipientX25519PrivateKey: ByteArray
val recipientX25519PublicKey: ByteArray
when (envelope.type) {
UNIDENTIFIED_SENDER_VALUE -> {
recipientX25519PrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
recipientX25519PublicKey = Hex.fromStringCondensed(TextSecurePreferences.getLocalNumber(context).removing05PrefixIfNeeded())
}
CLOSED_GROUP_CIPHERTEXT_VALUE -> {
val hexEncodedGroupPublicKey = envelope.source
val sskDB = DatabaseFactory.getSSKDatabase(context)
if (!sskDB.isSSKBasedClosedGroup(hexEncodedGroupPublicKey)) { throw SessionProtocol.Exception.InvalidGroupPublicKey }
val hexEncodedGroupPrivateKey = sskDB.getClosedGroupPrivateKey(hexEncodedGroupPublicKey) ?: throw SessionProtocol.Exception.NoGroupPrivateKey
recipientX25519PrivateKey = Hex.fromStringCondensed(hexEncodedGroupPrivateKey)
recipientX25519PublicKey = Hex.fromStringCondensed(hexEncodedGroupPublicKey.removing05PrefixIfNeeded())
}
else -> throw AssertionError()
}
val sodium = LazySodiumAndroid(SodiumAndroid())
val signatureSize = Sign.BYTES
val ed25519PublicKeySize = Sign.PUBLICKEYBYTES
// 1. ) Decrypt the message
val plaintextWithMetadata = ByteArray(ciphertext.size - Box.SEALBYTES)
try {
sodium.cryptoBoxSealOpen(plaintextWithMetadata, ciphertext, ciphertext.size.toLong(), recipientX25519PublicKey, recipientX25519PrivateKey)
} catch (exception: Exception) {
Log.d("Loki", "Couldn't decrypt message due to error: $exception.")
throw SessionProtocol.Exception.DecryptionFailed
}
if (plaintextWithMetadata.size <= (signatureSize + ed25519PublicKeySize)) { throw SessionProtocol.Exception.DecryptionFailed }
// 2. ) Get the message parts
val signature = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - signatureSize until plaintextWithMetadata.size)
val senderED25519PublicKey = plaintextWithMetadata.sliceArray(plaintextWithMetadata.size - (signatureSize + ed25519PublicKeySize) until plaintextWithMetadata.size - signatureSize)
val plaintext = plaintextWithMetadata.sliceArray(0 until plaintextWithMetadata.size - (signatureSize + ed25519PublicKeySize))
// 3. ) Verify the signature
val verificationData = (plaintext + senderED25519PublicKey + recipientX25519PublicKey)
try {
val isValid = sodium.cryptoSignVerifyDetached(signature, verificationData, verificationData.size, senderED25519PublicKey)
if (!isValid) { throw SessionProtocol.Exception.InvalidSignature }
} catch (exception: Exception) {
Log.d("Loki", "Couldn't verify message signature due to error: $exception.")
throw SessionProtocol.Exception.InvalidSignature
}
// 4. ) Get the sender's X25519 public key
val senderX25519PublicKey = ByteArray(Sign.CURVE25519_PUBLICKEYBYTES)
sodium.convertPublicKeyEd25519ToCurve25519(senderX25519PublicKey, senderED25519PublicKey)
return Pair(plaintext, "05" + senderX25519PublicKey.toHexString())
}
}

View File

@ -9,6 +9,8 @@ import org.thoughtcrime.securesms.loki.utilities.*
import org.session.libsignal.service.loki.api.Snode
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
import org.thoughtcrime.securesms.util.TextSecurePreferences
import java.util.*
class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiAPIDatabaseProtocol {
@ -375,6 +377,16 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
return database.delete(openGroupProfilePictureTable, "$publicChatID = ?", arrayOf(index)) > 0
}
override fun getLastSnodePoolRefreshDate(): Date? {
val time = TextSecurePreferences.getLastSnodePoolRefreshDate(context)
if (time <= 0) { return null }
return Date(time)
}
override fun setLastSnodePoolRefreshDate(date: Date) {
TextSecurePreferences.setLastSnodePoolRefreshDate(context, date)
}
// region Deprecated
override fun getDeviceLinks(publicKey: String): Set<DeviceLink> {
return setOf()

View File

@ -10,12 +10,13 @@ import android.view.LayoutInflater
import kotlinx.android.synthetic.main.dialog_clear_all_data.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.loki.utilities.KeyPairUtilities
class ClearAllDataDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(context!!)
val contentView = LayoutInflater.from(context!!).inflate(R.layout.dialog_clear_all_data, null)
val builder = AlertDialog.Builder(requireContext())
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_clear_all_data, null)
contentView.cancelButton.setOnClickListener { dismiss() }
contentView.clearAllDataButton.setOnClickListener { clearAllData() }
builder.setView(contentView)
@ -25,6 +26,19 @@ class ClearAllDataDialog : DialogFragment() {
}
private fun clearAllData() {
ApplicationContext.getInstance(context).clearData()
if (KeyPairUtilities.hasV2KeyPair(requireContext())) {
ApplicationContext.getInstance(context).clearAllData()
} else {
val dialog = AlertDialog.Builder(requireContext())
val message = "Weve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID."
dialog.setMessage(message)
dialog.setPositiveButton("Yes") { _, _ ->
ApplicationContext.getInstance(context).clearAllData()
}
dialog.setNegativeButton("Cancel") { _, _ ->
// Do nothing
}
dialog.create().show()
}
}
}

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.utilities
import android.content.Context
import com.goterl.lazycode.lazysodium.LazySodiumAndroid
import com.goterl.lazycode.lazysodium.SodiumAndroid
import com.goterl.lazycode.lazysodium.utils.Key
import com.goterl.lazycode.lazysodium.utils.KeyPair
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.util.Base64
@ -15,12 +16,6 @@ object KeyPairUtilities {
private val sodium = LazySodiumAndroid(SodiumAndroid())
data class KeyPairGenerationResult(
val seed: ByteArray,
val ed25519KeyPair: KeyPair,
val x25519KeyPair: ECKeyPair
)
fun generate(): KeyPairGenerationResult {
val seed = sodium.randomBytesBuf(16)
try {
@ -45,4 +40,22 @@ object KeyPairUtilities {
IdentityKeyUtil.save(context, IdentityKeyUtil.ED25519_PUBLIC_KEY, Base64.encodeBytes(ed25519KeyPair.publicKey.asBytes))
IdentityKeyUtil.save(context, IdentityKeyUtil.ED25519_SECRET_KEY, Base64.encodeBytes(ed25519KeyPair.secretKey.asBytes))
}
fun hasV2KeyPair(context: Context): Boolean {
return (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) != null)
}
fun getUserED25519KeyPair(context: Context): KeyPair? {
val hexEncodedED25519PublicKey = IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_PUBLIC_KEY) ?: return null
val hexEncodedED25519SecretKey = IdentityKeyUtil.retrieve(context, IdentityKeyUtil.ED25519_SECRET_KEY) ?: return null
val ed25519PublicKey = Key.fromBase64String(hexEncodedED25519PublicKey)
val ed25519SecretKey = Key.fromBase64String(hexEncodedED25519SecretKey)
return KeyPair(ed25519PublicKey, ed25519SecretKey)
}
data class KeyPairGenerationResult(
val seed: ByteArray,
val ed25519KeyPair: KeyPair,
val x25519KeyPair: ECKeyPair
)
}

View File

@ -5,17 +5,13 @@ import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.PassphraseChangeActivity;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -27,8 +23,6 @@ import network.loki.messenger.R;
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment implements InjectableType {
private CheckBoxPreference disablePassphrase;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
@ -39,17 +33,12 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary");
this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF).setOnPreferenceClickListener(new ChangePassphraseClickListener());
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setOnPreferenceClickListener(new PassphraseIntervalClickListener());
this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
this.findPreference(TextSecurePreferences.TYPING_INDICATORS).setOnPreferenceChangeListener(new TypingIndicatorsToggleListener());
this.findPreference(TextSecurePreferences.LINK_PREVIEWS).setOnPreferenceChangeListener(new LinkPreviewToggleListener());
disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
initializeVisibility();
}
@ -62,16 +51,9 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
@Override
public void onResume() {
super.onResume();
if (!TextSecurePreferences.isPasswordDisabled(getContext())) initializePassphraseTimeoutSummary();
else initializeScreenLockTimeoutSummary();
disablePassphrase.setChecked(!TextSecurePreferences.isPasswordDisabled(getActivity()));
}
private void initializePassphraseTimeoutSummary() {
int timeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(getActivity());
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF)
.setSummary(getResources().getQuantityString(R.plurals.AppProtectionPreferenceFragment_minutes, timeoutMinutes, timeoutMinutes));
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
initializeScreenLockTimeoutSummary();
}
}
private void initializeScreenLockTimeoutSummary() {
@ -87,11 +69,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
private void initializeVisibility() {
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
findPreference("pref_enable_passphrase_temporary").setVisible(false);
findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF).setVisible(false);
findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setVisible(false);
findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF).setVisible(false);
KeyguardManager keyguardManager = (KeyguardManager)getContext().getSystemService(Context.KEYGUARD_SERVICE);
if (!keyguardManager.isKeyguardSecure()) {
((SwitchPreferenceCompat)findPreference(TextSecurePreferences.SCREEN_LOCK)).setChecked(false);
@ -178,90 +155,4 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
return true;
}
}
public static CharSequence getSummary(Context context) {
final int privacySummaryResId = R.string.ApplicationPreferencesActivity_privacy_summary;
final String onRes = context.getString(R.string.ApplicationPreferencesActivity_on);
final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off);
if (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context)) {
// if (TextSecurePreferences.isRegistrationtLockEnabled(context)) {
// return context.getString(privacySummaryResId, offRes, onRes);
// } else {
return context.getString(privacySummaryResId, offRes, offRes);
// }
} else {
// if (TextSecurePreferences.isRegistrationtLockEnabled(context)) {
// return context.getString(privacySummaryResId, onRes, onRes);
// } else {
return context.getString(privacySummaryResId, onRes, offRes);
// }
}
}
private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (MasterSecretUtil.isPassphraseInitialized(getActivity())) {
startActivity(new Intent(getActivity(), PassphraseChangeActivity.class));
} else {
Toast.makeText(getActivity(),
R.string.ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet,
Toast.LENGTH_LONG).show();
}
return true;
}
}
private class PassphraseIntervalClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
new TimeDurationPickerDialog(getContext(), (view, duration) -> {
int timeoutMinutes = Math.max((int)TimeUnit.MILLISECONDS.toMinutes(duration), 1);
TextSecurePreferences.setPassphraseTimeoutInterval(getActivity(), timeoutMinutes);
initializePassphraseTimeoutSummary();
}, 0).show();
return true;
}
}
private class DisablePassphraseClickListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
if (((CheckBoxPreference)preference).isChecked()) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ApplicationPreferencesActivity_disable_passphrase);
builder.setMessage(R.string.ApplicationPreferencesActivity_this_will_permanently_unlock_signal_and_message_notifications);
builder.setIconAttribute(R.attr.dialog_alert_icon);
builder.setPositiveButton(R.string.ApplicationPreferencesActivity_disable, (dialog, which) -> {
MasterSecretUtil.changeMasterSecretPassphrase(getActivity(),
KeyCachingService.getMasterSecret(getContext()),
MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
TextSecurePreferences.setPasswordDisabled(getActivity(), true);
((CheckBoxPreference)preference).setChecked(false);
Intent intent = new Intent(getActivity(), KeyCachingService.class);
intent.setAction(KeyCachingService.DISABLE_ACTION);
getActivity().startService(intent);
initializeVisibility();
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
} else {
Intent intent = new Intent(getActivity(), PassphraseChangeActivity.class);
startActivity(intent);
}
return false;
}
}
}

View File

@ -21,8 +21,10 @@ import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
@ -34,13 +36,9 @@ import androidx.core.app.NotificationCompat;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
import org.thoughtcrime.securesms.DummyActivity;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -53,7 +51,8 @@ import network.loki.messenger.R;
*
* @author Moxie Marlinspike
*/
//TODO AC: This service does only serve one purpose now - to track the screen lock state and handle the timer.
// We need to refactor it and cleanup from all the old Signal code.
public class KeyCachingService extends Service {
private static final String TAG = KeyCachingService.class.getSimpleName();
@ -61,19 +60,41 @@ public class KeyCachingService extends Service {
public static final int SERVICE_RUNNING_ID = 4141;
public static final String KEY_PERMISSION = "network.loki.messenger.ACCESS_SESSION_SECRETS";
public static final String NEW_KEY_EVENT = "org.thoughtcrime.securesms.service.action.NEW_KEY_EVENT";
// public static final String NEW_KEY_EVENT = "org.thoughtcrime.securesms.service.action.NEW_KEY_EVENT";
public static final String CLEAR_KEY_EVENT = "org.thoughtcrime.securesms.service.action.CLEAR_KEY_EVENT";
public static final String LOCK_TOGGLED_EVENT = "org.thoughtcrime.securesms.service.action.LOCK_ENABLED_EVENT";
private static final String PASSPHRASE_EXPIRED_EVENT = "org.thoughtcrime.securesms.service.action.PASSPHRASE_EXPIRED_EVENT";
public static final String CLEAR_KEY_ACTION = "org.thoughtcrime.securesms.service.action.CLEAR_KEY";
public static final String DISABLE_ACTION = "org.thoughtcrime.securesms.service.action.DISABLE";
public static final String LOCALE_CHANGE_EVENT = "org.thoughtcrime.securesms.service.action.LOCALE_CHANGE_EVENT";
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
// public static final String DISABLE_ACTION = "org.thoughtcrime.securesms.service.action.DISABLE";
private final IBinder binder = new KeySetBinder();
private static MasterSecret masterSecret;
// AC: This is a temporal drop off replacement for the refactoring time being.
// This field only indicates if the app was unlocked or not (null means locked).
private static Object masterSecret;
/**
* A temporal utility method to quickly call {@link KeyCachingService#setMasterSecret(Object)}
* without explicitly binding to the service.
*/
public static void setMasterSecret(Context context, Object masterSecret) {
// Start and bind to the KeyCachingService instance.
Intent bindIntent = new Intent(context, KeyCachingService.class);
context.startService(bindIntent);
context.bindService(bindIntent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
KeyCachingService service = ((KeySetBinder) binder).getService();
service.setMasterSecret(masterSecret);
context.unbindService(this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);
}
public KeyCachingService() {}
@ -81,18 +102,6 @@ public class KeyCachingService extends Service {
return getMasterSecret(context) == null;
}
public static synchronized @Nullable MasterSecret getMasterSecret(Context context) {
if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
try {
return MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
} catch (InvalidPassphraseException e) {
Log.w("KeyCachingService", e);
}
}
return masterSecret;
}
public static void onAppForegrounded(@NonNull Context context) {
ServiceUtil.getAlarmManager(context).cancel(buildExpirationPendingIntent(context));
}
@ -101,13 +110,25 @@ public class KeyCachingService extends Service {
startTimeoutIfAppropriate(context);
}
public static synchronized @Nullable Object getMasterSecret(Context context) {
// if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
// try {
// return MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
// } catch (InvalidPassphraseException e) {
// Log.w("KeyCachingService", e);
// }
// }
return masterSecret;
}
@SuppressLint("StaticFieldLeak")
public void setMasterSecret(final MasterSecret masterSecret) {
public void setMasterSecret(final Object masterSecret) {
synchronized (KeyCachingService.class) {
KeyCachingService.masterSecret = masterSecret;
foregroundService();
broadcastNewSecret();
// broadcastNewSecret();
startTimeoutIfAppropriate(this);
new AsyncTask<Void, Void, Void>() {
@ -131,8 +152,7 @@ public class KeyCachingService extends Service {
switch (intent.getAction()) {
case CLEAR_KEY_ACTION: handleClearKey(); break;
case PASSPHRASE_EXPIRED_EVENT: handleClearKey(); break;
case DISABLE_ACTION: handleDisableService(); break;
case LOCALE_CHANGE_EVENT: handleLocaleChanged(); break;
// case DISABLE_ACTION: handleDisableService(); break;
case LOCK_TOGGLED_EVENT: handleLockToggled(); break;
}
}
@ -146,12 +166,12 @@ public class KeyCachingService extends Service {
super.onCreate();
if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
} catch (InvalidPassphraseException e) {
Log.w("KeyCachingService", e);
}
// try {
// MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(new Object());
// } catch (InvalidPassphraseException e) {
// Log.w("KeyCachingService", e);
// }
}
}
@ -196,12 +216,12 @@ public class KeyCachingService extends Service {
private void handleLockToggled() {
stopForeground(true);
try {
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
// try {
// MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
setMasterSecret(masterSecret);
} catch (InvalidPassphraseException e) {
Log.w(TAG, e);
}
// } catch (InvalidPassphraseException e) {
// Log.w(TAG, e);
// }
}
private void handleDisableService() {
@ -212,11 +232,6 @@ public class KeyCachingService extends Service {
}
}
private void handleLocaleChanged() {
dynamicLanguage.updateServiceLocale(this);
foregroundService();
}
private static void startTimeoutIfAppropriate(@NonNull Context context) {
boolean appVisible = ApplicationContext.getInstance(context).isAppVisible();
boolean secretSet = KeyCachingService.masterSecret != null;
@ -268,14 +283,14 @@ public class KeyCachingService extends Service {
startForeground(SERVICE_RUNNING_ID, builder.build());
}
private void broadcastNewSecret() {
Log.i(TAG, "Broadcasting new secret...");
Intent intent = new Intent(NEW_KEY_EVENT);
intent.setPackage(getApplicationContext().getPackageName());
sendBroadcast(intent, KEY_PERMISSION);
}
// private void broadcastNewSecret() {
// Log.i(TAG, "Broadcasting new secret...");
//
// Intent intent = new Intent(NEW_KEY_EVENT);
// intent.setPackage(getApplicationContext().getPackageName());
//
// sendBroadcast(intent, KEY_PERMISSION);
// }
private PendingIntent buildLockIntent() {
Intent intent = new Intent(this, KeyCachingService.class);

View File

@ -1,38 +0,0 @@
package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class RotateSenderCertificateListener extends PersistentAlarmManagerListener {
private static final long INTERVAL = TimeUnit.DAYS.toMillis(1);
@Override
protected long getNextScheduledExecutionTime(Context context) {
return TextSecurePreferences.getUnidentifiedAccessCertificateRotationTime(context);
}
@Override
protected long onAlarm(Context context, long scheduledTime) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new RotateCertificateJob(context));
long nextTime = System.currentTimeMillis() + INTERVAL;
TextSecurePreferences.setUnidentifiedAccessCertificateRotationTime(context, nextTime);
return nextTime;
}
public static void schedule(Context context) {
new RotateSenderCertificateListener().onReceive(context, new Intent());
}
}

View File

@ -1,252 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage;
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.sms.IncomingIdentityVerifiedMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingIdentityDefaultMessage;
import org.thoughtcrime.securesms.sms.OutgoingIdentityVerifiedMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.SignalProtocolAddress;
import org.session.libsignal.libsignal.state.IdentityKeyStore;
import org.session.libsignal.libsignal.state.SessionRecord;
import org.session.libsignal.libsignal.state.SessionStore;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.multidevice.VerifiedMessage;
import java.util.List;
import network.loki.messenger.R;
import static org.session.libsignal.libsignal.SessionCipher.SESSION_LOCK;
public class IdentityUtil {
private static final String TAG = IdentityUtil.class.getSimpleName();
public static ListenableFuture<Optional<IdentityRecord>> getRemoteIdentityKey(final Context context, final Recipient recipient) {
final SettableFuture<Optional<IdentityRecord>> future = new SettableFuture<>();
new AsyncTask<Recipient, Void, Optional<IdentityRecord>>() {
@Override
protected Optional<IdentityRecord> doInBackground(Recipient... recipient) {
return DatabaseFactory.getIdentityDatabase(context)
.getIdentity(recipient[0].getAddress());
}
@Override
protected void onPostExecute(Optional<IdentityRecord> result) {
future.set(result);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient);
return future;
}
public static void markIdentityVerified(Context context, Recipient recipient, boolean verified, boolean remote)
{
long time = System.currentTimeMillis();
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
GroupDatabase.Reader reader = groupDatabase.getGroups();
GroupDatabase.GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) {
if (groupRecord.isRSSFeed() || groupRecord.isOpenGroup()) { continue; }
if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive() && !groupRecord.isMms()) {
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId(), SignalServiceGroup.GroupType.SIGNAL);
if (remote) {
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
else incoming = new IncomingIdentityDefaultMessage(incoming);
smsDatabase.insertMessageInbox(incoming);
} else {
Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(group.getGroupId(), false)), true);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(groupRecipient);
OutgoingTextMessage outgoing ;
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipient);
else outgoing = new OutgoingIdentityDefaultMessage(recipient);
DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoing, false, time, null);
}
}
}
if (remote) {
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false);
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
else incoming = new IncomingIdentityDefaultMessage(incoming);
smsDatabase.insertMessageInbox(incoming);
} else {
OutgoingTextMessage outgoing;
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipient);
else outgoing = new OutgoingIdentityDefaultMessage(recipient);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
Log.i(TAG, "Inserting verified outbox...");
DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoing, false, time, null);
}
}
public static void markIdentityUpdate(Context context, Recipient recipient) {
long time = System.currentTimeMillis();
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
GroupDatabase.Reader reader = groupDatabase.getGroups();
GroupDatabase.GroupRecord groupRecord;
while ((groupRecord = reader.getNext()) != null) {
if (groupRecord.isRSSFeed() || groupRecord.isOpenGroup()) { continue; }
if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) {
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId(), SignalServiceGroup.GroupType.SIGNAL);
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);
smsDatabase.insertMessageInbox(groupUpdate);
}
}
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false);
IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate);
if (insertResult.isPresent()) {
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
}
public static void saveIdentity(Context context, String number, IdentityKey identityKey) {
synchronized (SESSION_LOCK) {
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context);
SessionStore sessionStore = new TextSecureSessionStore(context);
SignalProtocolAddress address = new SignalProtocolAddress(number, 1);
if (identityKeyStore.saveIdentity(address, identityKey)) {
if (sessionStore.containsSession(address)) {
SessionRecord sessionRecord = sessionStore.loadSession(address);
sessionRecord.archiveCurrentState();
sessionStore.storeSession(address, sessionRecord);
}
}
}
}
public static void processVerifiedMessage(Context context, VerifiedMessage verifiedMessage) {
synchronized (SESSION_LOCK) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
Recipient recipient = Recipient.from(context, Address.fromExternal(context, verifiedMessage.getDestination()), true);
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(recipient.getAddress());
if (!identityRecord.isPresent() && verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT) {
Log.w(TAG, "No existing record for default status");
return;
}
if (verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT &&
identityRecord.isPresent() &&
identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey()) &&
identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.DEFAULT)
{
identityDatabase.setVerified(recipient.getAddress(), identityRecord.get().getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT);
markIdentityVerified(context, recipient, false, true);
}
if (verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.VERIFIED &&
(!identityRecord.isPresent() ||
(identityRecord.isPresent() && !identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey())) ||
(identityRecord.isPresent() && identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED)))
{
saveIdentity(context, verifiedMessage.getDestination(), verifiedMessage.getIdentityKey());
identityDatabase.setVerified(recipient.getAddress(), verifiedMessage.getIdentityKey(), IdentityDatabase.VerifiedStatus.VERIFIED);
markIdentityVerified(context, recipient, true, true);
}
}
}
public static @Nullable String getUnverifiedBannerDescription(@NonNull Context context,
@NonNull List<Recipient> unverified)
{
return getPluralizedIdentityDescription(context, unverified,
R.string.IdentityUtil_unverified_banner_one,
R.string.IdentityUtil_unverified_banner_two,
R.string.IdentityUtil_unverified_banner_many);
}
public static @Nullable String getUnverifiedSendDialogDescription(@NonNull Context context,
@NonNull List<Recipient> unverified)
{
return getPluralizedIdentityDescription(context, unverified,
R.string.IdentityUtil_unverified_dialog_one,
R.string.IdentityUtil_unverified_dialog_two,
R.string.IdentityUtil_unverified_dialog_many);
}
public static @Nullable String getUntrustedSendDialogDescription(@NonNull Context context,
@NonNull List<Recipient> untrusted)
{
return getPluralizedIdentityDescription(context, untrusted,
R.string.IdentityUtil_untrusted_dialog_one,
R.string.IdentityUtil_untrusted_dialog_two,
R.string.IdentityUtil_untrusted_dialog_many);
}
private static @Nullable String getPluralizedIdentityDescription(@NonNull Context context,
@NonNull List<Recipient> recipients,
@StringRes int resourceOne,
@StringRes int resourceTwo,
@StringRes int resourceMany)
{
if (recipients.isEmpty()) return null;
if (recipients.size() == 1) {
String name = recipients.get(0).toShortString();
return context.getString(resourceOne, name);
} else {
String firstName = recipients.get(0).toShortString();
String secondName = recipients.get(1).toShortString();
if (recipients.size() == 2) {
return context.getString(resourceTwo, firstName, secondName);
} else {
int othersCount = recipients.size() - 2;
String nMore = context.getResources().getQuantityString(R.plurals.identity_others, othersCount, othersCount);
return context.getString(resourceMany, firstName, secondName, nMore);
}
}
}
}

View File

@ -23,6 +23,7 @@ import java.io.IOException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@ -81,7 +82,6 @@ public class TextSecurePreferences {
public static final String REGISTERED_GCM_PREF = "pref_gcm_registered";
private static final String GCM_PASSWORD_PREF = "pref_gcm_password";
private static final String SEEN_WELCOME_SCREEN_PREF = "pref_seen_welcome_screen";
private static final String PROMPTED_PUSH_REGISTRATION_PREF = "pref_prompted_push_registration";
private static final String PROMPTED_DEFAULT_SMS_PREF = "pref_prompted_default_sms";
private static final String PROMPTED_OPTIMIZE_DOZE_PREF = "pref_prompted_optimize_doze";
private static final String PROMPTED_SHARE_PREF = "pref_prompted_share";
@ -136,7 +136,7 @@ public class TextSecurePreferences {
private static final String DATABASE_UNENCRYPTED_SECRET = "pref_database_unencrypted_secret";
private static final String ATTACHMENT_ENCRYPTED_SECRET = "pref_attachment_encrypted_secret";
private static final String ATTACHMENT_UNENCRYPTED_SECRET = "pref_attachment_unencrypted_secret";
private static final String NEEDS_SQLCIPHER_MIGRATION = "pref_needs_sql_cipher_migration";
private static final String NEEDS_SQLCIPHER_MIGRATION = "pref_needs_sql_cipher_migration"; //TODO AC: Delete
private static final String NEXT_PRE_KEY_ID = "pref_next_pre_key_id";
private static final String ACTIVE_SIGNED_PRE_KEY_ID = "pref_active_signed_pre_key_id";
@ -879,21 +879,13 @@ public class TextSecurePreferences {
}
public static boolean hasSeenWelcomeScreen(Context context) {
return getBooleanPreference(context, SEEN_WELCOME_SCREEN_PREF, true);
return getBooleanPreference(context, SEEN_WELCOME_SCREEN_PREF, false);
}
public static void setHasSeenWelcomeScreen(Context context, boolean value) {
setBooleanPreference(context, SEEN_WELCOME_SCREEN_PREF, value);
}
public static boolean hasPromptedPushRegistration(Context context) {
return getBooleanPreference(context, PROMPTED_PUSH_REGISTRATION_PREF, false);
}
public static void setPromptedPushRegistration(Context context, boolean value) {
setBooleanPreference(context, PROMPTED_PUSH_REGISTRATION_PREF, value);
}
public static boolean hasPromptedDefaultSmsProvider(Context context) {
return getBooleanPreference(context, PROMPTED_DEFAULT_SMS_PREF, false);
}
@ -1246,6 +1238,14 @@ public class TextSecurePreferences {
public static void setHasSeenLightThemeIntroSheet(Context context) {
setBooleanPreference(context, "has_seen_light_theme_intro_sheet", true);
}
public static long getLastSnodePoolRefreshDate(Context context) {
return getLongPreference(context, "last_snode_pool_refresh_date", 0);
}
public static void setLastSnodePoolRefreshDate(Context context, Date date) {
setLongPreference(context, "last_snode_pool_refresh_date", date.getTime());
}
// endregion
// region Backup related

View File

@ -1,41 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import android.text.style.ClickableSpan;
import android.view.View;
import org.thoughtcrime.securesms.VerifyIdentityActivity;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.session.libsignal.libsignal.IdentityKey;
public class VerifySpan extends ClickableSpan {
private final Context context;
private final Address address;
private final IdentityKey identityKey;
public VerifySpan(@NonNull Context context, @NonNull IdentityKeyMismatch mismatch) {
this.context = context;
this.address = mismatch.getAddress();
this.identityKey = mismatch.getIdentityKey();
}
public VerifySpan(@NonNull Context context, @NonNull Address address, @NonNull IdentityKey identityKey) {
this.context = context;
this.address = address;
this.identityKey = identityKey;
}
@Override
public void onClick(@NonNull View widget) {
Intent intent = new Intent(context, VerifyIdentityActivity.class);
intent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, address);
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey));
intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, false);
context.startActivity(intent);
}
}

View File

@ -1,22 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import androidx.annotation.NonNull;
import java.io.IOException;
public class VersionTracker {
public static int getLastSeenVersion(@NonNull Context context) {
return TextSecurePreferences.getLastVersionCode(context);
}
public static void updateLastSeenVersion(@NonNull Context context) {
try {
int currentVersionCode = Util.getCanonicalVersionCode();
TextSecurePreferences.setLastVersionCode(context, currentVersionCode);
} catch (IOException ioe) {
throw new AssertionError(ioe);
}
}
}

View File

@ -0,0 +1,29 @@
package org.thoughtcrime.securesms.util
import android.content.Context
import java.io.IOException
import java.lang.RuntimeException
object VersionTracker {
@JvmStatic
fun getLastSeenVersion(context: Context): Int {
var version = TextSecurePreferences.getLastVersionCode(context)
// Zero means the app is freshly installed = user is actually on the current version.
if (version == 0) {
version = updateLastSeenVersion(context)
}
return version
}
@JvmStatic
fun updateLastSeenVersion(context: Context): Int {
return try {
val currentVersionCode = Util.getCanonicalVersionCode()
TextSecurePreferences.setLastVersionCode(context, currentVersionCode)
currentVersionCode
} catch (e: IOException) {
throw RuntimeException("Failed to update the last seen app version.", e)
}
}
}

View File

@ -1,116 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
<LinearLayout
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/very_large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="Message Notifications" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="There are two ways Session can notify you of new messages." />
<org.thoughtcrime.securesms.loki.views.PNModeView
android:id="@+id/fcmOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/very_large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="Message Notifications" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="Fast Mode" />
android:text="There are two ways Session can notify you of new messages." />
<TextView
<org.thoughtcrime.securesms.loki.views.PNModeView
android:id="@+id/fcmOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="Youll be notified of new messages reliably and immediately using Googles notification servers. The contents of your messages, and who youre messaging, are never exposed to Google." />
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="Fast Mode" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="Youll be notified of new messages reliably and immediately using Googles notification servers. The contents of your messages, and who youre messaging, are never exposed to Google." />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/small_font_size"
android:textColor="@color/accent"
android:textStyle="bold"
android:text="@string/activity_pn_mode_recommended_option_tag" />
</org.thoughtcrime.securesms.loki.views.PNModeView>
<org.thoughtcrime.securesms.loki.views.PNModeView
android:id="@+id/backgroundPollingOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/small_font_size"
android:textColor="@color/accent"
android:textStyle="bold"
android:text="@string/activity_pn_mode_recommended_option_tag" />
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
</org.thoughtcrime.securesms.loki.views.PNModeView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="Slow Mode" />
<org.thoughtcrime.securesms.loki.views.PNModeView
android:id="@+id/backgroundPollingOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="Session will occasionally check for new messages in the background. Full metadata protection is guaranteed, but message notifications will be unreliable." />
<TextView
</org.thoughtcrime.securesms.loki.views.PNModeView>
<Button
style="@style/Widget.Session.Button.Common.ProminentFilled"
android:id="@+id/registerButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="Slow Mode" />
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginTop="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/onboarding_button_bottom_offset"
android:text="@string/continue_2" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="Session will occasionally check for new messages in the background. Full metadata protection is guaranteed, but message notifications will be unreliable." />
</LinearLayout>
</org.thoughtcrime.securesms.loki.views.PNModeView>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
style="@style/Widget.Session.Button.Common.ProminentFilled"
android:id="@+id/registerButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/onboarding_button_bottom_offset"
android:text="@string/continue_2" />
</LinearLayout>
</ScrollView>

View File

@ -30,7 +30,7 @@
android:textColor="@color/text"
android:text="@string/activity_register_explanation" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofTextView
<TextView
style="@style/SessionIDTextView"
android:id="@+id/publicKeyTextView"
android:layout_width="match_parent"
@ -54,7 +54,7 @@
android:layout_marginRight="@dimen/massive_spacing"
android:text="@string/continue_2" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofButton
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/copyButton"
android:layout_width="match_parent"

View File

@ -36,7 +36,7 @@
android:textColor="@color/text"
android:text="@string/activity_seed_explanation" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofTextView
<TextView
style="@style/SessionIDTextView"
android:id="@+id/seedTextView"
android:layout_width="match_parent"
@ -49,7 +49,7 @@
android:textAlignment="center"
android:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofTextView
<TextView
android:id="@+id/revealButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -65,7 +65,7 @@
android:layout_height="0dp"
android:layout_weight="1"/>
<org.thoughtcrime.securesms.loki.views.TapJackingProofButton
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/copyButton"
android:layout_width="196dp"

View File

@ -50,7 +50,7 @@
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/large_spacing" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofTextView
<TextView
android:id="@+id/publicKeyTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -71,7 +71,7 @@
android:layout_marginRight="@dimen/large_spacing"
android:orientation="horizontal">
<org.thoughtcrime.securesms.loki.views.TapJackingProofButton
<Button
style="@style/Widget.Session.Button.Common.UnimportantFilled"
android:id="@+id/copyButton"
android:layout_width="0dp"
@ -79,7 +79,7 @@
android:layout_weight="1"
android:text="@string/copy" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofButton
<Button
style="@style/Widget.Session.Button.Common.UnimportantFilled"
android:id="@+id/shareButton"
android:layout_width="0dp"

View File

@ -1,116 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
<LinearLayout
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="Message Notifications" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="There are two ways Session can notify you of new messages." />
<org.thoughtcrime.securesms.loki.views.PNModeView
android:id="@+id/fcmOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="Message Notifications" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="Fast Mode" />
android:text="There are two ways Session can notify you of new messages." />
<TextView
<org.thoughtcrime.securesms.loki.views.PNModeView
android:id="@+id/fcmOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="Youll be notified of new messages reliably and immediately using Googles notification servers. The contents of your messages, and who youre messaging, are never exposed to Google." />
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="Fast Mode" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="Youll be notified of new messages reliably and immediately using Googles notification servers. The contents of your messages, and who youre messaging, are never exposed to Google." />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/small_font_size"
android:textColor="@color/accent"
android:textStyle="bold"
android:text="@string/activity_pn_mode_recommended_option_tag" />
</org.thoughtcrime.securesms.loki.views.PNModeView>
<org.thoughtcrime.securesms.loki.views.PNModeView
android:id="@+id/backgroundPollingOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/small_font_size"
android:textColor="@color/accent"
android:textStyle="bold"
android:text="@string/activity_pn_mode_recommended_option_tag" />
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
</org.thoughtcrime.securesms.loki.views.PNModeView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="Slow Mode" />
<org.thoughtcrime.securesms.loki.views.PNModeView
android:id="@+id/backgroundPollingOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="Session will occasionally check for new messages in the background. Full metadata protection is guaranteed, but message notifications will be unreliable." />
<TextView
</org.thoughtcrime.securesms.loki.views.PNModeView>
<Button
style="@style/Widget.Session.Button.Common.ProminentFilled"
android:id="@+id/registerButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="Slow Mode" />
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginTop="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/onboarding_button_bottom_offset"
android:text="@string/continue_2" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="Session will occasionally check for new messages in the background. Full metadata protection is guaranteed, but message notifications will be unreliable." />
</LinearLayout>
</org.thoughtcrime.securesms.loki.views.PNModeView>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
style="@style/Widget.Session.Button.Common.ProminentFilled"
android:id="@+id/registerButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/onboarding_button_bottom_offset"
android:text="@string/continue_2" />
</LinearLayout>
</ScrollView>

View File

@ -30,7 +30,7 @@
android:textColor="@color/text"
android:text="@string/activity_register_explanation" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofTextView
<TextView
style="@style/SessionIDTextView"
android:id="@+id/publicKeyTextView"
android:layout_width="match_parent"
@ -55,7 +55,7 @@
android:layout_marginRight="@dimen/massive_spacing"
android:text="@string/continue_2" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofButton
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/copyButton"
android:layout_width="match_parent"

View File

@ -36,7 +36,7 @@
android:textColor="@color/text"
android:text="@string/activity_seed_explanation" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofTextView
<TextView
style="@style/SessionIDTextView"
android:id="@+id/seedTextView"
android:layout_width="match_parent"
@ -49,7 +49,7 @@
android:textAlignment="center"
android:text="nautical novelty populate onion awkward bent etiquette plant submarine itches vipers september axis maximum populate" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofTextView
<TextView
android:id="@+id/revealButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -66,7 +66,7 @@
android:layout_height="0dp"
android:layout_weight="1"/>
<org.thoughtcrime.securesms.loki.views.TapJackingProofButton
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/copyButton"
android:layout_width="196dp"

View File

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true">
<FrameLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center">
<LinearLayout android:paddingEnd="16dip"
android:paddingStart="16dip"
android:paddingTop="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="visible"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText android:id="@+id/old_passphrase"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:layout_marginBottom="10dip"
android:hint="@string/change_passphrase_activity__old_passphrase"
android:singleLine="true"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText android:id="@+id/new_passphrase"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:hint="@string/change_passphrase_activity__new_passphrase"
android:singleLine="true"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText android:id="@+id/repeat_passphrase"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:hint="@string/change_passphrase_activity__repeat_new_passphrase"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp">
<Button android:id="@+id/cancel_button"
android:text="@android:string/cancel"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="7dp"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<Button android:id="@+id/ok_button"
android:text="@android:string/ok"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</ScrollView>

View File

@ -146,13 +146,6 @@
android:inflatedId="@+id/group_share_profile_view"
android:layout="@layout/conversation_activity_group_share_profile_stub" />
<ViewStub
android:id="@+id/unverified_banner_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/unverified_banner"
android:layout="@layout/conversation_activity_unverified_banner_stub" />
<FrameLayout
android:id="@+id/fragment_content"
android:layout_width="match_parent"

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.identity.UnverifiedBannerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/unverified_banner"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/prompt_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/passphrase_edit"
style="?android:attr/progressBarStyleLargeInverse"
android:layout_width="75dp"
android:layout_height="75dp"
android:layout_centerInParent="true"
android:indeterminate="true"
android:padding="10dp"
android:visibility="gone"
tools:visibility="visible"/>
<ImageView
android:id="@+id/watermark"
android:layout_width="wrap_content"
android:layout_height="128dp"
android:layout_above="@id/passphrase_edit"
android:layout_centerInParent="true"
android:layout_marginBottom="20dp"
android:contentDescription="@string/PassphrasePromptActivity_watermark_content_description"
android:src="@drawable/ic_launcher_foreground" />
</RelativeLayout>

View File

@ -20,7 +20,7 @@
android:textAlignment="center"
android:textSize="@dimen/medium_font_size" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofTextView
<TextView
style="@style/SessionIDTextView"
android:id="@+id/seedTextView"
android:layout_width="wrap_content"
@ -56,7 +56,7 @@
android:layout_weight="1"
android:text="@string/cancel" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofButton
<Button
style="@style/Widget.Session.Button.Dialog.Unimportant"
android:id="@+id/copyButton"
android:layout_width="0dp"

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fab="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button android:id="@+id/continue_button"
android:layout_width="140sp"
android:layout_height="wrap_content"
android:text="@string/experience_upgrade_activity__continue"
android:visibility="invisible"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
<com.melnykov.fab.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="25dp"
android:src="@drawable/ic_arrow_forward_white_24dp"
android:focusable="true"
android:contentDescription="@string/conversation_list_fragment__fab_content_description"
fab:fab_shadow="false"
fab:fab_colorNormal="#33000000"
fab:fab_colorPressed="#66000000"
fab:fab_colorRipple="#66000000" />
</FrameLayout>

View File

@ -49,7 +49,7 @@
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/large_spacing" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofTextView
<TextView
android:id="@+id/publicKeyTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -70,7 +70,7 @@
android:layout_marginRight="@dimen/large_spacing"
android:orientation="horizontal">
<org.thoughtcrime.securesms.loki.views.TapJackingProofButton
<Button
style="@style/Widget.Session.Button.Common.UnimportantFilled"
android:id="@+id/copyButton"
android:layout_width="0dp"
@ -78,7 +78,7 @@
android:layout_weight="1"
android:text="@string/copy" />
<org.thoughtcrime.securesms.loki.views.TapJackingProofButton
<Button
style="@style/Widget.Session.Button.Common.UnimportantFilled"
android:id="@+id/shareButton"
android:layout_width="0dp"

View File

@ -73,73 +73,7 @@
android:id="@+id/lock_screen_auth_container"
android:layout_width="196dp"
android:layout_height="@dimen/medium_button_height"
android:text="Tap to Unlock" />
<RelativeLayout android:id="@+id/password_auth_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="60dp"
tools:visibility="visible">
<EditText android:id="@+id/passphrase_edit"
android:layout_width="match_parent"
android:layout_height="45sp"
android:inputType="textPassword"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
android:singleLine="true"
android:paddingStart="10dp"
android:paddingEnd="40dp"
tools:text="password"/>
<org.thoughtcrime.securesms.components.AnimatingToggle
android:id="@+id/button_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/passphrase_edit"
android:layout_centerVertical="true"
android:layout_gravity="center"
android:gravity="center">
<ImageButton android:id="@+id/passphrase_visibility"
android:src="?ic_visibility_on"
android:background="@drawable/touch_highlight_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:layout_centerVertical="true" />
<ImageButton android:id="@+id/passphrase_visibility_off"
android:src="?ic_visibility_off"
android:background="@drawable/touch_highlight_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:layout_centerVertical="true" />
</org.thoughtcrime.securesms.components.AnimatingToggle>
<ImageButton android:id="@+id/ok_button"
android:src="?ic_arrow_forward"
android:contentDescription="@string/PassphrasePromptActivity_ok_button_content_description"
android:background="@null"
android:text="@string/prompt_passphrase_activity__unlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:paddingStart="5dp"
android:paddingTop="5dp"
android:paddingEnd="10dp"
android:paddingBottom="5dp"/>
</RelativeLayout>
android:text="Tap to Unlock"/>
</LinearLayout>
</RelativeLayout>

View File

@ -1,188 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:tools="http://schemas.android.com/tools"
android:fillViewport="true">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:gravity="center_horizontal"
android:background="?verification_background"
android:orientation="vertical">
<FrameLayout android:layout_width="250dp"
android:layout_height="250dp">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="20sp"
android:text="@string/verify_display_fragment__loading"/>
<org.thoughtcrime.securesms.components.SquareImageView
android:id="@+id/qr_code"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:padding="20dp"
android:background="@drawable/qr_code_background"
android:visibility="invisible"
tools:src="@drawable/splash_logo"
tools:visibility="invisible"/>
<TextView android:id="@+id/tap_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="35dp"
android:textColor="@color/gray50"
android:textSize="11sp"
android:visibility="invisible"
android:text="@string/verify_display_fragment__tap_to_scan"/>
<org.thoughtcrime.securesms.components.SquareImageView
android:id="@+id/qr_verified"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:padding="20dp"
android:src="@drawable/ic_check_white_48dp"
android:background="@drawable/qr_code_background"
android:backgroundTint="@color/green_500"
android:visibility="gone"/>
</FrameLayout>
<TableLayout android:id="@+id/number_table"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:clickable="true"
android:focusable="true">
<TableRow android:gravity="center_horizontal"
android:clickable="false"
android:focusable="false">
<TextView android:id="@+id/code_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/IdentityKey"
tools:text="22934"/>
<TextView android:id="@+id/code_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
style="@style/IdentityKey"
tools:text="56944"/>
<TextView android:id="@+id/code_third"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
style="@style/IdentityKey"
tools:text="42738"/>
<TextView android:id="@+id/code_fourth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
style="@style/IdentityKey"
tools:text="20038"/>
</TableRow>
<TableRow android:gravity="center_horizontal">
<TextView android:id="@+id/code_fifth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/IdentityKey"
tools:text="34431"/>
<TextView android:id="@+id/code_sixth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
style="@style/IdentityKey"
tools:text="24922"/>
<TextView android:id="@+id/code_seventh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
style="@style/IdentityKey"
tools:text="58594"/>
<TextView android:id="@+id/code_eighth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
style="@style/IdentityKey"
tools:text="24109"/>
</TableRow>
<TableRow android:gravity="center_horizontal">
<TextView android:id="@+id/code_ninth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/IdentityKey"
tools:text="00257"/>
<TextView android:id="@+id/code_tenth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
style="@style/IdentityKey"
tools:text="34956"/>
<TextView android:id="@+id/code_eleventh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
style="@style/IdentityKey"
tools:text="32440"/>
<TextView android:id="@+id/code_twelth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
style="@style/IdentityKey"
tools:text="15774"/>
</TableRow>
</TableLayout>
<LinearLayout android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:paddingStart="20dp">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/verified_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_marginStart="5dp"
android:textSize="17dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/verify_display_fragment__verified"/>
</LinearLayout>
<TextView android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:textSize="17sp"
android:lineSpacingExtra="3sp"
android:text="@string/verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s"/>
</LinearLayout>
</ScrollView>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/preferences__submit_debug_log"
android:id="@+id/menu_submit_debug_logs"
android:icon="@android:drawable/ic_menu_upload" />
</menu>

View File

@ -15,29 +15,29 @@
android:key="pref_android_screen_lock_timeout"
android:dependency="pref_android_screen_lock" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:key="pref_enable_passphrase_temporary"
android:defaultValue="true"
android:title="@string/preferences__enable_passphrase"
android:summary="@string/preferences__lock_signal_and_message_notifications_with_a_passphrase" />
<!-- <org.thoughtcrime.securesms.components.SwitchPreferenceCompat-->
<!-- android:key="pref_enable_passphrase_temporary"-->
<!-- android:defaultValue="true"-->
<!-- android:title="@string/preferences__enable_passphrase"-->
<!-- android:summary="@string/preferences__lock_signal_and_message_notifications_with_a_passphrase" />-->
<Preference
android:key="pref_change_passphrase"
android:title="@string/preferences__change_passphrase"
android:summary="@string/preferences__change_your_passphrase"
android:dependency="pref_enable_passphrase_temporary" />
<!-- <Preference-->
<!-- android:key="pref_change_passphrase"-->
<!-- android:title="@string/preferences__change_passphrase"-->
<!-- android:summary="@string/preferences__change_your_passphrase"-->
<!-- android:dependency="pref_enable_passphrase_temporary" />-->
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_timeout_passphrase"
android:title="@string/preferences__inactivity_timeout_passphrase"
android:summary="@string/preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity"
android:dependency="pref_enable_passphrase_temporary" />
<!-- <org.thoughtcrime.securesms.components.SwitchPreferenceCompat-->
<!-- android:defaultValue="false"-->
<!-- android:key="pref_timeout_passphrase"-->
<!-- android:title="@string/preferences__inactivity_timeout_passphrase"-->
<!-- android:summary="@string/preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity"-->
<!-- android:dependency="pref_enable_passphrase_temporary" />-->
<Preference
android:title="@string/preferences__inactivity_timeout_interval"
android:key="pref_timeout_interval"
android:dependency="pref_timeout_passphrase" />
<!-- <Preference-->
<!-- android:title="@string/preferences__inactivity_timeout_interval"-->
<!-- android:key="pref_timeout_interval"-->
<!-- android:dependency="pref_timeout_passphrase" />-->
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="true"

View File

@ -1,14 +1,28 @@
package org.session.libsession.database
import org.session.libsession.database.dto.DatabaseAttachmentDTO
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentPointer
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
interface MessageDataProvider {
fun getAttachment(uniqueID: String): DatabaseAttachmentDTO?
//fun getAttachment(attachmentId: Long): SignalServiceAttachmentStream?
fun getAttachmentPointer(attachmentID: String): SignalServiceAttachmentPointer?
fun getMessageID(serverID: Long): Long?
fun deleteMessage(messageID: Long)
fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream?
fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer?
fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long)
fun isOutgoingMessage(timestamp: Long): Boolean
@Throws(Exception::class)
fun uploadAttachment(attachmentId: Long)
}

View File

@ -1,73 +0,0 @@
package org.session.libsession.database.dto
import android.util.Size
import com.google.protobuf.ByteString
import org.session.libsignal.service.internal.push.SignalServiceProtos
import kotlin.math.round
class DatabaseAttachmentDTO {
var contentType: String? = null
var fileName: String? = null
var url: String? = null
var caption: String? = null
var size: Int = 0
var key: ByteString? = null
var digest: ByteString? = null
var flags: Int = 0
var width: Int = 0
var height: Int = 0
val isVoiceNote: Boolean = false
var shouldHaveImageSize: Boolean = false
val isUploaded: Boolean = false
fun toProto(): SignalServiceProtos.AttachmentPointer? {
val builder = SignalServiceProtos.AttachmentPointer.newBuilder()
builder.contentType = this.contentType
if (!this.fileName.isNullOrEmpty()) {
builder.fileName = this.fileName
}
if (!this.caption.isNullOrEmpty()) {
builder.caption = this.caption
}
builder.size = this.size
builder.key = this.key
builder.digest = this.digest
builder.flags = if (this.isVoiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0
//TODO I did copy the behavior of iOS below, not sure if that's relevant here...
if (this.shouldHaveImageSize) {
if (this.width < Int.MAX_VALUE && this.height < Int.MAX_VALUE) {
val imageSize= Size(this.width, this.height)
val imageWidth = round(imageSize.width.toDouble())
val imageHeight = round(imageSize.height.toDouble())
if (imageWidth > 0 && imageHeight > 0) {
builder.width = imageWidth.toInt()
builder.height = imageHeight.toInt()
}
}
}
builder.url = this.url
try {
return builder.build()
} catch (e: Exception) {
return null
}
}
}

View File

@ -38,7 +38,7 @@ interface StorageProtocol {
fun markJobAsSucceeded(job: Job)
fun markJobAsFailed(job: Job)
fun getAllPendingJobs(type: String): List<Job>
fun getAttachmentUploadJob(attachmentID: String): AttachmentUploadJob?
fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob?
fun getMessageSendJob(messageSendJobID: String): MessageSendJob?
fun resumeMessageSendJobIfNeeded(messageSendJobID: String)
fun isJobCanceled(job: Job): Boolean

View File

@ -4,12 +4,12 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map
import okhttp3.Request
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.libsignal.util.Hex
import org.session.libsignal.service.internal.util.Base64
import org.session.libsignal.service.internal.util.JsonUtil
import org.session.libsignal.service.loki.api.SnodeAPI
import org.session.libsignal.service.loki.api.LokiDotNetAPI
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
@ -18,7 +18,7 @@ import java.net.URL
import java.util.concurrent.ConcurrentHashMap
import kotlin.collections.set
class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : LokiDotNetAPI(userPublicKey, userPrivateKey, database) {
class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : DotNetAPI() {
companion object {
// region Settings
@ -49,6 +49,7 @@ class FileServerAPI(public val server: String, userPublicKey: String, userPrivat
* possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds.
*/
public val fileSizeORMultiplier = 2 // TODO: It should be possible to set this to 1.5?
val server = "https://file.getsession.org"
public val fileStorageBucketURL = "https://file-static.lokinet.org"
// endregion

View File

@ -1,17 +1,94 @@
package org.session.libsession.messaging.jobs
class AttachmentUploadJob : Job {
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.fileserver.FileServerAPI
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.PushAttachmentData
import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory
import org.session.libsignal.service.internal.util.Util
import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory
class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job {
override var delegate: JobDelegate? = null
override var id: String? = null
override var failureCount: Int = 0
// Error
internal sealed class Error(val description: String) : Exception() {
object NoAttachment : Error("No such attachment.")
}
// Settings
override val maxFailureCount: Int = 20
companion object {
val TAG = AttachmentUploadJob::class.qualifiedName
val collection: String = "AttachmentUploadJobCollection"
val maxFailureCount: Int = 20
}
override fun execute() {
TODO("Not yet implemented")
try {
val attachmentStream = MessagingConfiguration.shared.messageDataProvider.getAttachmentStream(attachmentID)
?: return handleFailure(Error.NoAttachment)
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)
val server = openGroup?.server ?: FileServerAPI.server
//TODO add some encryption stuff here
val isEncryptionRequired = false
//val isEncryptionRequired = (server == FileServerAPI.server)
val attachmentKey = Util.getSecretBytes(64)
val outputStreamFactory = if (isEncryptionRequired) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory()
val ciphertextLength = attachmentStream.length
val attachmentData = PushAttachmentData(attachmentStream.contentType, attachmentStream.inputStream, ciphertextLength, outputStreamFactory, attachmentStream.listener)
FileServerAPI.shared.uploadAttachment(server, attachmentData)
} catch (e: java.lang.Exception) {
if (e is Error && e == Error.NoAttachment) {
this.handlePermanentFailure(e)
} else if (e is DotNetAPI.Error && !e.isRetryable) {
this.handlePermanentFailure(e)
} else {
this.handleFailure(e)
}
}
}
private fun handleSuccess() {
Log.w(TAG, "Attachment uploaded successfully.")
delegate?.handleJobSucceeded(this)
MessagingConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
//TODO interaction stuff, not sure how to deal with that
}
private fun handlePermanentFailure(e: Exception) {
Log.w(TAG, "Attachment upload failed permanently due to error: $this.")
delegate?.handleJobFailedPermanently(this, e)
failAssociatedMessageSendJob(e)
}
private fun handleFailure(e: Exception) {
Log.w(TAG, "Attachment upload failed due to error: $this.")
delegate?.handleJobFailed(this, e)
if (failureCount + 1 == AttachmentUploadJob.maxFailureCount) {
failAssociatedMessageSendJob(e)
}
}
private fun failAssociatedMessageSendJob(e: Exception) {
val storage = MessagingConfiguration.shared.storage
val messageSendJob = storage.getMessageSendJob(messageSendJobID)
MessageSender.handleFailedMessageSend(this.message!!, e)
if (messageSendJob != null) {
storage.markJobAsFailed(messageSendJob)
}
}
}

View File

@ -1,8 +1,13 @@
package org.session.libsession.messaging.jobs
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.deferred
import org.session.libsession.messaging.sending_receiving.MessageReceiver
import org.session.libsession.messaging.sending_receiving.handle
import org.session.libsignal.libsignal.logging.Log
class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job {
override var delegate: JobDelegate? = null
override var id: String? = null
override var failureCount: Int = 0
@ -10,6 +15,8 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
// Settings
override val maxFailureCount: Int = 10
companion object {
val TAG = MessageReceiveJob::class.qualifiedName
val collection: String = "MessageReceiveJobCollection"
}
@ -18,6 +25,33 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
}
fun executeAsync(): Promise<Unit, Exception> {
TODO("Not yet implemented")
val deferred = deferred<Unit, Exception>()
try {
val (message, proto) = MessageReceiver.parse(this.data, this.openGroupMessageServerID)
MessageReceiver.handle(message, proto, this.openGroupID)
this.handleSuccess()
deferred.resolve(Unit)
} catch (e: Exception) {
Log.d(TAG, "Couldn't receive message due to error: $e.")
val error = e as? MessageReceiver.Error
error?.let {
if (!error.isRetryable) this.handlePermanentFailure(error)
}
this.handleFailure(e)
deferred.resolve(Unit) // The promise is just used to keep track of when we're done
}
return deferred.promise
}
private fun handleSuccess() {
delegate?.handleJobSucceeded(this)
}
private fun handlePermanentFailure(e: Exception) {
delegate?.handleJobFailedPermanently(this, e)
}
private fun handleFailure(e: Exception) {
delegate?.handleJobFailed(this, e)
}
}

View File

@ -1,9 +1,15 @@
package org.session.libsession.messaging.jobs
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
class MessageSendJob(val message: Message, val destination: Destination) : Job {
override var delegate: JobDelegate? = null
override var id: String? = null
override var failureCount: Int = 0
@ -11,10 +17,54 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
// Settings
override val maxFailureCount: Int = 10
companion object {
val TAG = MessageSendJob::class.qualifiedName
val collection: String = "MessageSendJobCollection"
}
override fun execute() {
TODO("Not yet implemented")
val messageDataProvider = MessagingConfiguration.shared.messageDataProvider
val message = message as? VisibleMessage
message?.let {
if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
val attachments = message.attachmentIDs.map { messageDataProvider.getAttachmentStream(it) }.filterNotNull()
val attachmentsToUpload = attachments.filter { !it.isUploaded }
attachmentsToUpload.forEach {
if(MessagingConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId) != null) {
// Wait for it to finish
} else {
val job = AttachmentUploadJob(it.attachmentId, message.threadID!!, message, id!!)
JobQueue.shared.add(job)
}
}
if (attachmentsToUpload.isNotEmpty()) return // Wait for all attachments to upload before continuing
}
MessageSender.send(this.message, this.destination).success {
this.handleSuccess()
}.fail { exception ->
Log.e(TAG, "Couldn't send message due to error: $exception.")
val e = exception as? MessageSender.Error
e?.let {
if (!e.isRetryable) this.handlePermanentFailure(e)
}
this.handleFailure(exception)
}
}
private fun handleSuccess() {
delegate?.handleJobSucceeded(this)
}
private fun handlePermanentFailure(error: Exception) {
delegate?.handleJobFailedPermanently(this, error)
}
private fun handleFailure(error: Exception) {
Log.w(TAG, "Failed to send $message::class.simpleName.")
val message = message as? VisibleMessage
message?.let {
if(!MessagingConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
}
delegate?.handleJobFailed(this, error)
}
}

View File

@ -8,7 +8,7 @@ class LinkPreview() {
var title: String? = null
var url: String? = null
var attachmentID: String? = null
var attachmentID: Long? = 0
companion object {
const val TAG = "LinkPreview"
@ -21,7 +21,7 @@ class LinkPreview() {
}
//constructor
internal constructor(title: String?, url: String, attachmentID: String?) : this() {
internal constructor(title: String?, url: String, attachmentID: Long?) : this() {
this.title = title
this.url = url
this.attachmentID = attachmentID
@ -44,7 +44,7 @@ class LinkPreview() {
title?.let { linkPreviewProto.title = title }
val attachmentID = attachmentID
attachmentID?.let {
val attachmentProto = MessagingConfiguration.shared.messageDataProvider.getAttachment(attachmentID)
val attachmentProto = MessagingConfiguration.shared.messageDataProvider.getAttachmentStream(attachmentID)
attachmentProto?.let { linkPreviewProto.image = attachmentProto.toProto() }
}
// Build

View File

@ -11,7 +11,7 @@ class Quote() {
var timestamp: Long? = 0
var publicKey: String? = null
var text: String? = null
var attachmentID: String? = null
var attachmentID: Long? = null
companion object {
const val TAG = "Quote"
@ -25,7 +25,7 @@ class Quote() {
}
//constructor
internal constructor(timestamp: Long, publicKey: String, text: String?, attachmentID: String?) : this() {
internal constructor(timestamp: Long, publicKey: String, text: String?, attachmentID: Long?) : this() {
this.timestamp = timestamp
this.publicKey = publicKey
this.text = text
@ -60,7 +60,7 @@ class Quote() {
private fun addAttachmentsIfNeeded(quoteProto: SignalServiceProtos.DataMessage.Quote.Builder, messageDataProvider: MessageDataProvider) {
val attachmentID = attachmentID ?: return
val attachmentProto = messageDataProvider.getAttachment(attachmentID)
val attachmentProto = messageDataProvider.getAttachmentStream(attachmentID)
if (attachmentProto == null) {
Log.w(TAG, "Ignoring invalid attachment for quoted message.")
return
@ -74,7 +74,7 @@ class Quote() {
}
val quotedAttachmentProto = SignalServiceProtos.DataMessage.Quote.QuotedAttachment.newBuilder()
quotedAttachmentProto.contentType = attachmentProto.contentType
val fileName = attachmentProto.fileName
val fileName = attachmentProto.fileName?.get()
fileName?.let { quotedAttachmentProto.fileName = fileName }
quotedAttachmentProto.thumbnail = attachmentProto.toProto()
try {

View File

@ -11,7 +11,7 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
class VisibleMessage : Message() {
var text: String? = null
var attachmentIDs = ArrayList<String>()
var attachmentIDs = ArrayList<Long>()
var quote: Quote? = null
var linkPreview: LinkPreview? = null
var contact: Contact? = null
@ -90,7 +90,7 @@ class VisibleMessage : Message() {
}
}
//Attachments
val attachments = attachmentIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getAttachment(it) }
val attachments = attachmentIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getAttachmentStream(it) }
if (!attachments.all { it.isUploaded }) {
if (BuildConfig.DEBUG) {
//TODO equivalent to iOS's preconditionFailure

View File

@ -0,0 +1,123 @@
package org.session.libsession.messaging.sending_receiving.attachments
import com.google.protobuf.ByteString
import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.messages.SignalServiceAttachment
import java.io.InputStream
abstract class SessionServiceAttachment protected constructor(val contentType: String?) {
var attachmentId: Long = 0
var isGif: Boolean = false
var isImage: Boolean = false
var isVideo: Boolean = false
var isAudio: Boolean = false
var url: String = ""
var key: ByteString? = null
abstract fun isStream(): Boolean
abstract fun isPointer(): Boolean
fun asStream(): SessionServiceAttachmentStream {
return this as SessionServiceAttachmentStream
}
fun asPointer(): SessionServiceAttachmentPointer {
return this as SessionServiceAttachmentPointer
}
fun shouldHaveImageSize(): Boolean {
return (isVideo || isImage || isGif);
}
class Builder internal constructor() {
private var inputStream: InputStream? = null
private var contentType: String? = null
private var fileName: String? = null
private var length: Long = 0
private var listener: SignalServiceAttachment.ProgressListener? = null
private var voiceNote = false
private var width = 0
private var height = 0
private var caption: String? = null
fun withStream(inputStream: InputStream?): Builder {
this.inputStream = inputStream
return this
}
fun withContentType(contentType: String?): Builder {
this.contentType = contentType
return this
}
fun withLength(length: Long): Builder {
this.length = length
return this
}
fun withFileName(fileName: String?): Builder {
this.fileName = fileName
return this
}
fun withListener(listener: SignalServiceAttachment.ProgressListener?): Builder {
this.listener = listener
return this
}
fun withVoiceNote(voiceNote: Boolean): Builder {
this.voiceNote = voiceNote
return this
}
fun withWidth(width: Int): Builder {
this.width = width
return this
}
fun withHeight(height: Int): Builder {
this.height = height
return this
}
fun withCaption(caption: String?): Builder {
this.caption = caption
return this
}
fun build(): SessionServiceAttachmentStream {
requireNotNull(inputStream) { "Must specify stream!" }
requireNotNull(contentType) { "No content type specified!" }
require(length != 0L) { "No length specified!" }
return SessionServiceAttachmentStream(inputStream, contentType, length, Optional.fromNullable(fileName), voiceNote, Optional.absent(), width, height, Optional.fromNullable(caption), listener)
}
}
/**
* An interface to receive progress information on upload/download of
* an attachment.
*/
/*interface ProgressListener {
/**
* Called on a progress change event.
*
* @param total The total amount to transmit/receive in bytes.
* @param progress The amount that has been transmitted/received in bytes thus far
*/
fun onAttachmentProgress(total: Long, progress: Long)
}*/
companion object {
@JvmStatic
fun newStreamBuilder(): Builder {
return Builder()
}
}
}
// matches values in AttachmentDatabase.java
enum class AttachmentState(val value: Int) {
DONE(0),
STARTED(1),
PENDING(2),
FAILED(3)
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2014-2017 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsession.messaging.sending_receiving.attachments
import org.session.libsignal.libsignal.util.guava.Optional
/**
* Represents a received SignalServiceAttachment "handle." This
* is a pointer to the actual attachment content, which needs to be
* retrieved using [SignalServiceMessageReceiver.retrieveAttachment]
*
* @author Moxie Marlinspike
*/
class SessionServiceAttachmentPointer(val id: Long, contentType: String?, key: ByteArray?,
val size: Optional<Int>, val preview: Optional<ByteArray>,
val width: Int, val height: Int,
val digest: Optional<ByteArray>, val fileName: Optional<String>,
val voiceNote: Boolean, val caption: Optional<String>, url: String) : SessionServiceAttachment(contentType) {
override fun isStream(): Boolean {
return false
}
override fun isPointer(): Boolean {
return true
}
}

Some files were not shown because too many files have changed in this diff Show More