Merge pull request #451 from RyanRory/refactor_clean_0

The Refactor: Clean Step 0
pull/461/head
Niels Andriesse 4 years ago committed by GitHub
commit 6b146c762e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -307,16 +307,6 @@
android:screenOrientation="portrait"
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="org.thoughtcrime.securesms.stickers.StickerManagementActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name="org.thoughtcrime.securesms.LogSubmitActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:label="@string/AndroidManifest__log_submit"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="org.thoughtcrime.securesms.MediaPreviewActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@ -351,17 +341,6 @@
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat" />
<activity
android:name="org.thoughtcrime.securesms.ClearProfileAvatarActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:icon="@drawable/clear_profile_avatar"
android:label="@string/AndroidManifest_remove_photo"
android:theme="@style/Theme.AppCompat.Dialog.Alert">
<intent-filter>
<action android:name="network.loki.securesms.action.CLEAR_PROFILE_PHOTO" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="org.thoughtcrime.securesms.ShortcutLauncherActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@ -375,16 +354,10 @@
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name="org.thoughtcrime.securesms.service.ApplicationMigrationService"
android:enabled="true" />
<service
android:name="org.thoughtcrime.securesms.service.KeyCachingService"
android:enabled="true"
android:exported="false" />
<service
android:name="org.thoughtcrime.securesms.service.IncomingMessageObserver$ForegroundService"
android:enabled="true" />
<service
android:name="org.thoughtcrime.securesms.service.DirectShareService"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
@ -430,11 +403,6 @@
android:authorities="network.loki.provider.securesms"
android:exported="false"
android:grantUriPermissions="true" />
<provider
android:name="org.thoughtcrime.securesms.providers.MmsBodyProvider"
android:authorities="network.loki.provider.securesms.mms"
android:exported="false"
android:grantUriPermissions="true" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="network.loki.securesms.fileprovider"
@ -491,16 +459,6 @@
<action android:name="network.loki.securesms.DELETE_NOTIFICATION" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.ExperienceUpgradeActivity$AppUpgradeReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<data android:scheme="package" />
</intent-filter>
<intent-filter>
<action android:name="network.loki.securesms.ExperienceUpgradeActivity.DISMISS_ACTION" />
</intent-filter>
</receiver>
<receiver
android:name="org.thoughtcrime.securesms.service.PanicResponderListener"
android:exported="true">

@ -32,7 +32,6 @@ import androidx.multidex.MultiDexApplication;
import com.google.firebase.iid.FirebaseInstanceId;
import org.conscrypt.Conscrypt;
import org.jetbrains.annotations.NotNull;
import org.session.libsession.messaging.MessagingConfiguration;
import org.session.libsession.messaging.avatars.AvatarHelper;
import org.session.libsession.utilities.SSKEnvironment;
@ -49,7 +48,6 @@ import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.session.libsession.utilities.preferences.ProfileKeyUtil;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -62,7 +60,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
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.logging.AndroidLogger;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.logging.PersistentLogger;
@ -75,20 +72,14 @@ import org.thoughtcrime.securesms.loki.api.PublicChatManager;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
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.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
import org.thoughtcrime.securesms.loki.utilities.Broadcaster;
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities;
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.session.libsession.messaging.threads.recipients.Recipient;
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.UpdateApkRefreshListener;
@ -106,17 +97,8 @@ import org.session.libsignal.service.loki.api.SnodeAPI;
import org.session.libsignal.service.loki.api.SwarmAPI;
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import org.session.libsignal.service.loki.api.shelved.p2p.LokiP2PAPI;
import org.session.libsignal.service.loki.api.shelved.p2p.LokiP2PAPIDelegate;
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysImplementation;
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.sessionmanagement.SessionManagementProtocol;
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocolDelegate;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink;
import org.session.libsignal.service.loki.protocol.shelved.syncmessages.SyncMessagesProtocol;
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
import java.io.File;
import java.io.FileInputStream;
@ -141,11 +123,11 @@ import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
*
* @author Moxie Marlinspike
*/
public class ApplicationContext extends MultiDexApplication implements DependencyInjector, DefaultLifecycleObserver, LokiP2PAPIDelegate,
SessionManagementProtocolDelegate, SharedSenderKeysImplementationDelegate {
public class ApplicationContext extends MultiDexApplication implements DependencyInjector, DefaultLifecycleObserver {
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
private static final String TAG = ApplicationContext.class.getSimpleName();
private final static int OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10 MB
private ExpiringMessageManager expiringMessageManager;
private TypingStatusRepository typingStatusRepository;
@ -153,7 +135,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private JobManager jobManager;
private ReadReceiptManager readReceiptManager;
private ProfileManager profileManager;
private IncomingMessageObserver incomingMessageObserver;
private ObjectGraph objectGraph;
private PersistentLogger persistentLogger;
@ -190,32 +171,18 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
SharedSenderKeysDatabase sskDatabase = DatabaseFactory.getSSKDatabase(this);
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
SessionResetImplementation sessionResetImpl = new SessionResetImplementation(this);
SharedSenderKeysImplementation.Companion.configureIfNeeded(sskDatabase, this);
MessagingConfiguration.Companion.configure(this,
DatabaseFactory.getStorage(this),
sskDatabase,
DatabaseFactory.getAttachmentProvider(this),
new SessionProtocolImpl(this));
if (userPublicKey != null) {
SwarmAPI.Companion.configureIfNeeded(apiDB);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
SessionMetaProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
SyncMessagesProtocol.Companion.configureIfNeeded(apiDB, userPublicKey);
}
org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol.Companion.configureIfNeeded(apiDB);
SessionManagementProtocol.Companion.configureIfNeeded(sessionResetImpl, sskDatabase, this);
setUpP2PAPIIfNeeded();
PushNotificationAPI.Companion.configureIfNeeded(BuildConfig.DEBUG);
if (setUpStorageAPIIfNeeded()) {
if (userPublicKey != null) {
Set<DeviceLink> deviceLinks = DatabaseFactory.getLokiAPIDatabase(this).getDeviceLinks(userPublicKey);
FileServerAPI.shared.setDeviceLinks(deviceLinks);
}
}
setUpStorageAPIIfNeeded();
resubmitProfilePictureIfNeeded();
publicChatManager = new PublicChatManager(this);
updateOpenGroupProfilePicturesIfNeeded();
@ -226,7 +193,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
UiModeUtilities.setupUiModeToUserSelected(this);
// ========
initializeJobManager();
initializeMessageRetrieval();
initializeExpiringMessageManager();
initializeTypingStatusRepository();
initializeTypingStatusSender();
@ -234,7 +200,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
initializeProfileManager();
initializePeriodicTasks();
initializeWebRtc();
initializePendingMessages();
initializeBlobProvider();
SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager());
}
@ -367,12 +332,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
.build());
}
public void initializeMessageRetrieval() {
this.incomingMessageObserver = new IncomingMessageObserver(this);
}
private void initializeDependencyInjection() {
communicationModule = new SignalCommunicationModule(this, new SignalServiceNetworkAccess(this));
communicationModule = new SignalCommunicationModule(this);
this.objectGraph = ObjectGraph.create(communicationModule);
}
@ -441,14 +402,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
}
private void initializePendingMessages() {
if (TextSecurePreferences.getNeedsMessagePull(this)) {
Log.i(TAG, "Scheduling a message fetch.");
ApplicationContext.getInstance(this).getJobManager().add(new PushNotificationReceiveJob(this));
TextSecurePreferences.setNeedsMessagePull(this, false);
}
}
private void initializeBlobProvider() {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
BlobProvider.getInstance().onSessionStart(this);
@ -467,22 +420,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
public boolean setUpStorageAPIIfNeeded() {
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null || !IdentityKeyUtil.hasIdentityKey(this)) { return false; }
boolean isDebugMode = BuildConfig.DEBUG;
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this);
FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB);
return true;
}
public void setUpP2PAPIIfNeeded() {
String hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
if (hexEncodedPublicKey == null) { return; }
LokiP2PAPI.Companion.configure(hexEncodedPublicKey, (isOnline, contactPublicKey) -> {
// TODO: Implement
return null;
}, this);
}
public void registerForFCMIfNeeded(Boolean force) {
Context context = this;
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> {
@ -501,11 +444,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
});
}
@Override
public void ping(@NotNull String s) {
// TODO: Implement
}
private void setUpPollingIfNeeded() {
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return;
@ -524,8 +462,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}
return Unit.INSTANCE;
});
SharedSenderKeysDatabase sskDatabase = DatabaseFactory.getSSKDatabase(this);
ClosedGroupPoller.Companion.configureIfNeeded(this, sskDatabase);
ClosedGroupPoller.Companion.configureIfNeeded(this);
closedGroupPoller = ClosedGroupPoller.Companion.getShared();
}
@ -551,7 +488,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
String encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this);
byte[] profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey);
try {
File profilePicture = AvatarHelper.getAvatarFile(this, Address.Companion.fromSerialized(userPublicKey));
File profilePicture = AvatarHelper.getAvatarFile(this, Address.fromSerialized(userPublicKey));
StreamDetails stream = new StreamDetails(new FileInputStream(profilePicture), "image/jpeg", profilePicture.length());
FileServerAPI.shared.uploadProfilePicture(FileServerAPI.shared.getServer(), profileKey, stream, () -> {
TextSecurePreferences.setLastProfilePictureUpload(this, new Date().getTime());
@ -576,12 +513,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
if (publicChatAPI == null) { return; }
byte[] profileKey = ProfileKeyUtil.getProfileKey(this);
String url = TextSecurePreferences.getProfilePictureURL(this);
String userMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(this);
if (userMasterDevice != null) {
Recipient userMasterDeviceAsRecipient = Recipient.from(this, Address.Companion.fromSerialized(userMasterDevice), false).resolve();
profileKey = userMasterDeviceAsRecipient.getProfileKey();
url = userMasterDeviceAsRecipient.getProfileAvatar();
}
Set<String> servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers();
for (String server : servers) {
if (profileKey != null) {
@ -604,7 +535,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
TextSecurePreferences.setIsUsingFCM(this, isUsingFCM);
TextSecurePreferences.setProfileName(this, displayName);
}
MasterSecretUtil.clear(this);
getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
if (!deleteDatabase("signal.db")) {
Log.d("Loki", "Failed to delete database.");
}
@ -617,25 +548,5 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
Runtime.getRuntime().exit(0);
}
// public boolean hasSentSessionRequestExpired(@NotNull String publicKey) {
// LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
// Long timestamp = apiDB.getSessionRequestSentTimestamp(publicKey);
// if (timestamp != null) {
// long expiration = timestamp + TTLUtilities.getTTL(TTLUtilities.MessageType.SessionRequest);
// return new Date().getTime() > expiration;
// } else {
// return false;
// }
// }
@Override
public void sendSessionRequestIfNeeded(@NotNull String publicKey) {
}
@Override
public void requestSenderKey(@NotNull String groupPublicKey, @NotNull String senderPublicKey) {
ClosedGroupsProtocol.requestSenderKey(this, groupPublicKey, senderPublicKey);
}
// endregion
}

@ -9,7 +9,6 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsession.messaging.sending_receiving.attachments.StickerLocator;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient;
@ -36,6 +35,5 @@ public interface BindableConversationItem extends Unbindable {
void onQuoteClicked(MmsMessageRecord messageRecord);
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms);
void onStickerClicked(@NonNull StickerLocator stickerLocator);
}
}

@ -1,17 +0,0 @@
package org.thoughtcrime.securesms;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import java.util.Locale;
import java.util.Set;
public interface BindableConversationListItem extends Unbindable {
public void bind(@NonNull ThreadRecord thread,
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
@NonNull Set<Long> typingThreads,
@NonNull Set<Long> selectedThreads, boolean batchMode);
}

@ -1,28 +0,0 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Intent;
import androidx.appcompat.app.AlertDialog;
import network.loki.messenger.R;
public class ClearProfileAvatarActivity extends Activity {
@Override
public void onResume() {
super.onResume();
new AlertDialog.Builder(this)
.setTitle(R.string.ClearProfileActivity_remove_profile_photo)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> finish())
.setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> {
Intent result = new Intent();
result.putExtra("delete", true);
setResult(Activity.RESULT_OK, result);
finish();
})
.show();
}
}

@ -48,106 +48,13 @@ 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
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);
}};
// private MasterSecret masterSecret;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
// this.masterSecret = KeyCachingService.getMasterSecret(this);
if (needsUpgradeTask()) {
Log.i("DatabaseUpgradeActivity", "Upgrading...");
setContentView(R.layout.database_upgrade_activity);
ProgressBar indeterminateProgress = findViewById(R.id.indeterminate_progress);
ProgressBar determinateProgress = findViewById(R.id.determinate_progress);
new DatabaseUpgradeTask(indeterminateProgress, determinateProgress)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, VersionTracker.getLastSeenVersion(this));
} else {
VersionTracker.updateLastSeenVersion(this);
updateNotifications(this);
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
finish();
}
}
private boolean needsUpgradeTask() {
int currentVersionCode = Util.getCanonicalVersionCode();
int lastSeenVersion = VersionTracker.getLastSeenVersion(this);
Log.i("DatabaseUpgradeActivity", "LastSeenVersion: " + lastSeenVersion);
if (lastSeenVersion >= currentVersionCode)
return false;
for (int version : UPGRADE_VERSIONS) {
Log.i("DatabaseUpgradeActivity", "Comparing: " + version);
if (lastSeenVersion < version)
return true;
}
return false;
VersionTracker.updateLastSeenVersion(this);
updateNotifications(this);
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
finish();
}
public static boolean isUpdate(Context context) {
@ -167,251 +74,4 @@ public class DatabaseUpgradeActivity extends BaseActivity {
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public interface DatabaseUpgradeListener {
public void setProgress(int progress, int total);
}
@SuppressLint("StaticFieldLeak")
private class DatabaseUpgradeTask extends AsyncTask<Integer, Double, Void>
implements DatabaseUpgradeListener
{
private final ProgressBar indeterminateProgress;
private final ProgressBar determinateProgress;
DatabaseUpgradeTask(ProgressBar indeterminateProgress, ProgressBar determinateProgress) {
this.indeterminateProgress = indeterminateProgress;
this.determinateProgress = determinateProgress;
}
@Override
protected Void doInBackground(Integer... params) {
Context context = DatabaseUpgradeActivity.this.getApplicationContext();
Log.i("DatabaseUpgradeActivity", "Running background upgrade..");
// 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");
////
//// 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;
}
private void schedulePendingIncomingParts(Context context) {
final AttachmentDatabase attachmentDb = DatabaseFactory.getAttachmentDatabase(context);
final MmsDatabase mmsDb = DatabaseFactory.getMmsDatabase(context);
final List<DatabaseAttachment> pendingAttachments = DatabaseFactory.getAttachmentDatabase(context).getPendingAttachments();
Log.i(TAG, pendingAttachments.size() + " pending parts.");
for (DatabaseAttachment attachment : pendingAttachments) {
final Reader reader = mmsDb.readerFor(mmsDb.getMessage(attachment.getMmsId()));
final MessageRecord record = reader.getNext();
if (attachment.hasData()) {
Log.i(TAG, "corrected a pending media part " + attachment.getAttachmentId() + "that already had data.");
attachmentDb.setTransferState(attachment.getMmsId(), attachment.getAttachmentId(), AttachmentDatabase.TRANSFER_PROGRESS_DONE);
} else if (record != null && !record.isOutgoing() && record.isPush()) {
Log.i(TAG, "queuing new attachment download job for incoming push part " + attachment.getAttachmentId() + ".");
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(attachment.getMmsId(), attachment.getAttachmentId(), false));
}
reader.close();
}
}
private void scheduleMessagesInPushDatabase(Context context) {
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
Cursor pushReader = null;
try {
pushReader = pushDatabase.getPending();
while (pushReader != null && pushReader.moveToNext()) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new PushDecryptJob(getApplicationContext(),
pushReader.getLong(pushReader.getColumnIndexOrThrow(PushDatabase.ID))));
}
} finally {
if (pushReader != null)
pushReader.close();
}
}
@Override
protected void onProgressUpdate(Double... update) {
indeterminateProgress.setVisibility(View.GONE);
determinateProgress.setVisibility(View.VISIBLE);
double scaler = update[0];
determinateProgress.setProgress((int)Math.floor(determinateProgress.getMax() * scaler));
}
@Override
protected void onPostExecute(Void result) {
VersionTracker.updateLastSeenVersion(DatabaseUpgradeActivity.this);
updateNotifications(DatabaseUpgradeActivity.this);
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
finish();
}
@Override
public void setProgress(int progress, int total) {
publishProgress(((double)progress / (double)total));
}
}
}

@ -1,57 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import network.loki.messenger.R;
public class LinkPreviewsIntroFragment extends Fragment {
private Controller controller;
public static LinkPreviewsIntroFragment newInstance() {
LinkPreviewsIntroFragment fragment = new LinkPreviewsIntroFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
public LinkPreviewsIntroFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (!(getActivity() instanceof Controller)) {
throw new IllegalStateException("Parent activity must implement the Controller interface.");
}
controller = (Controller) getActivity();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.experience_upgrade_link_previews_fragment, container, false);
view.findViewById(R.id.experience_ok_button).setOnClickListener(v -> {
controller.onLinkPreviewsFinished();
});
return view;
}
public interface Controller {
void onLinkPreviewsFinished();
}
}

@ -1,76 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
import androidx.fragment.app.FragmentTransaction;
import android.view.MenuItem;
import android.widget.Toast;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.logsubmit.SubmitLogFragment;
import network.loki.messenger.R;
/**
* Activity for submitting logcat logs to a pastebin service.
*/
public class LogSubmitActivity extends BaseActionBarActivity implements SubmitLogFragment.OnLogSubmittedListener {
private static final String TAG = LogSubmitActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.log_submit_activity);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
SubmitLogFragment fragment = SubmitLogFragment.newInstance();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, fragment);
transaction.commit();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return false;
}
@Override
public void onSuccess() {
Toast.makeText(getApplicationContext(), R.string.log_submit_activity__thanks, Toast.LENGTH_LONG).show();
finish();
}
@Override
public void onFailure() {
Toast.makeText(getApplicationContext(), R.string.log_submit_activity__log_fetch_failed, Toast.LENGTH_LONG).show();
finish();
}
@Override
public void onCancel() {
finish();
}
@Override
public void startActivity(Intent intent) {
try {
super.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w(TAG, e);
Toast.makeText(this, R.string.log_submit_activity__no_browser_installed, Toast.LENGTH_LONG).show();
}
}
}

@ -198,7 +198,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
if (address == null) throw new AssertionError();
if (locale == null) throw new AssertionError();
this.recipient = Recipient.from(getContext(), Address.Companion.fromSerialized(address), true);
this.recipient = Recipient.from(getContext(), Address.fromSerialized(address), true);
this.locale = locale;
getLoaderManager().initLoader(0, null, this);

@ -47,7 +47,6 @@ import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.sending_receiving.attachments.StickerLocator;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.session.libsession.utilities.ViewUtil;
@ -229,11 +228,9 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
final Intent intent = new Intent(this, target);
final String textExtra = getIntent().getStringExtra(Intent.EXTRA_TEXT);
final ArrayList<Media> mediaExtra = getIntent().getParcelableArrayListExtra(ConversationActivity.MEDIA_EXTRA);
final StickerLocator stickerExtra = getIntent().getParcelableExtra(ConversationActivity.STICKER_EXTRA);
intent.putExtra(ConversationActivity.TEXT_EXTRA, textExtra);
intent.putExtra(ConversationActivity.MEDIA_EXTRA, mediaExtra);
intent.putExtra(ConversationActivity.STICKER_EXTRA, stickerExtra);
if (resolvedExtra != null) intent.setDataAndType(resolvedExtra, mimeType);
@ -250,7 +247,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
@Override
public void onContactSelected(String number) {
Recipient recipient = Recipient.from(this, Address.Companion.fromExternal(this, number), true);
Recipient recipient = Recipient.from(this, Address.fromExternal(this, number), true);
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient);
createConversation(existingThread, recipient.getAddress(), ThreadDatabase.DistributionTypes.DEFAULT);
}

@ -43,7 +43,7 @@ public class ShortcutLauncherActivity extends AppCompatActivity {
return;
}
Address address = Address.Companion.fromSerialized(serializedAddress);
Address address = Address.fromSerialized(serializedAddress);
Recipient recipient = Recipient.from(this, address, true);
TaskStackBuilder backStack = TaskStackBuilder.create(this)
.addNextIntent(new Intent(this, HomeActivity.class));

@ -1,7 +0,0 @@
package org.thoughtcrime.securesms;
public class TextSecureExpiredException extends Exception {
public TextSecureExpiredException(String message) {
super(message);
}
}

@ -1,69 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.thoughtcrime.securesms.components.TypingIndicatorView;
import org.session.libsession.utilities.TextSecurePreferences;
import network.loki.messenger.R;
public class TypingIndicatorIntroFragment extends Fragment {
private Controller controller;
public static TypingIndicatorIntroFragment newInstance() {
TypingIndicatorIntroFragment fragment = new TypingIndicatorIntroFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
public TypingIndicatorIntroFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (!(getActivity() instanceof Controller)) {
throw new IllegalStateException("Parent activity must implement the Controller interface.");
}
controller = (Controller) getActivity();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.experience_upgrade_typing_indicators_fragment, container, false);
View yesButton = view.findViewById(R.id.experience_yes_button);
View noButton = view.findViewById(R.id.experience_no_button);
((TypingIndicatorView) view.findViewById(R.id.typing_indicator)).startAnimation();
yesButton.setOnClickListener(v -> onButtonClicked(true));
noButton.setOnClickListener(v -> onButtonClicked(false));
return view;
}
private void onButtonClicked(boolean typingEnabled) {
TextSecurePreferences.setTypingIndicatorsEnabled(getContext(), typingEnabled);
controller.onTypingIndicatorsFinished();
}
public interface Controller {
void onTypingIndicatorsFinished();
}
}

@ -11,7 +11,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
public class MmsNotificationAttachment extends Attachment {
public MmsNotificationAttachment(int status, long size) {
super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0, false, null, null, "");
super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0, false, null, "");
}
@Nullable

@ -86,12 +86,6 @@ object FullBackupExporter {
},
count)
}
StickerDatabase.TABLE_NAME -> {
exportTable(table, input, outputStream,
{ true },
{ cursor: Cursor -> exportSticker(attachmentSecret, cursor, outputStream) },
count)
}
else -> {
exportTable(table, input, outputStream, null, null, count)
}
@ -120,10 +114,7 @@ object FullBackupExporter {
}
private inline fun shouldExportTable(table: String): Boolean {
return table != SignedPreKeyDatabase.TABLE_NAME &&
table != OneTimePreKeyDatabase.TABLE_NAME &&
table != SessionDatabase.TABLE_NAME &&
table != PushDatabase.TABLE_NAME &&
return table != PushDatabase.TABLE_NAME &&
table != LokiBackupFilesDatabase.TABLE_NAME &&
table != LokiAPIDatabase.openGroupProfilePictureTable &&
@ -239,20 +230,6 @@ object FullBackupExporter {
}
}
private fun exportSticker(attachmentSecret: AttachmentSecret, cursor: Cursor, outputStream: BackupFrameOutputStream) {
try {
val rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID))
val size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH))
val data = cursor.getString(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_PATH))
val random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_RANDOM))
if (!TextUtils.isEmpty(data) && size > 0) {
ModernDecryptingPartInputStream.createFor(attachmentSecret, random, File(data), 0).use { inputStream -> outputStream.writeSticker(rowId, inputStream, size) }
}
} catch (e: IOException) {
Log.w(TAG, e)
}
}
@Throws(IOException::class)
private fun calculateVeryOldStreamLength(attachmentSecret: AttachmentSecret, random: ByteArray?, data: String): Long {
var result: Long = 0

@ -67,7 +67,6 @@ object FullBackupImporter {
frame.hasStatement() -> processStatement(db, frame.statement)
frame.hasPreference() -> processPreference(context, frame.preference)
frame.hasAttachment() -> processAttachment(context, attachmentSecret, db, frame.attachment, inputStream)
frame.hasSticker() -> processSticker(context, attachmentSecret, db, frame.sticker, inputStream)
frame.hasAvatar() -> processAvatar(context, frame.avatar, inputStream)
}
}
@ -132,21 +131,6 @@ object FullBackupImporter {
arrayOf(attachment.rowId.toString(), attachment.attachmentId.toString()))
}
@Throws(IOException::class)
private fun processSticker(context: Context, attachmentSecret: AttachmentSecret,
db: SQLiteDatabase, sticker: Sticker,
inputStream: BackupRecordInputStream) {
val stickerDirectory = context.getDir(AttachmentDatabase.DIRECTORY, Context.MODE_PRIVATE)
val dataFile = File.createTempFile("sticker", ".mms", stickerDirectory)
val output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false)
inputStream.readAttachmentTo(output.second, sticker.length)
val contentValues = ContentValues()
contentValues.put(StickerDatabase.FILE_PATH, dataFile.absolutePath)
contentValues.put(StickerDatabase.FILE_RANDOM, output.first)
db.update(StickerDatabase.TABLE_NAME, contentValues,
StickerDatabase._ID + " = ?", arrayOf(sticker.rowId.toString()))
}
@Throws(IOException::class)
private fun processAvatar(context: Context, avatar: Avatar, inputStream: BackupRecordInputStream) {
inputStream.readAttachmentTo(FileOutputStream(

@ -113,7 +113,7 @@ public class AvatarImageView extends AppCompatImageView {
}
public void update(String hexEncodedPublicKey) {
Address address = Address.Companion.fromSerialized(hexEncodedPublicKey);
Address address = Address.fromSerialized(hexEncodedPublicKey);
Recipient recipient = Recipient.from(getContext(), address, false);
updateAvatar(recipient);
}

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.text.InputType;
@ -104,23 +103,10 @@ public class ComposeText extends EmojiEditText {
}
}
public void appendInvite(String invite) {
if (!TextUtils.isEmpty(getText()) && !getText().toString().equals(" ")) {
append(" ");
}
append(invite);
setSelection(getText().length());
}
public void setCursorPositionChangedListener(@Nullable CursorPositionChangedListener listener) {
this.cursorPositionChangedListener = listener;
}
private boolean isLandscape() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}
public void setTransport() {
final boolean useSystemEmoji = TextSecurePreferences.isSystemEmojiPreferred(getContext());
final boolean isIncognito = TextSecurePreferences.isIncognitoKeyboardEnabled(getContext());
@ -140,12 +126,6 @@ public class ComposeText extends EmojiEditText {
} else {
setImeOptions(imeOptions);
}
/*
setHint(transport.getComposeHint(),
transport.getSimName().isPresent()
? getContext().getString(R.string.conversation_activity__from_sim_name, transport.getSimName().get())
: null);
*/
}
@Override

@ -9,8 +9,6 @@ import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
@ -24,16 +22,11 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.conversation.ConversationStickerSuggestionAdapter;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.SlideDeck;
@ -48,7 +41,6 @@ import org.session.libsignal.utilities.concurrent.ListenableFuture;
import org.session.libsignal.utilities.concurrent.SettableFuture;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.util.List;
import java.util.concurrent.TimeUnit;
import network.loki.messenger.R;
@ -56,15 +48,13 @@ import network.loki.messenger.R;
public class InputPanel extends LinearLayout
implements MicrophoneRecorderView.Listener,
KeyboardAwareLinearLayout.OnKeyboardShownListener,
EmojiKeyboardProvider.EmojiEventListener,
ConversationStickerSuggestionAdapter.EventListener
EmojiKeyboardProvider.EmojiEventListener
{
private static final String TAG = InputPanel.class.getSimpleName();
private static final int FADE_TIME = 150;
private RecyclerView stickerSuggestion;
private QuoteView quoteView;
private LinkPreviewView linkPreview;
private EmojiToggle mediaKeyboard;
@ -82,8 +72,6 @@ public class InputPanel extends LinearLayout
private @Nullable Listener listener;
private boolean emojiVisible;
private ConversationStickerSuggestionAdapter stickerSuggestionAdapter;
public InputPanel(Context context) {
super(context);
}
@ -103,7 +91,6 @@ public class InputPanel extends LinearLayout
View quoteDismiss = findViewById(R.id.quote_dismiss);
this.stickerSuggestion = findViewById(R.id.input_panel_sticker_suggestion);
this.quoteView = findViewById(R.id.quote_view);
this.linkPreview = findViewById(R.id.link_preview);
this.mediaKeyboard = findViewById(R.id.emoji_toggle);
@ -139,11 +126,6 @@ public class InputPanel extends LinearLayout
listener.onLinkPreviewCanceled();
}
});
stickerSuggestionAdapter = new ConversationStickerSuggestionAdapter(GlideApp.with(this), this);
stickerSuggestion.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
stickerSuggestion.setAdapter(stickerSuggestionAdapter);
}
public void setListener(final @NonNull Listener listener) {
@ -206,24 +188,6 @@ public class InputPanel extends LinearLayout
this.mediaKeyboard.attach(mediaKeyboard);
}
public void setStickerSuggestions(@NonNull List<StickerRecord> stickers) {
stickerSuggestion.setVisibility(stickers.isEmpty() ? View.GONE : View.VISIBLE);
stickerSuggestionAdapter.setStickers(stickers);
}
public void showMediaKeyboardToggle(boolean show) {
emojiVisible = show;
mediaKeyboard.setVisibility(show ? View.VISIBLE : GONE);
}
public void setMediaKeyboardToggleMode(boolean isSticker) {
mediaKeyboard.setStickerMode(isSticker);
}
public View getMediaKeyboardToggleAnchorView() {
return mediaKeyboard;
}
@Override
public void onRecordPermissionRequired() {
if (listener != null) listener.onRecorderPermissionRequired();
@ -335,13 +299,6 @@ public class InputPanel extends LinearLayout
composeText.insertEmoji(emoji);
}
@Override
public void onStickerSuggestionClicked(@NonNull StickerRecord sticker) {
if (listener != null) {
listener.onStickerSuggestionSelected(sticker);
}
}
private int readDimen(@DimenRes int dimenRes) {
return getResources().getDimensionPixelSize(dimenRes);
}
@ -362,7 +319,6 @@ public class InputPanel extends LinearLayout
void onRecorderPermissionRequired();
void onEmojiToggle();
void onLinkPreviewCanceled();
void onStickerSuggestionSelected(@NonNull StickerRecord sticker);
}
private static class SlideToCancel {

@ -131,7 +131,7 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
return insets.bottom;
}
} catch (NoSuchFieldException nsfe) {
Log.w(TAG, "field reflection error when measuring view inset", nsfe);
Log.w(TAG, "field reflection error when measuring view inset, NoSuchFieldException");
} catch (IllegalAccessException iae) {
Log.w(TAG, "access reflection error when measuring view inset", iae);
}

@ -234,7 +234,6 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
List<Slide> imageSlides = Stream.of(attachments.getSlides()).filter(Slide::hasImage).limit(1).toList();
List<Slide> videoSlides = Stream.of(attachments.getSlides()).filter(Slide::hasVideo).limit(1).toList();
List<Slide> stickerSlides = Stream.of(attachments.getSlides()).filter(Slide::hasSticker).limit(1).toList();
// Given that most types have images, we specifically check images last
if (!audioSlides.isEmpty()) {
@ -243,15 +242,13 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener
mediaDescriptionText.setVisibility(GONE);
} else if (!videoSlides.isEmpty()) {
mediaDescriptionText.setText(R.string.QuoteView_video);
} else if (!stickerSlides.isEmpty()) {
mediaDescriptionText.setText(R.string.QuoteView_sticker);
} else if (!imageSlides.isEmpty()) {
mediaDescriptionText.setText(R.string.QuoteView_photo);
}
}
private void setQuoteAttachment(@NonNull GlideRequests glideRequests, @NonNull SlideDeck slideDeck) {
List<Slide> imageVideoSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo() || s.hasSticker()).limit(1).toList();
List<Slide> imageVideoSlides = Stream.of(slideDeck.getSlides()).filter(s -> s.hasImage() || s.hasVideo()).limit(1).toList();
List<Slide> documentSlides = Stream.of(attachments.getSlides()).filter(Slide::hasDocument).limit(1).toList();
attachmentVideoOverlayView.setVisibility(GONE);

@ -1,125 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.session.libsession.utilities.ViewUtil;
public class ThreadPhotoRailView extends FrameLayout {
@NonNull private final RecyclerView recyclerView;
@Nullable private OnItemClickedListener listener;
public ThreadPhotoRailView(Context context) {
this(context, null);
}
public ThreadPhotoRailView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ThreadPhotoRailView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.recipient_preference_photo_rail, this);
this.recyclerView = ViewUtil.findById(this, R.id.photo_list);
this.recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
this.recyclerView.setItemAnimator(new DefaultItemAnimator());
this.recyclerView.setNestedScrollingEnabled(false);
}
public void setListener(@Nullable OnItemClickedListener listener) {
this.listener = listener;
if (this.recyclerView.getAdapter() != null) {
((ThreadPhotoRailAdapter)this.recyclerView.getAdapter()).setListener(listener);
}
}
public void setCursor(@NonNull GlideRequests glideRequests, @Nullable Cursor cursor) {
this.recyclerView.setAdapter(new ThreadPhotoRailAdapter(getContext(), glideRequests, cursor, this.listener));
}
private static class ThreadPhotoRailAdapter extends CursorRecyclerViewAdapter<ThreadPhotoRailAdapter.ThreadPhotoViewHolder> {
@SuppressWarnings("unused")
private static final String TAG = ThreadPhotoRailAdapter.class.getSimpleName();
@NonNull private final GlideRequests glideRequests;
@Nullable private OnItemClickedListener clickedListener;
private ThreadPhotoRailAdapter(@NonNull Context context,
@NonNull GlideRequests glideRequests,
@Nullable Cursor cursor,
@Nullable OnItemClickedListener listener)
{
super(context, cursor);
this.glideRequests = glideRequests;
this.clickedListener = listener;
}
@Override
public ThreadPhotoViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recipient_preference_photo_rail_item, parent, false);
return new ThreadPhotoViewHolder(itemView);
}
@Override
public void onBindItemViewHolder(ThreadPhotoViewHolder viewHolder, @NonNull Cursor cursor) {
ThumbnailView imageView = viewHolder.imageView;
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor);
Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment());
if (slide != null) {
imageView.setImageResource(glideRequests, slide, false, false);
}
imageView.setOnClickListener(v -> {
if (clickedListener != null) clickedListener.onItemClicked(mediaRecord);
});
}
public void setListener(@Nullable OnItemClickedListener listener) {
this.clickedListener = listener;
}
static class ThreadPhotoViewHolder extends RecyclerView.ViewHolder {
ThumbnailView imageView;
ThreadPhotoViewHolder(View itemView) {
super(itemView);
this.imageView = ViewUtil.findById(itemView, R.id.thumbnail);
}
}
}
public interface OnItemClickedListener {
void onItemClicked(MediaDatabase.MediaRecord mediaRecord);
}
}

@ -1,226 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.graphics.PorterDuff;
import android.os.Build;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.session.libsession.utilities.ThemeUtil;
import network.loki.messenger.R;
/**
* Class for creating simple tooltips to show throughout the app. Utilizes a popup window so you
* don't have to worry about view hierarchies or anything.
*/
public class TooltipPopup extends PopupWindow {
public static final int POSITION_ABOVE = 0;
public static final int POSITION_BELOW = 1;
public static final int POSITION_START = 2;
public static final int POSITION_END = 3;
private static final int POSITION_LEFT = 4;
private static final int POSITION_RIGHT = 5;
private final View anchor;
private final ImageView arrow;
private final int position;
public static Builder forTarget(@NonNull View anchor) {
return new Builder(anchor);
}
private TooltipPopup(@NonNull View anchor,
int rawPosition,
@NonNull String text,
@ColorInt int backgroundTint,
@ColorInt int textColor,
@Nullable Object iconGlideModel,
@Nullable OnDismissListener dismissListener)
{
super(LayoutInflater.from(anchor.getContext()).inflate(R.layout.tooltip, null),
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
this.anchor = anchor;
this.position = getRtlPosition(anchor.getContext(), rawPosition);
switch (rawPosition) {
case POSITION_ABOVE: arrow = getContentView().findViewById(R.id.tooltip_arrow_bottom); break;
case POSITION_BELOW: arrow = getContentView().findViewById(R.id.tooltip_arrow_top); break;
case POSITION_START: arrow = getContentView().findViewById(R.id.tooltip_arrow_end); break;
case POSITION_END: arrow = getContentView().findViewById(R.id.tooltip_arrow_start); break;
default: throw new AssertionError("Invalid position!");
}
arrow.setVisibility(View.VISIBLE);
TextView textView = getContentView().findViewById(R.id.tooltip_text);
textView.setText(text);
if (textColor != 0) {
textView.setTextColor(textColor);
}
View bubble = getContentView().findViewById(R.id.tooltip_bubble);
if (backgroundTint == 0) {
bubble.getBackground().setColorFilter(ThemeUtil.getThemedColor(anchor.getContext(), R.attr.tooltip_default_color), PorterDuff.Mode.MULTIPLY);
arrow.setColorFilter(ThemeUtil.getThemedColor(anchor.getContext(), R.attr.tooltip_default_color), PorterDuff.Mode.MULTIPLY);
} else {
bubble.getBackground().setColorFilter(backgroundTint, PorterDuff.Mode.MULTIPLY);
arrow.setColorFilter(backgroundTint, PorterDuff.Mode.MULTIPLY);
}
if (iconGlideModel != null) {
ImageView iconView = getContentView().findViewById(R.id.tooltip_icon);
iconView.setVisibility(View.VISIBLE);
GlideApp.with(anchor.getContext()).load(iconGlideModel).into(iconView);
}
if (Build.VERSION.SDK_INT >= 21) {
setElevation(10);
}
getContentView().setOnClickListener(v -> dismiss());
setOnDismissListener(dismissListener);
setBackgroundDrawable(null);
setOutsideTouchable(true);
}
private void show() {
if (anchor.getWidth() == 0 && anchor.getHeight() == 0) {
anchor.post(this::show);
return;
}
getContentView().measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
int tooltipSpacing = anchor.getContext().getResources().getDimensionPixelOffset(R.dimen.tooltip_popup_margin);
int xoffset;
int yoffset;
switch (position) {
case POSITION_ABOVE:
xoffset = 0;
yoffset = -(2 * anchor.getWidth() + tooltipSpacing);
onLayout(() -> setArrowHorizontalPosition(arrow, anchor));
break;
case POSITION_BELOW:
xoffset = 0;
yoffset = tooltipSpacing;
onLayout(() -> setArrowHorizontalPosition(arrow, anchor));
break;
case POSITION_LEFT:
xoffset = -getContentView().getMeasuredWidth() - tooltipSpacing;
yoffset = -(getContentView().getMeasuredHeight()/2 + anchor.getHeight()/2);
break;
case POSITION_RIGHT:
xoffset = anchor.getWidth() + tooltipSpacing;
yoffset = -(getContentView().getMeasuredHeight()/2 + anchor.getHeight()/2);
break;
default:
throw new AssertionError("Invalid tooltip position!");
}
showAsDropDown(anchor, xoffset, yoffset);
}
private void onLayout(@NonNull Runnable runnable) {
getContentView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getContentView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
runnable.run();
}
});
}
private static void setArrowHorizontalPosition(@NonNull View arrow, @NonNull View anchor) {
int arrowCenterX = getAbsolutePosition(arrow)[0] + arrow.getWidth()/2;
int anchorCenterX = getAbsolutePosition(anchor)[0] + anchor.getWidth()/2;
arrow.setX(anchorCenterX - arrowCenterX);
}
private static int[] getAbsolutePosition(@NonNull View view) {
int[] position = new int[2];
view.getLocationOnScreen(position);
return position;
}
private static int getRtlPosition(@NonNull Context context, int position) {
if (position == POSITION_ABOVE || position == POSITION_BELOW) {
return position;
} else if (context.getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
return position == POSITION_START ? POSITION_RIGHT : POSITION_LEFT;
} else {
return position == POSITION_START ? POSITION_LEFT : POSITION_RIGHT;
}
}
public static class Builder {
private final View anchor;
private int backgroundTint;
private int textColor;
private int textResId;
private Object iconGlideModel;
private OnDismissListener dismissListener;
private Builder(@NonNull View anchor) {
this.anchor = anchor;
}
public Builder setBackgroundTint(int color) {
this.backgroundTint = color;
return this;
}
public Builder setTextColor(int color) {
this.textColor = color;
return this;
}
public Builder setText(@StringRes int stringResId) {
this.textResId = stringResId;
return this;
}
public Builder setIconGlideModel(Object model) {
this.iconGlideModel = model;
return this;
}
public Builder setOnDismissListener(OnDismissListener dismissListener) {
this.dismissListener = dismissListener;
return this;
}
public TooltipPopup show(int position) {
String text = anchor.getContext().getString(textResId);
TooltipPopup tooltip = new TooltipPopup(anchor, position, text, backgroundTint, textColor, iconGlideModel, dismissListener);
tooltip.show();
return tooltip;
}
}
}

@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.Util;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import java.util.HashMap;
import java.util.Map;
@ -87,12 +86,8 @@ public class TypingStatusSender {
if (recipient != null && !SessionMetaProtocol.shouldSendTypingIndicator(recipient.getAddress())) { return; }
// Loki - Take into account multi device
if (recipient == null) { return; }
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(recipient.getAddress().serialize());
for (String device : linkedDevices) {
Recipient deviceAsRecipient = Recipient.from(context, Address.Companion.fromSerialized(device), false);
long deviceThreadID = threadDatabase.getOrCreateThreadIdFor(deviceAsRecipient);
ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(deviceThreadID, typingStarted));
}
long threadID = threadDatabase.getOrCreateThreadIdFor(recipient);
ApplicationContext.getInstance(context).getJobManager().add(new TypingSendJob(threadID, typingStarted));
}
private class StartRunnable implements Runnable {

@ -7,8 +7,6 @@ import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import org.thoughtcrime.securesms.stickers.StickerKeyboardProvider;
import org.session.libsession.utilities.TextSecurePreferences;
import network.loki.messenger.R;
@ -82,8 +80,5 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
@Override
public void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider) {
setStickerMode(provider instanceof StickerKeyboardProvider);
TextSecurePreferences.setMediaKeyboardMode(getContext(), (provider instanceof StickerKeyboardProvider) ? TextSecurePreferences.MediaKeyboardMode.STICKER
: TextSecurePreferences.MediaKeyboardMode.EMOJI);
}
}

@ -1,59 +0,0 @@
package org.thoughtcrime.securesms.components.location;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.session.libsignal.utilities.concurrent.ListenableFuture;
import org.session.libsignal.utilities.concurrent.SettableFuture;
import org.session.libsession.utilities.ViewUtil;
import network.loki.messenger.R;
public class SignalMapView extends LinearLayout {
private ImageView imageView;
private TextView textView;
public SignalMapView(Context context) {
this(context, null);
}
public SignalMapView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public SignalMapView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context);
}
private void initialize(Context context) {
setOrientation(LinearLayout.VERTICAL);
LayoutInflater.from(context).inflate(R.layout.signal_map_view, this, true);
this.imageView = ViewUtil.findById(this, R.id.image_view);
this.textView = ViewUtil.findById(this, R.id.address_view);
}
public ListenableFuture<Bitmap> display(final SignalPlace place) {
final SettableFuture<Bitmap> future = new SettableFuture<>();
this.imageView.setVisibility(View.GONE);
this.textView.setText(place.getDescription());
return future;
}
}

@ -1,96 +0,0 @@
package org.thoughtcrime.securesms.components.location;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.utilities.JsonUtil;
import java.io.IOException;
public class SignalPlace {
/* Loki - Temporary Placeholders */
class LatLng {
double latitude;
double longitude;
LatLng(double latitude, double longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
}
class Place {
public CharSequence getName() { return ""; }
public CharSequence getAddress() { return ""; }
LatLng getLatLng() { return new LatLng(0, 0); }
}
private static final String URL = "https://maps.google.com/maps";
private static final String TAG = SignalPlace.class.getSimpleName();
@JsonProperty
private CharSequence name;
@JsonProperty
private CharSequence address;
@JsonProperty
private double latitude;
@JsonProperty
private double longitude;
public SignalPlace(Place place) {
this.name = place.getName();
this.address = place.getAddress();
this.latitude = place.getLatLng().latitude;
this.longitude = place.getLatLng().longitude;
}
public SignalPlace() {}
@JsonIgnore
public LatLng getLatLong() {
return new LatLng(latitude, longitude);
}
@JsonIgnore
public String getDescription() {
String description = "";
if (!TextUtils.isEmpty(name)) {
description += (name + "\n");
}
if (!TextUtils.isEmpty(address)) {
description += (address + "\n");
}
description += Uri.parse(URL)
.buildUpon()
.appendQueryParameter("q", String.format("%s,%s", latitude, longitude))
.build().toString();
return description;
}
public @Nullable String serialize() {
try {
return JsonUtil.toJsonThrows(this);
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}
public static SignalPlace deserialize(@NonNull String serialized) throws IOException {
return JsonUtil.fromJson(serialized, SignalPlace.class);
}
}

@ -1,6 +0,0 @@
package org.thoughtcrime.securesms.contactshare;
public interface Selectable {
void setSelected(boolean selected);
boolean isSelected();
}

@ -85,14 +85,13 @@ import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.MediaTypes;
import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.protocol.mentions.Mention;
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.shelved.multidevice.MultiDeviceProtocol;
import org.session.libsignal.service.loki.utilities.mentions.Mention;
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
import org.session.libsignal.service.loki.utilities.HexEncodingKt;
import org.session.libsignal.service.loki.utilities.PublicKeyValidation;
import org.thoughtcrime.securesms.ApplicationContext;
@ -111,16 +110,13 @@ import org.thoughtcrime.securesms.components.HidingLinearLayout;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.InputPanel;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
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.location.SignalPlace;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase;
@ -131,7 +127,6 @@ import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
@ -141,17 +136,13 @@ import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate;
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2;
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities;
import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView;
import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
import org.thoughtcrime.securesms.loki.views.SessionRestoreBannerView;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
import org.thoughtcrime.securesms.mms.AttachmentManager;
@ -161,7 +152,6 @@ import org.thoughtcrime.securesms.mms.GifSlide;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.LocationSlide;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
@ -169,26 +159,18 @@ import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.QuoteId;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.mms.TextSlide;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.RecipientFormattingException;
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.search.model.MessageResult;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.stickers.StickerKeyboardProvider;
import org.thoughtcrime.securesms.stickers.StickerManagementActivity;
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent;
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.MediaUtil;
@ -199,7 +181,6 @@ import org.session.libsession.utilities.Util;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.sending_receiving.attachments.StickerLocator;
import org.session.libsession.messaging.threads.GroupRecord;
import org.session.libsession.utilities.ExpirationUtil;
import org.session.libsession.utilities.views.Stub;
@ -208,7 +189,6 @@ import org.session.libsession.utilities.concurrent.AssertedSuccessListener;
import org.session.libsignal.utilities.concurrent.ListenableFuture;
import org.session.libsignal.utilities.concurrent.SettableFuture;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.TextSecurePreferences.MediaKeyboardMode;
import java.io.IOException;
import java.text.SimpleDateFormat;
@ -241,9 +221,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
InputPanel.Listener,
InputPanel.MediaListener,
ComposeText.CursorPositionChangedListener,
ConversationSearchBottomBar.EventListener,
StickerKeyboardProvider.StickerEventListener,
LokiThreadDatabaseDelegate
ConversationSearchBottomBar.EventListener
{
private static final String TAG = ConversationActivity.class.getSimpleName();
@ -278,12 +256,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private ImageButton attachButton;
private ProfilePictureView profilePictureView;
private TextView titleTextView;
private TextView charactersLeft;
private ConversationFragment fragment;
private Button unblockButton;
private Button makeDefaultSmsButton;
private InputAwareLayout container;
private Stub<GroupShareProfileView> groupShareProfileView;
private TypingStatusTextWatcher typingTextWatcher;
private MentionTextWatcher mentionTextWatcher;
private ConversationSearchBottomBar searchNav;
@ -298,7 +274,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private AudioRecorder audioRecorder;
private Handler audioHandler;
private Runnable stopRecordingTask;
private BroadcastReceiver securityUpdateReceiver;
private Stub<MediaKeyboard> emojiDrawerStub;
protected HidingLinearLayout quickAttachmentToggle;
protected HidingLinearLayout inlineAttachmentToggle;
@ -306,14 +281,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private LinkPreviewViewModel linkPreviewViewModel;
private ConversationSearchViewModel searchViewModel;
private ConversationStickerViewModel stickerViewModel;
private Recipient recipient;
private long threadId;
private int distributionType;
private boolean archived;
private boolean isDefaultSms = false;
private boolean isMmsEnabled = false;
private boolean isSecurityInitialized = false;
private int expandedKeyboardHeight = 0;
private int collapsedKeyboardHeight = Integer.MAX_VALUE;
@ -330,9 +302,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private ArrayList<Mention> mentions = new ArrayList<>();
private String oldText = "";
// Restoration
protected SessionRestoreBannerView sessionRestoreBannerView;
private final PushCharacterCalculator characterCalculator = new PushCharacterCalculator();
@Override
@ -358,13 +327,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
broadcastReceivers.add(broadcastReceiver);
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter("clockOutOfSync"));
initializeReceivers();
initializeActionBar();
initializeViews();
initializeResources();
initializeLinkPreviewObserver();
initializeSearchObserver();
initializeStickerObserver();
initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
@ -404,18 +371,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
});
sessionRestoreBannerView.setOnRestore(() -> {
SessionManagementProtocol.startSessionReset(this, recipient.getAddress().serialize());
updateSessionRestoreBanner();
return Unit.INSTANCE;
});
sessionRestoreBannerView.setOnDismiss(() -> {
// TODO: Maybe silence for x minutes?
DatabaseFactory.getLokiThreadDatabase(ConversationActivity.this).removeAllSessionRestoreDevices(threadId);
updateSessionRestoreBanner();
return Unit.INSTANCE;
});
MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this);
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
@ -494,24 +449,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
EventBus.getDefault().register(this);
initializeEnabledCheck();
initializeMmsEnabledCheck();
composeText.setTransport();
updateTitleTextView(recipient);
updateProfilePicture();
updateSubtitleTextView();
updateInputUI(recipient);
setGroupShareProfileReminder(recipient);
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId);
markThreadAsRead();
DatabaseFactory.getLokiThreadDatabase(this).setDelegate(this);
inputPanel.setHint(getResources().getString(R.string.ConversationActivity_message));
updateSessionRestoreBanner();
Log.i(TAG, "onResume() Finished: " + (System.currentTimeMillis() - getIntent().getLongExtra(TIMING_EXTRA, 0)));
}
@ -526,8 +475,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
markLastSeen();
AudioSlidePlayer.stopAll();
EventBus.getDefault().unregister(this);
DatabaseFactory.getLokiThreadDatabase(this).setDelegate(null);
}
@Override
@ -551,7 +498,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
protected void onDestroy() {
saveDraft();
if (recipient != null) recipient.removeListener(this);
if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver);
for (BroadcastReceiver broadcastReceiver : broadcastReceivers) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
}
@ -577,13 +523,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
case PICK_AUDIO:
setMedia(data.getData(), MediaType.AUDIO);
break;
// case PICK_CONTACT:
// if (isSecureText && !isSmsForced()) {
//// openContactShareEditor(data.getData());
// } else {
// addAttachmentContactInfo(data.getData());
// }
// break;
case TAKE_PHOTO:
if (attachmentManager.getCaptureUri() != null) {
setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE);
@ -953,36 +892,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
startActivityForResult(intent, SMS_DEFAULT);
}
private void handleResetSecureSession() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.ConversationActivity_reset_secure_session_question);
builder.setIconAttribute(R.attr.dialog_alert_icon);
builder.setCancelable(true);
builder.setMessage(R.string.ConversationActivity_this_may_help_if_youre_having_encryption_problems);
builder.setPositiveButton(R.string.ConversationActivity_reset, (dialog, which) -> {
if (isSingleConversation()) {
final Context context = getApplicationContext();
OutgoingEndSessionMessage endSessionMessage =
new OutgoingEndSessionMessage(new OutgoingTextMessage(getRecipient(), "TERMINATE", 0, -1));
new AsyncTask<OutgoingEndSessionMessage, Void, Long>() {
@Override
protected Long doInBackground(OutgoingEndSessionMessage... messages) {
return MessageSender.send(context, messages[0], threadId, false, null);
}
@Override
protected void onPostExecute(Long result) {
sendComplete(result);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, endSessionMessage);
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
private void handleBlock() {
int titleRes = R.string.RecipientPreferenceActivity_block_this_contact_question;
int bodyRes = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact;
@ -1099,20 +1008,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
builder.setPositiveButton(R.string.yes, (dialog, which) -> {
Recipient groupRecipient = getRecipient();
String groupPublicKey;
boolean isSSKBasedClosedGroup;
boolean isClosedGroup;
try {
groupPublicKey = HexEncodingKt.toHexString(ClosedGroupsProtocol.doubleDecodeGroupID(groupRecipient.getAddress().toString()));
isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(this).isSSKBasedClosedGroup(groupPublicKey);
groupPublicKey = HexEncodingKt.toHexString(GroupUtil.doubleDecodeGroupID(groupRecipient.getAddress().toString()));
isClosedGroup = DatabaseFactory.getLokiAPIDatabase(this).isClosedGroup(groupPublicKey);
} catch (IOException e) {
groupPublicKey = null;
isSSKBasedClosedGroup = false;
isClosedGroup = false;
}
try {
if (isSSKBasedClosedGroup) {
if (isClosedGroup) {
ClosedGroupsProtocolV2.explicitLeave(this, groupPublicKey);
initializeEnabledCheck();
} else if (ClosedGroupsProtocol.leaveLegacyGroup(this, groupRecipient)) {
initializeEnabledCheck();
} else {
Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show();
}
@ -1165,16 +1072,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleAddAttachment() {
if (this.isMmsEnabled) {
if (attachmentTypeSelector == null) {
attachmentTypeSelector = new AttachmentTypeSelector(
this,
LoaderManager.getInstance(this),
new AttachmentTypeListener(),
keyboardHeight);
}
attachmentTypeSelector.show(this, attachButton);
if (attachmentTypeSelector == null) {
attachmentTypeSelector = new AttachmentTypeSelector(
this,
LoaderManager.getInstance(this),
new AttachmentTypeListener(),
keyboardHeight);
}
attachmentTypeSelector.show(this, attachButton);
}
private void handleSecurityChange(boolean isSecureText, boolean isDefaultSms) {
@ -1215,12 +1120,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
final Uri draftMedia = getIntent().getData();
final MediaType draftMediaType = MediaType.from(getIntent().getType());
final List<Media> mediaList = getIntent().getParcelableArrayListExtra(MEDIA_EXTRA);
final StickerLocator stickerLocator = getIntent().getParcelableExtra(STICKER_EXTRA);
if (stickerLocator != null && draftMedia != null) {
sendSticker(stickerLocator, draftMedia, 0, true);
return new SettableFuture<>(false);
}
if (!Util.isEmpty(mediaList)) {
Intent sendIntent = MediaSendActivity.buildEditorIntent(this, mediaList, recipient, draftText);
@ -1291,32 +1190,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
};
for (Draft draft : drafts) {
try {
switch (draft.getType()) {
case Draft.TEXT:
composeText.setText(draft.getValue());
listener.onSuccess(true);
break;
case Draft.LOCATION:
attachmentManager.setLocation(SignalPlace.deserialize(draft.getValue()), MediaConstraints.getPushMediaConstraints()).addListener(listener);
break;
case Draft.IMAGE:
setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE).addListener(listener);
break;
case Draft.AUDIO:
setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO).addListener(listener);
break;
case Draft.VIDEO:
setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO).addListener(listener);
break;
case Draft.QUOTE:
SettableFuture<Boolean> quoteResult = new SettableFuture<>();
new QuoteRestorationTask(draft.getValue(), quoteResult).execute();
quoteResult.addListener(listener);
break;
}
} catch (IOException e) {
Log.w(TAG, e);
switch (draft.getType()) {
case Draft.TEXT:
composeText.setText(draft.getValue());
listener.onSuccess(true);
break;
case Draft.IMAGE:
setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE).addListener(listener);
break;
case Draft.AUDIO:
setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO).addListener(listener);
break;
case Draft.VIDEO:
setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO).addListener(listener);
break;
case Draft.QUOTE:
SettableFuture<Boolean> quoteResult = new SettableFuture<>();
new QuoteRestorationTask(draft.getValue(), quoteResult).execute();
quoteResult.addListener(listener);
break;
}
}
@ -1357,30 +1249,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return future;
}
private void updateSessionRestoreBanner() {
Set<String> devices = DatabaseFactory.getLokiThreadDatabase(this).getSessionRestoreDevices(threadId);
if (devices.size() > 0) {
sessionRestoreBannerView.update(recipient);
sessionRestoreBannerView.show();
} else {
sessionRestoreBannerView.hide();
}
}
private void initializeMmsEnabledCheck() {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
return org.thoughtcrime.securesms.util.Util.isMmsCapable(ConversationActivity.this);
}
@Override
protected void onPostExecute(Boolean isMmsEnabled) {
ConversationActivity.this.isMmsEnabled = isMmsEnabled;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void initializeViews() {
profilePictureView = findViewById(R.id.profilePictureView);
titleTextView = findViewById(R.id.titleTextView);
@ -1388,19 +1256,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendButton = ViewUtil.findById(this, R.id.send_button);
attachButton = ViewUtil.findById(this, R.id.attach_button);
composeText = ViewUtil.findById(this, R.id.embedded_text_editor);
charactersLeft = ViewUtil.findById(this, R.id.space_left);
emojiDrawerStub = ViewUtil.findStubById(this, R.id.emoji_drawer_stub);
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);
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);
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
mentionCandidateSelectionViewContainer = ViewUtil.findById(this, R.id.mentionCandidateSelectionViewContainer);
mentionCandidateSelectionView = ViewUtil.findById(this, R.id.userSelectionView);
sessionRestoreBannerView = ViewUtil.findById(this, R.id.sessionRestoreBannerView);
messageStatusProgressBar = ViewUtil.findById(this, R.id.messageStatusProgressBar);
muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView);
subtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView);
@ -1429,10 +1294,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendButton.setOnClickListener(sendButtonListener);
sendButton.setEnabled(true);
/*
titleView.setOnClickListener(v -> handleConversationSettings());
titleView.setOnLongClickListener(v -> handleDisplayQuickContact());
*/
unblockButton.setOnClickListener(v -> handleUnblock());
makeDefaultSmsButton.setOnClickListener(v -> handleMakeDefaultSms());
@ -1474,7 +1335,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (address == null) { finish(); return; }
recipient = Recipient.from(this, address, true);
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
archived = getIntent().getBooleanExtra(IS_ARCHIVED_EXTRA, false);
distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
glideRequests = GlideApp.with(this);
@ -1519,52 +1379,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
});
}
private void initializeStickerObserver() {
StickerSearchRepository repository = new StickerSearchRepository(this);
stickerViewModel = ViewModelProviders.of(this, new ConversationStickerViewModel.Factory(getApplication(), repository))
.get(ConversationStickerViewModel.class);
stickerViewModel.getStickerResults().observe(this, stickers -> {
if (stickers == null) return;
inputPanel.setStickerSuggestions(stickers);
});
stickerViewModel.getStickersAvailability().observe(this, stickersAvailable -> {
if (stickersAvailable == null) return;
boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this);
MediaKeyboardMode keyboardMode = TextSecurePreferences.getMediaKeyboardMode(this);
boolean stickerIntro = !TextSecurePreferences.hasSeenStickerIntroTooltip(this);
if (stickersAvailable) {
inputPanel.showMediaKeyboardToggle(true);
inputPanel.setMediaKeyboardToggleMode(isSystemEmojiPreferred || keyboardMode == MediaKeyboardMode.STICKER);
if (stickerIntro) showStickerIntroductionTooltip();
}
if (emojiDrawerStub.resolved()) {
initializeMediaKeyboardProviders(emojiDrawerStub.get(), stickersAvailable);
}
});
}
private void showStickerIntroductionTooltip() {
TextSecurePreferences.setMediaKeyboardMode(this, MediaKeyboardMode.STICKER);
inputPanel.setMediaKeyboardToggleMode(true);
TooltipPopup.forTarget(inputPanel.getMediaKeyboardToggleAnchorView())
.setBackgroundTint(getResources().getColor(R.color.core_blue))
.setTextColor(getResources().getColor(R.color.core_white))
.setText(R.string.ConversationActivity_new_say_it_with_stickers)
.setOnDismissListener(() -> {
TextSecurePreferences.setHasSeenStickerIntroTooltip(this, true);
EventBus.getDefault().removeStickyEvent(StickerPackInstallEvent.class);
})
.show(TooltipPopup.POSITION_ABOVE);
}
@Override
public void onSearchMoveUpPressed() {
searchViewModel.onMoveUp();
@ -1583,9 +1397,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateTitleTextView(recipient);
updateProfilePicture();
updateSubtitleTextView();
// titleView.setVerified(identityRecords.isVerified());
updateInputUI(recipient);
setGroupShareProfileReminder(recipient);
initializeSecurity(true, isDefaultSms);
if (searchViewItem == null || !searchViewItem.isActionViewExpanded()) {
@ -1594,17 +1406,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
});
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onStickerPackInstalled(final StickerPackInstallEvent event) {
if (!TextSecurePreferences.hasSeenStickerIntroTooltip(this)) return;
EventBus.getDefault().removeStickyEvent(event);
TooltipPopup.forTarget(inputPanel.getMediaKeyboardToggleAnchorView())
.setText(R.string.ConversationActivity_sticker_pack_installed)
.setIconGlideModel(event.getIconGlideModel())
.show(TooltipPopup.POSITION_ABOVE);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onOpenGroupInfoUpdated(OpenGroupUtilities.GroupInfoUpdatedEvent event) {
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
@ -1615,19 +1416,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private void initializeReceivers() {
securityUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
initializeSecurity(true, isDefaultSms);
}
};
registerReceiver(securityUpdateReceiver,
new IntentFilter(SecurityEvent.SECURITY_UPDATE_EVENT),
KeyCachingService.KEY_PERMISSION, null);
}
//////// Helper Methods
private void addAttachment(int type) {
@ -1730,7 +1518,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
for (Slide slide : attachmentManager.buildSlideDeck().getSlides()) {
if (slide.hasAudio() && slide.getUri() != null) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString()));
else if (slide.hasVideo() && slide.getUri() != null) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString()));
else if (slide.hasLocation()) drafts.add(new Draft(Draft.LOCATION, ((LocationSlide)slide).getPlace().serialize()));
else if (slide.hasImage() && slide.getUri() != null) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString()));
}
@ -1795,14 +1582,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
unblockButton.setVisibility(View.VISIBLE);
inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
// } else if (!isSecureText && isPushGroupConversation()) {
// unblockButton.setVisibility(View.GONE);
// inputPanel.setVisibility(View.GONE);
// makeDefaultSmsButton.setVisibility(View.GONE);
// } else if (!isSecureText && !isDefaultSms) {
// unblockButton.setVisibility(View.GONE);
// inputPanel.setVisibility(View.GONE);
// makeDefaultSmsButton.setVisibility(View.GONE);
} else {
inputPanel.setVisibility(View.VISIBLE);
unblockButton.setVisibility(View.GONE);
@ -1810,30 +1589,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private void setGroupShareProfileReminder(@NonNull Recipient recipient) {
if (recipient.isPushGroupRecipient() && !recipient.isProfileSharing() && !recipient.getAddress().isOpenGroup()) {
groupShareProfileView.get().setRecipient(recipient);
groupShareProfileView.get().setVisibility(View.GONE); // Loki - Always hide for now
} else if (groupShareProfileView.resolved()) {
groupShareProfileView.get().setVisibility(View.GONE);
}
}
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard, boolean stickersAvailable) {
private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard) {
boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this);
if (stickersAvailable) {
if (isSystemEmojiPreferred) {
mediaKeyboard.setProviders(0, new StickerKeyboardProvider(this, this));
} else {
MediaKeyboardMode keyboardMode = TextSecurePreferences.getMediaKeyboardMode(this);
int index = keyboardMode == MediaKeyboardMode.STICKER ? 1 : 0;
mediaKeyboard.setProviders(index,
new EmojiKeyboardProvider(this, inputPanel),
new StickerKeyboardProvider(this, this));
}
} else if (!isSystemEmojiPreferred) {
if (!isSystemEmojiPreferred) {
mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, inputPanel));
}
}
@ -1963,13 +1721,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateLinkPreviewState();
}
@Override
public void handleSessionRestoreDevicesChanged(long threadID) {
if (threadID == this.threadId) {
runOnUiThread(this::updateSessionRestoreBanner);
}
}
private void sendMessage() {
if (inputPanel.isRecordingInLockedMode()) {
inputPanel.releaseRecordingLock();
@ -2006,7 +1757,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Log.w(TAG, ex);
}
if (messageStatus == null && !isGroupConversation() && !SessionMetaProtocol.shared.isNoteToSelf(recipient.getAddress().serialize())) {
if (messageStatus == null && !isGroupConversation() && !(TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize()))) {
messageStatus = "calculatingPoW";
updateSubtitleTextView();
updateMessageStatusProgressBar();
@ -2063,10 +1814,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
long result = MessageSender.send(context, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
if (!recipient.isGroupRecipient()) {
ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(recipient.getAddress().serialize());
}
sendComplete(result);
future.set(null);
@ -2093,10 +1840,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
long result = MessageSender.send(context, message, threadId, false, () -> fragment.releaseOutgoingMessage(id));
if (!recipient.isGroupRecipient()) {
ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(recipient.getAddress().serialize());
}
sendComplete(result);
}
@ -2244,9 +1987,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onEmojiToggle() {
if (!emojiDrawerStub.resolved()) {
Boolean stickersAvailable = stickerViewModel.getStickersAvailability().getValue();
initializeMediaKeyboardProviders(emojiDrawerStub.get(), stickersAvailable == null ? false : stickersAvailable);
initializeMediaKeyboardProviders(emojiDrawerStub.get());
inputPanel.setMediaKeyboard(emojiDrawerStub.get());
}
@ -2263,11 +2004,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
linkPreviewViewModel.onUserCancel();
}
@Override
public void onStickerSuggestionSelected(@NonNull StickerRecord sticker) {
sendSticker(sticker, true);
}
@Override
public void onMediaSelected(@NonNull Uri uri, String contentType) {
if (!TextUtils.isEmpty(contentType) && contentType.trim().equals("image/gif")) {
@ -2286,38 +2022,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), start, end);
}
@Override
public void onStickerSelected(@NonNull StickerRecord stickerRecord) {
sendSticker(stickerRecord, false);
}
@Override
public void onStickerManagementClicked() {
startActivity(StickerManagementActivity.getIntent(this));
container.hideAttachedInput(true);
}
private void sendSticker(@NonNull StickerRecord stickerRecord, boolean clearCompose) {
sendSticker(new StickerLocator(stickerRecord.getPackId(), stickerRecord.getPackKey(), stickerRecord.getStickerId()), stickerRecord.getUri(), stickerRecord.getSize(), clearCompose);
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
DatabaseFactory.getStickerDatabase(this).updateStickerLastUsedTime(stickerRecord.getRowId(), System.currentTimeMillis());
});
}
private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull Uri uri, long size, boolean clearCompose) {
long expiresIn = recipient.getExpireMessages() * 1000L;
int subscriptionId = -1;
boolean initiating = threadId == -1;
SlideDeck slideDeck = new SlideDeck();
Slide stickerSlide = new StickerSlide(this, uri, size, stickerLocator);
slideDeck.addSlide(stickerSlide);
sendMediaMessage(false, "", slideDeck, null, Collections.emptyList(), Collections.emptyList(), expiresIn, subscriptionId, initiating, clearCompose);
}
private void silentlySetComposeText(String text) {
typingTextWatcher.setEnabled(false);
composeText.setText(text);
@ -2418,12 +2122,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void afterTextChanged(Editable s) {
if (composeText.getTextTrimmed().length() == 0 || beforeLength == 0) {
composeText.postDelayed(ConversationActivity.this::updateToggleButtonState, 50);
}
stickerViewModel.onInputTextUpdated(s.toString());
}
@Override
@ -2518,7 +2219,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Recipient author;
if (messageRecord.isOutgoing()) {
author = Recipient.from(this, Address.Companion.fromSerialized(TextSecurePreferences.getLocalNumber(this)), true);
author = Recipient.from(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), true);
} else {
author = messageRecord.getIndividualRecipient();
}
@ -2620,10 +2321,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
// region Loki
private void updateTitleTextView(Recipient recipient) {
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
Set<String> allUserDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
if (recipient == null) {
titleTextView.setText("Compose");
} else if (allUserDevices.contains(recipient.getAddress().toString().toLowerCase())) {
} else if (recipient.getAddress().toString().toLowerCase().equals(userPublicKey)) {
titleTextView.setText(getResources().getString(R.string.note_to_self));
} else {
boolean hasName = (recipient.getName() != null && !recipient.getName().isEmpty());
@ -2692,7 +2392,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleMessageStatusChanged(String newMessageStatus, long timestamp) {
if (timestamp == 0 || SessionMetaProtocol.shared.isNoteToSelf(recipient.getAddress().serialize()) ) { return; }
if (timestamp == 0 || (TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize())) ) { return; }
updateForNewMessageStatusIfNeeded(newMessageStatus, timestamp);
if (newMessageStatus.equals("messageFailed") || newMessageStatus.equals("messageSent")) {
new Handler().postDelayed(() -> clearMessageStatusIfNeeded(timestamp), 1000);

@ -339,10 +339,6 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
if (slideDeck.getThumbnailSlide() != null && slideDeck.getThumbnailSlide().getFastPreflightId() != null) {
return Long.valueOf(slideDeck.getThumbnailSlide().getFastPreflightId());
}
if (slideDeck.getStickerSlide() != null && slideDeck.getStickerSlide().getFastPreflightId() != null) {
return Long.valueOf(slideDeck.getStickerSlide().getFastPreflightId());
}
}
return record.getId();

@ -81,11 +81,9 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.UnknownSenderView;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
@ -95,7 +93,6 @@ import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.attachments.StickerLocator;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.ViewUtil;
@ -146,7 +143,6 @@ public class ConversationFragment extends Fragment
private ViewSwitcher topLoadMoreView;
private ViewSwitcher bottomLoadMoreView;
private ConversationTypingView typingView;
private UnknownSenderView unknownSenderView;
private View composeDivider;
private View scrollToBottomButton;
private TextView scrollDateHeader;
@ -255,7 +251,6 @@ public class ConversationFragment extends Fragment
this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1);
this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1);
this.firstLoad = true;
this.unknownSenderView = new UnknownSenderView(getActivity(), recipient, threadId);
OnScrollListener scrollListener = new ConversationScrollListener(getActivity());
list.addOnScrollListener(scrollListener);
@ -385,8 +380,7 @@ public class ConversationFragment extends Fragment
menu.findItem(R.id.menu_context_save_attachment).setVisible(!actionMessage &&
messageRecord.isMms() &&
!messageRecord.isMmsNotification() &&
((MediaMmsMessageRecord)messageRecord).containsMediaSlide() &&
((MediaMmsMessageRecord)messageRecord).getSlideDeck().getStickerSlide() == null);
((MediaMmsMessageRecord)messageRecord).containsMediaSlide());
menu.findItem(R.id.menu_context_reply).setVisible(!actionMessage &&
!messageRecord.isPending() &&
@ -654,8 +648,7 @@ public class ConversationFragment extends Fragment
boolean isAlbum = mediaMessage.containsMediaSlide() &&
mediaMessage.getSlideDeck().getSlides().size() > 1 &&
mediaMessage.getSlideDeck().getAudioSlide() == null &&
mediaMessage.getSlideDeck().getDocumentSlide() == null &&
mediaMessage.getSlideDeck().getStickerSlide() == null;
mediaMessage.getSlideDeck().getDocumentSlide() == null;
if (isAlbum) {
ArrayList<Media> mediaList = new ArrayList<>(mediaMessage.getSlideDeck().getSlides().size());
@ -686,10 +679,6 @@ public class ConversationFragment extends Fragment
Slide slide = mediaMessage.getSlideDeck().getSlides().get(0);
composeIntent.putExtra(Intent.EXTRA_STREAM, slide.getUri());
composeIntent.setType(slide.getContentType());
if (slide.hasSticker()) {
composeIntent.putExtra(ConversationActivity.STICKER_EXTRA, slide.asAttachment().getSticker());
}
}
if (mediaMessage.getSlideDeck().getTextSlide() != null && mediaMessage.getSlideDeck().getTextSlide().getUri() != null) {
@ -1084,13 +1073,6 @@ public class ConversationFragment extends Fragment
startActivity(LongMessageActivity.getIntent(getContext(), conversationAddress, messageId, isMms));
}
}
@Override
public void onStickerClicked(@NonNull StickerLocator sticker) {
if (getContext() != null && getActivity() != null) {
startActivity(StickerPackPreviewActivity.getIntent(sticker.getPackId(), sticker.getPackKey()));
}
}
}
private class ActionModeCallback implements ActionMode.Callback {

@ -49,7 +49,6 @@ import android.widget.Toast;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.annimon.stream.Stream;
@ -71,17 +70,12 @@ import org.thoughtcrime.securesms.components.StickerView;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
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.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.MmsDownloadJob;
import org.thoughtcrime.securesms.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
@ -96,7 +90,6 @@ import org.thoughtcrime.securesms.mms.SlidesClickedListener;
import org.thoughtcrime.securesms.mms.TextSlide;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.stickers.StickerUrl;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.LongClickCopySpan;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
@ -405,15 +398,10 @@ public class ConversationItem extends LinearLayout
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null;
}
private boolean hasSticker(MessageRecord messageRecord) {
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getStickerSlide() != null;
}
private boolean hasOnlyThumbnail(MessageRecord messageRecord) {
return hasThumbnail(messageRecord) &&
!hasAudio(messageRecord) &&
!hasDocument(messageRecord) &&
!hasSticker(messageRecord);
!hasDocument(messageRecord);
}
private boolean hasOnlyDocument(MessageRecord messageRecord) {
@ -421,7 +409,6 @@ public class ConversationItem extends LinearLayout
!hasThumbnail(messageRecord) &&
!hasAudio(messageRecord) &&
hasDocument(messageRecord) &&
!hasSticker(messageRecord) &&
!hasQuote(messageRecord);
}
@ -430,7 +417,6 @@ public class ConversationItem extends LinearLayout
!hasThumbnail(messageRecord) &&
!hasAudio(messageRecord) &&
!hasDocument(messageRecord) &&
!hasSticker(messageRecord) &&
!hasQuote(messageRecord);
}
@ -460,8 +446,7 @@ public class ConversationItem extends LinearLayout
int minWidth = getResources().getDimensionPixelSize(R.dimen.media_bubble_min_width);
return linkPreview.getThumbnail().isPresent() &&
linkPreview.getThumbnail().get().getWidth() >= minWidth &&
!StickerUrl.isValidShareLink(linkPreview.getUrl());
linkPreview.getThumbnail().get().getWidth() >= minWidth;
}
private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery, boolean isGroupThread) {
@ -604,26 +589,6 @@ public class ConversationItem extends LinearLayout
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
footer.setVisibility(VISIBLE);
} else if (hasSticker(messageRecord) && isCaptionlessMms(messageRecord)) {
bodyBubble.setBackgroundColor(Color.TRANSPARENT);
stickerStub.get().setVisibility(View.VISIBLE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
//noinspection ConstantConditions
stickerStub.get().setSticker(glideRequests, ((MmsMessageRecord) messageRecord).getSlideDeck().getStickerSlide());
stickerStub.get().setThumbnailClickListener(new StickerClickListener());
stickerStub.get().setDownloadClickListener(downloadClickListener);
stickerStub.get().setOnLongClickListener(passthroughClickListener);
stickerStub.get().setOnClickListener(passthroughClickListener);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
footer.setVisibility(VISIBLE);
} else if (hasThumbnail(messageRecord)) {
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
@ -878,9 +843,7 @@ public class ConversationItem extends LinearLayout
}
private ConversationItemFooter getActiveFooter(@NonNull MessageRecord messageRecord) {
if (hasSticker(messageRecord)) {
return stickerFooter;
} else if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getDisplayBody(getContext()))) {
if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getDisplayBody(getContext()))) {
return mediaThumbnailStub.get().getFooter();
} else {
return footer;
@ -924,13 +887,8 @@ public class ConversationItem extends LinearLayout
}
private void setGroupAuthorColor(@NonNull MessageRecord messageRecord) {
if (hasSticker(messageRecord)) {
groupSender.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color));
groupSenderProfileName.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_sticker_author_color));
} else {
groupSender.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_primary_color));
groupSenderProfileName.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_primary_color));
}
groupSender.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_primary_color));
groupSenderProfileName.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_primary_color));
}
private void setAuthor(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
@ -1116,21 +1074,13 @@ public class ConversationItem extends LinearLayout
@Override
public void onClick(View v, final List<Slide> slides) {
Log.i(TAG, "onClick() for attachment download");
if (messageRecord.isMmsNotification()) {
Log.i(TAG, "Scheduling MMS attachment download");
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MmsDownloadJob(messageRecord.getId(),
messageRecord.getThreadId(), false));
} else {
Log.i(TAG, "Scheduling push attachment downloads for " + slides.size() + " items");
Log.i(TAG, "Scheduling push attachment downloads for " + slides.size() + " items");
for (Slide slide : slides) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(messageRecord.getId(),
((DatabaseAttachment)slide.asAttachment()).getAttachmentId(), true));
}
for (Slide slide : slides) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(messageRecord.getId(),
((DatabaseAttachment)slide.asAttachment()).getAttachmentId(), true));
}
}
}
@ -1154,9 +1104,6 @@ public class ConversationItem extends LinearLayout
public void onClick(View v, Slide slide) {
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
performClick();
} else if (eventListener != null && hasSticker(messageRecord)){
//noinspection ConstantConditions
eventListener.onStickerClicked(((MmsMessageRecord) messageRecord).getSlideDeck().getStickerSlide().asAttachment().getSticker());
}
}
}
@ -1229,58 +1176,7 @@ public class ConversationItem extends LinearLayout
intent.putExtra(MessageDetailsActivity.IS_PUSH_GROUP_EXTRA, groupThread && messageRecord.isPush());
intent.putExtra(MessageDetailsActivity.ADDRESS_EXTRA, conversationRecipient.getAddress());
context.startActivity(intent);
} else if (!messageRecord.isOutgoing() && messageRecord.isIdentityMismatchFailure()) {
// handleApproveIdentity();
} else if (messageRecord.isPendingInsecureSmsFallback()) {
handleMessageApproval();
}
}
}
private void handleMessageApproval() {
final int title;
final int message;
if (messageRecord.isMms()) title = R.string.ConversationItem_click_to_approve_unencrypted_mms_dialog_title;
else title = R.string.ConversationItem_click_to_approve_unencrypted_sms_dialog_title;
message = R.string.ConversationItem_click_to_approve_unencrypted_dialog_message;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(title);
if (message > -1) builder.setMessage(message);
builder.setPositiveButton(R.string.yes, (dialogInterface, i) -> {
if (messageRecord.isMms()) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
database.markAsInsecure(messageRecord.getId());
database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(messageRecord.getId());
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MmsSendJob(messageRecord.getId()));
} else {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
database.markAsInsecure(messageRecord.getId());
database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(messageRecord.getId());
ApplicationContext.getInstance(context)
.getJobManager()
.add(new SmsSendJob(context, messageRecord.getId(),
messageRecord.getIndividualRecipient().getAddress().serialize()));
}
});
builder.setNegativeButton(R.string.no, (dialogInterface, i) -> {
if (messageRecord.isMms()) {
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageRecord.getId());
} else {
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageRecord.getId());
}
});
builder.show();
}
}

@ -1,87 +0,0 @@
package org.thoughtcrime.securesms.conversation;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideRequests;
import java.util.ArrayList;
import java.util.List;
import network.loki.messenger.R;
public class ConversationStickerSuggestionAdapter extends RecyclerView.Adapter<ConversationStickerSuggestionAdapter.StickerSuggestionViewHolder> {
private final GlideRequests glideRequests;
private final EventListener eventListener;
private final List<StickerRecord> stickers;
public ConversationStickerSuggestionAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
this.glideRequests = glideRequests;
this.eventListener = eventListener;
this.stickers = new ArrayList<>();
}
@Override
public @NonNull StickerSuggestionViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new StickerSuggestionViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.sticker_suggestion_list_item, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull StickerSuggestionViewHolder viewHolder, int i) {
viewHolder.bind(glideRequests, eventListener, stickers.get(i));
}
@Override
public void onViewRecycled(@NonNull StickerSuggestionViewHolder holder) {
holder.recycle();
}
@Override
public int getItemCount() {
return stickers.size();
}
public void setStickers(@NonNull List<StickerRecord> stickers) {
this.stickers.clear();
this.stickers.addAll(stickers);
notifyDataSetChanged();
}
static class StickerSuggestionViewHolder extends RecyclerView.ViewHolder {
private final ImageView image;
StickerSuggestionViewHolder(@NonNull View itemView) {
super(itemView);
this.image = itemView.findViewById(R.id.sticker_suggestion_item_image);
}
void bind(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener, @NonNull StickerRecord sticker) {
glideRequests.load(new DecryptableUri(sticker.getUri()))
.transition(DrawableTransitionOptions.withCrossFade())
.into(image);
itemView.setOnClickListener(v -> {
eventListener.onStickerSuggestionClicked(sticker);
});
}
void recycle() {
itemView.setOnClickListener(null);
}
}
public interface EventListener {
void onStickerSuggestionClicked(@NonNull StickerRecord sticker);
}
}

@ -1,85 +0,0 @@
package org.thoughtcrime.securesms.conversation;
import android.app.Application;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.database.ContentObserver;
import android.os.Handler;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import org.thoughtcrime.securesms.database.CursorList;
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
import org.thoughtcrime.securesms.util.CloseableLiveData;
import org.session.libsession.utilities.Throttler;
class ConversationStickerViewModel extends ViewModel {
private static final int SEARCH_LIMIT = 10;
private final Application application;
private final StickerSearchRepository repository;
private final CloseableLiveData<CursorList<StickerRecord>> stickers;
private final MutableLiveData<Boolean> stickersAvailable;
private final Throttler availabilityThrottler;
private final ContentObserver packObserver;
private ConversationStickerViewModel(@NonNull Application application, @NonNull StickerSearchRepository repository) {
this.application = application;
this.repository = repository;
this.stickers = new CloseableLiveData<>();
this.stickersAvailable = new MutableLiveData<>();
this.availabilityThrottler = new Throttler(500);
this.packObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
availabilityThrottler.publish(() -> repository.getStickerFeatureAvailability(stickersAvailable::postValue));
}
};
application.getContentResolver().registerContentObserver(DatabaseContentProviders.StickerPack.CONTENT_URI, true, packObserver);
}
@NonNull LiveData<CursorList<StickerRecord>> getStickerResults() {
return stickers;
}
@NonNull LiveData<Boolean> getStickersAvailability() {
repository.getStickerFeatureAvailability(stickersAvailable::postValue);
return stickersAvailable;
}
void onInputTextUpdated(@NonNull String text) {
if (TextUtils.isEmpty(text) || text.length() > SEARCH_LIMIT) {
stickers.setValue(CursorList.emptyList());
} else {
repository.searchByEmoji(text, stickers::postValue);
}
}
@Override
protected void onCleared() {
stickers.close();
application.getContentResolver().unregisterContentObserver(packObserver);
}
static class Factory extends ViewModelProvider.NewInstanceFactory {
private final Application application;
private final StickerSearchRepository repository;
public Factory(@NonNull Application application, @NonNull StickerSearchRepository repository) {
this.application = application;
this.repository = repository;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection ConstantConditions
return modelClass.cast(new ConversationStickerViewModel(application, repository));
}
}
}

@ -1,61 +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.session.libsignal.libsignal.ecc.ECPrivateKey;
import org.session.libsignal.libsignal.ecc.ECPublicKey;
/**
* 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 the ECC keypair.
*
* @author Moxie Marlinspike
*
*/
public class AsymmetricMasterSecret {
private final ECPublicKey djbPublicKey;
private final ECPrivateKey djbPrivateKey;
public AsymmetricMasterSecret(ECPublicKey djbPublicKey, ECPrivateKey djbPrivateKey)
{
this.djbPublicKey = djbPublicKey;
this.djbPrivateKey = djbPrivateKey;
}
public ECPublicKey getDjbPublicKey() {
return djbPublicKey;
}
public ECPrivateKey getPrivateKey() {
return djbPrivateKey;
}
}

@ -1,41 +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;
public class InvalidPassphraseException extends Exception {
public InvalidPassphraseException() {
super();
// TODO Auto-generated constructor stub
}
public InvalidPassphraseException(String detailMessage) {
super(detailMessage);
// TODO Auto-generated constructor stub
}
public InvalidPassphraseException(Throwable throwable) {
super(throwable);
// TODO Auto-generated constructor stub
}
public InvalidPassphraseException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
// TODO Auto-generated constructor stub
}
}

@ -1,226 +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.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.ecc.Curve;
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.utilities.Base64;
import org.session.libsignal.utilities.Hex;
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;
}
}

@ -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;
}
}

@ -1,375 +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.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 org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.utilities.Base64;
import org.session.libsession.utilities.Util;
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;
}
}

@ -1,104 +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.session.libsignal.utilities.logging.Log;
import org.session.libsignal.utilities.Hex;
import org.session.libsession.utilities.Util;
import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.ecc.Curve;
import org.session.libsignal.libsignal.ecc.ECPublicKey;
import org.session.libsession.utilities.Conversions;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class PublicKey {
private static final String TAG = PublicKey.class.getSimpleName();
public static final int KEY_SIZE = 3 + ECPublicKey.KEY_SIZE;
private final ECPublicKey publicKey;
private int id;
public PublicKey(PublicKey publicKey) {
this.id = publicKey.id;
// FIXME :: This not strictly an accurate copy constructor.
this.publicKey = publicKey.publicKey;
}
public PublicKey(int id, ECPublicKey publicKey) {
this.publicKey = publicKey;
this.id = id;
}
public PublicKey(byte[] bytes, int offset) throws InvalidKeyException {
Log.i(TAG, "PublicKey Length: " + (bytes.length - offset));
if ((bytes.length - offset) < KEY_SIZE)
throw new InvalidKeyException("Provided bytes are too short.");
this.id = Conversions.byteArrayToMedium(bytes, offset);
this.publicKey = Curve.decodePoint(bytes, offset + 3);
}
public PublicKey(byte[] bytes) throws InvalidKeyException {
this(bytes, 0);
}
public int getType() {
return publicKey.getType();
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public ECPublicKey getKey() {
return publicKey;
}
public String getFingerprint() {
return Hex.toString(getFingerprintBytes());
}
public byte[] getFingerprintBytes() {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
return md.digest(serialize());
} catch (NoSuchAlgorithmException nsae) {
Log.w("LocalKeyPair", nsae);
throw new IllegalArgumentException("SHA-1 isn't supported!");
}
}
public byte[] serialize() {
byte[] keyIdBytes = Conversions.mediumToByteArray(id);
byte[] serializedPoint = publicKey.serialize();
Log.i(TAG, "Serializing public key point: " + Hex.toString(serializedPoint));
return Util.combine(keyIdBytes, serializedPoint);
}
}

@ -1,25 +0,0 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.content.Intent;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.service.KeyCachingService;
/**
* This class processes key exchange interactions.
*
* @author Moxie Marlinspike
*/
public class SecurityEvent {
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
public static void broadcastSecurityUpdateEvent(Context context) {
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
intent.setPackage(context.getPackageName());
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
}
}

@ -1,30 +0,0 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.session.libsession.messaging.threads.Address;
import org.session.libsignal.libsignal.SignalProtocolAddress;
import org.session.libsignal.libsignal.state.SessionStore;
import org.session.libsignal.service.api.push.SignalServiceAddress;
public class SessionUtil {
public static boolean hasSession(Context context, @NonNull Address address) {
SessionStore sessionStore = new TextSecureSessionStore(context);
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(address.serialize(), SignalServiceAddress.DEFAULT_DEVICE_ID);
return sessionStore.containsSession(axolotlAddress);
}
public static void archiveSiblingSessions(Context context, SignalProtocolAddress address) {
TextSecureSessionStore sessionStore = new TextSecureSessionStore(context);
sessionStore.archiveSiblingSessions(address);
}
public static void archiveAllSessions(Context context) {
new TextSecureSessionStore(context).archiveAllSessions();
}
}

@ -8,90 +8,57 @@ import androidx.annotation.WorkerThread;
import org.session.libsession.utilities.preferences.ProfileKeyUtil;
import org.session.libsignal.metadata.SignalProtos;
import org.session.libsignal.metadata.certificate.CertificateValidator;
import org.session.libsignal.metadata.certificate.InvalidCertificateException;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.session.libsignal.service.api.crypto.UnidentifiedAccessPair;
import org.session.libsignal.service.api.push.SignalServiceAddress;
public class UnidentifiedAccessUtil {
private static final String TAG = UnidentifiedAccessUtil.class.getSimpleName();
public static CertificateValidator getCertificateValidator() {
return new CertificateValidator();
}
@WorkerThread
public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context,
public static Optional<UnidentifiedAccess> getAccessFor(@NonNull Context context,
@NonNull Recipient recipient)
{
if (!TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
Log.i(TAG, "Unidentified delivery is disabled. [other]");
return Optional.absent();
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate(context);
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
try {
byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate(context);
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
Log.i(TAG, "Their access key present? " + (theirUnidentifiedAccessKey != null) +
" | Our access key present? " + (ourUnidentifiedAccessKey != null) +
" | Our certificate present? " + (ourUnidentifiedAccessCertificate != null));
if (theirUnidentifiedAccessKey != null &&
ourUnidentifiedAccessKey != null &&
ourUnidentifiedAccessCertificate != null)
{
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(theirUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate),
new UnidentifiedAccess(ourUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate)));
}
return Optional.absent();
} catch (InvalidCertificateException e) {
Log.w(TAG, e);
return Optional.absent();
Log.i(TAG, "Their access key present? " + (theirUnidentifiedAccessKey != null) +
" | Our access key present? " + (ourUnidentifiedAccessKey != null) +
" | Our certificate present? " + (ourUnidentifiedAccessCertificate != null));
if (theirUnidentifiedAccessKey != null &&
ourUnidentifiedAccessKey != null &&
ourUnidentifiedAccessCertificate != null)
{
return Optional.of(new UnidentifiedAccess(theirUnidentifiedAccessKey));
}
return Optional.absent();
}
public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
if (!TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
Log.i(TAG, "Unidentified delivery is disabled. [self]");
return Optional.absent();
public static Optional<UnidentifiedAccess> getAccessForSync(@NonNull Context context) {
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate(context);
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
try {
byte[] ourUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(context);
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate(context);
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
ourUnidentifiedAccessKey = Util.getSecretBytes(16);
}
if (ourUnidentifiedAccessKey != null && ourUnidentifiedAccessCertificate != null) {
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(ourUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate),
new UnidentifiedAccess(ourUnidentifiedAccessKey,
ourUnidentifiedAccessCertificate)));
}
return Optional.absent();
} catch (InvalidCertificateException e) {
Log.w(TAG, e);
return Optional.absent();
if (ourUnidentifiedAccessKey != null && ourUnidentifiedAccessCertificate != null) {
return Optional.of(new UnidentifiedAccess(ourUnidentifiedAccessKey));
}
return Optional.absent();
}
public static @NonNull byte[] getSelfUnidentifiedAccessKey(@NonNull Context context) {

@ -2,150 +2,20 @@ package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.IdentityKeyPair;
import org.session.libsignal.libsignal.InvalidKeyIdException;
import org.session.libsignal.libsignal.SignalProtocolAddress;
import org.session.libsignal.libsignal.state.IdentityKeyStore;
import org.session.libsignal.libsignal.state.PreKeyRecord;
import org.session.libsignal.libsignal.state.PreKeyStore;
import org.session.libsignal.libsignal.state.SessionRecord;
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 {
public class SignalProtocolStoreImpl implements 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.sessionStore = new TextSecureSessionStore(context);
this.context = context;
}
@Override
public IdentityKeyPair 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();
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey 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);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
// return identityKeyStore.getIdentity(address);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
// return preKeyStore.loadPreKey(preKeyId);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public void storePreKey(int preKeyId, PreKeyRecord record) {
// preKeyStore.storePreKey(preKeyId, record);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public boolean containsPreKey(int preKeyId) {
// return preKeyStore.containsPreKey(preKeyId);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public void removePreKey(int preKeyId) {
// preKeyStore.removePreKey(preKeyId);
throw new UnsupportedOperationException("This method will be removed with refactor.");
}
@Override
public SessionRecord loadSession(SignalProtocolAddress axolotlAddress) {
return sessionStore.loadSession(axolotlAddress);
}
@Override
public List<Integer> getSubDeviceSessions(String number) {
return sessionStore.getSubDeviceSessions(number);
}
@Override
public void storeSession(SignalProtocolAddress axolotlAddress, SessionRecord record) {
sessionStore.storeSession(axolotlAddress, record);
}
@Override
public boolean containsSession(SignalProtocolAddress axolotlAddress) {
return sessionStore.containsSession(axolotlAddress);
}
@Override
public void deleteSession(SignalProtocolAddress axolotlAddress) {
sessionStore.deleteSession(axolotlAddress);
}
@Override
public void deleteAllSessions(String number) {
sessionStore.deleteAllSessions(number);
}
@Override
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
throw new UnsupportedOperationException("This method will be removed with refactor.");
// return signedPreKeyStore.loadSignedPreKey(signedPreKeyId);
}
@Override
public List<SignedPreKeyRecord> loadSignedPreKeys() {
throw new UnsupportedOperationException("This method will be removed with refactor.");
// return signedPreKeyStore.loadSignedPreKeys();
}
@Override
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
throw new UnsupportedOperationException("This method will be removed with refactor.");
// signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
}
@Override
public boolean containsSignedPreKey(int signedPreKeyId) {
throw new UnsupportedOperationException("This method will be removed with refactor.");
// return signedPreKeyStore.containsSignedPreKey(signedPreKeyId);
}
@Override
public void removeSignedPreKey(int signedPreKeyId) {
throw new UnsupportedOperationException("This method will be removed with refactor.");
// signedPreKeyStore.removeSignedPreKey(signedPreKeyId);
}
}

@ -1,110 +0,0 @@
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.SessionDatabase;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.libsignal.SignalProtocolAddress;
import org.session.libsignal.libsignal.protocol.CiphertextMessage;
import org.session.libsignal.libsignal.state.SessionRecord;
import org.session.libsignal.libsignal.state.SessionStore;
import java.util.List;
public class TextSecureSessionStore implements SessionStore {
private static final String TAG = TextSecureSessionStore.class.getSimpleName();
private static final Object FILE_LOCK = new Object();
@NonNull private final Context context;
public TextSecureSessionStore(@NonNull Context context) {
this.context = context;
}
@Override
public SessionRecord loadSession(@NonNull SignalProtocolAddress address) {
synchronized (FILE_LOCK) {
SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(Address.Companion.fromSerialized(address.getName()), address.getDeviceId());
if (sessionRecord == null) {
Log.w(TAG, "No existing session information found.");
return new SessionRecord();
}
return sessionRecord;
}
}
@Override
public void storeSession(@NonNull SignalProtocolAddress address, @NonNull SessionRecord record) {
synchronized (FILE_LOCK) {
DatabaseFactory.getSessionDatabase(context).store(Address.Companion.fromSerialized(address.getName()), address.getDeviceId(), record);
}
}
@Override
public boolean containsSession(SignalProtocolAddress address) {
synchronized (FILE_LOCK) {
SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(Address.Companion.fromSerialized(address.getName()), address.getDeviceId());
return sessionRecord != null &&
sessionRecord.getSessionState().hasSenderChain() &&
sessionRecord.getSessionState().getSessionVersion() == CiphertextMessage.CURRENT_VERSION;
}
}
@Override
public void deleteSession(SignalProtocolAddress address) {
synchronized (FILE_LOCK) {
DatabaseFactory.getSessionDatabase(context).delete(Address.Companion.fromSerialized(address.getName()), address.getDeviceId());
}
}
@Override
public void deleteAllSessions(String name) {
synchronized (FILE_LOCK) {
DatabaseFactory.getSessionDatabase(context).deleteAllFor(Address.Companion.fromSerialized(name));
}
}
@Override
public List<Integer> getSubDeviceSessions(String name) {
synchronized (FILE_LOCK) {
return DatabaseFactory.getSessionDatabase(context).getSubDevices(Address.Companion.fromSerialized(name));
}
}
public void archiveSiblingSessions(@NonNull SignalProtocolAddress address) {
synchronized (FILE_LOCK) {
List<SessionDatabase.SessionRow> sessions = DatabaseFactory.getSessionDatabase(context).getAllFor(Address.Companion.fromSerialized(address.getName()));
for (SessionDatabase.SessionRow row : sessions) {
if (row.getDeviceId() != address.getDeviceId()) {
row.getRecord().archiveCurrentState();
storeSession(new SignalProtocolAddress(row.getAddress().serialize(), row.getDeviceId()), row.getRecord());
}
}
}
}
public void archiveAllSessions(@NonNull String hexEncodedPublicKey) {
SignalProtocolAddress address = new SignalProtocolAddress(hexEncodedPublicKey, -1);
archiveSiblingSessions(address);
}
public void archiveAllSessions() {
synchronized (FILE_LOCK) {
List<SessionDatabase.SessionRow> sessions = DatabaseFactory.getSessionDatabase(context).getAll();
for (SessionDatabase.SessionRow row : sessions) {
row.getRecord().archiveCurrentState();
storeSession(new SignalProtocolAddress(row.getAddress().serialize(), row.getDeviceId()), row.getRecord());
}
}
}
}

@ -1,174 +0,0 @@
/**
* Copyright (C) 2014 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.database;
import android.content.Context;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.text.TextUtils;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.mms.LegacyMmsConnection.Apn;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Database to query APN and MMSC information
*/
public class ApnDatabase {
private static final String TAG = ApnDatabase.class.getSimpleName();
private final SQLiteDatabase db;
private final Context context;
private static final String DATABASE_NAME = "apns.db";
private static final String ASSET_PATH = "databases" + File.separator + DATABASE_NAME;
private static final String TABLE_NAME = "apns";
private static final String ID_COLUMN = "_id";
private static final String MCC_MNC_COLUMN = "mccmnc";
private static final String MCC_COLUMN = "mcc";
private static final String MNC_COLUMN = "mnc";
private static final String CARRIER_COLUMN = "carrier";
private static final String APN_COLUMN = "apn";
private static final String MMSC_COLUMN = "mmsc";
private static final String PORT_COLUMN = "port";
private static final String TYPE_COLUMN = "type";
private static final String PROTOCOL_COLUMN = "protocol";
private static final String BEARER_COLUMN = "bearer";
private static final String ROAMING_PROTOCOL_COLUMN = "roaming_protocol";
private static final String CARRIER_ENABLED_COLUMN = "carrier_enabled";
private static final String MMS_PROXY_COLUMN = "mmsproxy";
private static final String MMS_PORT_COLUMN = "mmsport";
private static final String PROXY_COLUMN = "proxy";
private static final String MVNO_MATCH_DATA_COLUMN = "mvno_match_data";
private static final String MVNO_TYPE_COLUMN = "mvno";
private static final String AUTH_TYPE_COLUMN = "authtype";
private static final String USER_COLUMN = "user";
private static final String PASSWORD_COLUMN = "password";
private static final String SERVER_COLUMN = "server";
private static final String BASE_SELECTION = MCC_MNC_COLUMN + " = ?";
private static ApnDatabase instance = null;
public synchronized static ApnDatabase getInstance(Context context) throws IOException {
if (instance == null) instance = new ApnDatabase(context.getApplicationContext());
return instance;
}
private ApnDatabase(final Context context) throws IOException {
this.context = context;
File dbFile = context.getDatabasePath(DATABASE_NAME);
if (!dbFile.getParentFile().exists() && !dbFile.getParentFile().mkdir()) {
throw new IOException("couldn't make databases directory");
}
Util.copy(context.getAssets().open(ASSET_PATH, AssetManager.ACCESS_STREAMING),
new FileOutputStream(dbFile));
try {
this.db = SQLiteDatabase.openDatabase(context.getDatabasePath(DATABASE_NAME).getPath(),
null,
SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
} catch (SQLiteException e) {
throw new IOException(e);
}
}
private Apn getCustomApnParameters() {
String mmsc = TextSecurePreferences.getMmscUrl(context).trim();
if (!TextUtils.isEmpty(mmsc) && !mmsc.startsWith("http"))
mmsc = "http://" + mmsc;
String proxy = TextSecurePreferences.getMmscProxy(context);
String port = TextSecurePreferences.getMmscProxyPort(context);
String user = TextSecurePreferences.getMmscUsername(context);
String pass = TextSecurePreferences.getMmscPassword(context);
return new Apn(mmsc, proxy, port, user, pass);
}
public Apn getDefaultApnParameters(String mccmnc, String apn) {
if (mccmnc == null) {
Log.w(TAG, "mccmnc was null, returning null");
return Apn.EMPTY;
}
Cursor cursor = null;
try {
if (apn != null) {
Log.d(TAG, "Querying table for MCC+MNC " + mccmnc + " and APN name " + apn);
cursor = db.query(TABLE_NAME, null,
BASE_SELECTION + " AND " + APN_COLUMN + " = ?",
new String[] {mccmnc, apn},
null, null, null);
}
if (cursor == null || !cursor.moveToFirst()) {
if (cursor != null) cursor.close();
Log.d(TAG, "Querying table for MCC+MNC " + mccmnc + " without APN name");
cursor = db.query(TABLE_NAME, null,
BASE_SELECTION,
new String[] {mccmnc},
null, null, null);
}
if (cursor != null && cursor.moveToFirst()) {
Apn params = new Apn(cursor.getString(cursor.getColumnIndexOrThrow(MMSC_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(MMS_PROXY_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(MMS_PORT_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(USER_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(PASSWORD_COLUMN)));
Log.d(TAG, "Returning preferred APN " + params);
return params;
}
Log.w(TAG, "No matching APNs found, returning null");
return Apn.EMPTY;
} finally {
if (cursor != null) cursor.close();
}
}
public Optional<Apn> getMmsConnectionParameters(String mccmnc, String apn) {
Apn customApn = getCustomApnParameters();
Apn defaultApn = getDefaultApnParameters(mccmnc, apn);
Apn result = new Apn(customApn, defaultApn,
TextSecurePreferences.getUseCustomMmsc(context),
TextSecurePreferences.getUseCustomMmscProxy(context),
TextSecurePreferences.getUseCustomMmscProxyPort(context),
TextSecurePreferences.getUseCustomMmscUsername(context),
TextSecurePreferences.getUseCustomMmscPassword(context));
if (TextUtils.isEmpty(result.getMmsc())) return Optional.absent();
else return Optional.of(result);
}
}

@ -51,7 +51,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras;
import org.session.libsession.messaging.sending_receiving.attachments.StickerLocator;
import org.session.libsignal.utilities.JsonUtil;
import org.session.libsession.utilities.Util;
@ -504,7 +503,6 @@ public class AttachmentDatabase extends Database {
mediaStream.getHeight(),
databaseAttachment.isQuote(),
databaseAttachment.getCaption(),
databaseAttachment.getSticker(),
databaseAttachment.getUrl());
}
@ -548,18 +546,6 @@ public class AttachmentDatabase extends Database {
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
}
/**
* Returns (pack_id, pack_key) pairs that are referenced in attachments but not in the stickers
* database.
*/
public @Nullable Cursor getUnavailableStickerPacks() {
String query = "SELECT DISTINCT " + STICKER_PACK_ID + ", " + STICKER_PACK_KEY + " FROM " + TABLE_NAME + " WHERE " + STICKER_PACK_ID + " NOT IN (" +
"SELECT DISTINCT " + StickerDatabase.PACK_ID + " FROM " + StickerDatabase.TABLE_NAME +
")";
return databaseHelper.getReadableDatabase().rawQuery(query, null);
}
public boolean hasStickerAttachments() {
String selection = STICKER_PACK_ID + " NOT NULL";
@ -701,11 +687,7 @@ public class AttachmentDatabase extends Database {
object.getInt(HEIGHT),
object.getInt(QUOTE) == 1,
object.getString(CAPTION),
object.getInt(STICKER_ID) >= 0
? new StickerLocator(object.getString(STICKER_PACK_ID),
object.getString(STICKER_PACK_KEY),
object.getInt(STICKER_ID))
: null, "")); // TODO: Not sure if this will break something
"")); // TODO: Not sure if this will break something
}
}
@ -731,11 +713,6 @@ public class AttachmentDatabase extends Database {
cursor.getInt(cursor.getColumnIndexOrThrow(HEIGHT)),
cursor.getInt(cursor.getColumnIndexOrThrow(QUOTE)) == 1,
cursor.getString(cursor.getColumnIndexOrThrow(CAPTION)),
cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)) >= 0
? new StickerLocator(cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_ID)),
cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_KEY)),
cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)))
: null,
urlIndex > 0 ? cursor.getString(urlIndex) : ""));
}
} catch (JSONException e) {
@ -777,19 +754,12 @@ public class AttachmentDatabase extends Database {
contentValues.put(CAPTION, attachment.getCaption());
contentValues.put(URL, attachment.getUrl());
if (attachment.isSticker()) {
contentValues.put(STICKER_PACK_ID, attachment.getSticker().getPackId());
contentValues.put(STICKER_PACK_KEY, attachment.getSticker().getPackKey());
contentValues.put(STICKER_ID, attachment.getSticker().getStickerId());
}
if (dataInfo != null) {
contentValues.put(DATA, dataInfo.file.getAbsolutePath());
contentValues.put(SIZE, dataInfo.length);
contentValues.put(DATA_RANDOM, dataInfo.random);
}
boolean notifyPacks = attachment.isSticker() && !hasStickerAttachments();
long rowId = database.insert(TABLE_NAME, null, contentValues);
AttachmentId attachmentId = new AttachmentId(rowId, uniqueId);
Uri thumbnailUri = attachment.getThumbnailUri();
@ -829,10 +799,6 @@ public class AttachmentDatabase extends Database {
}
}
if (notifyPacks) {
notifyStickerPackListeners();
}
return attachmentId;
}

@ -66,14 +66,6 @@ public abstract class Database {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.ConversationList.CONTENT_URI);
}
protected void setNotifyStickerListeners(Cursor cursor) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Sticker.CONTENT_URI);
}
protected void setNotifyStickerPackListeners(Cursor cursor) {
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.StickerPack.CONTENT_URI);
}
protected void registerAttachmentListeners(@NonNull ContentObserver observer) {
context.getContentResolver().registerContentObserver(DatabaseContentProviders.Attachment.CONTENT_URI,
true,

@ -34,7 +34,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.SessionJobDatabase;
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
public class DatabaseFactory {
@ -54,12 +53,8 @@ public class DatabaseFactory {
private final GroupDatabase groupDatabase;
private final RecipientDatabase recipientDatabase;
private final GroupReceiptDatabase groupReceiptDatabase;
private final OneTimePreKeyDatabase preKeyDatabase;
private final SignedPreKeyDatabase signedPreKeyDatabase;
private final SessionDatabase sessionDatabase;
private final SearchDatabase searchDatabase;
private final JobDatabase jobDatabase;
private final StickerDatabase stickerDatabase;
// Loki
private final LokiAPIDatabase lokiAPIDatabase;
@ -67,7 +62,6 @@ public class DatabaseFactory {
private final LokiThreadDatabase lokiThreadDatabase;
private final LokiUserDatabase lokiUserDatabase;
private final LokiBackupFilesDatabase lokiBackupFilesDatabase;
private final SharedSenderKeysDatabase sskDatabase;
private final SessionJobDatabase sessionJobDatabase;
// Refactor
@ -127,18 +121,6 @@ public class DatabaseFactory {
return getInstance(context).groupReceiptDatabase;
}
public static OneTimePreKeyDatabase getPreKeyDatabase(Context context) {
return getInstance(context).preKeyDatabase;
}
public static SignedPreKeyDatabase getSignedPreKeyDatabase(Context context) {
return getInstance(context).signedPreKeyDatabase;
}
public static SessionDatabase getSessionDatabase(Context context) {
return getInstance(context).sessionDatabase;
}
public static SearchDatabase getSearchDatabase(Context context) {
return getInstance(context).searchDatabase;
}
@ -147,10 +129,6 @@ public class DatabaseFactory {
return getInstance(context).jobDatabase;
}
public static StickerDatabase getStickerDatabase(Context context) {
return getInstance(context).stickerDatabase;
}
public static SQLiteDatabase getBackupDatabase(Context context) {
return getInstance(context).databaseHelper.getReadableDatabase();
}
@ -176,10 +154,6 @@ public class DatabaseFactory {
return getInstance(context).lokiBackupFilesDatabase;
}
public static SharedSenderKeysDatabase getSSKDatabase(Context context) {
return getInstance(context).sskDatabase;
}
public static SessionJobDatabase getSessionJobDatabase(Context context) {
return getInstance(context).sessionJobDatabase;
}
@ -218,18 +192,13 @@ public class DatabaseFactory {
this.groupDatabase = new GroupDatabase(context, databaseHelper);
this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
this.preKeyDatabase = new OneTimePreKeyDatabase(context, databaseHelper);
this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper);
this.sessionDatabase = new SessionDatabase(context, databaseHelper);
this.searchDatabase = new SearchDatabase(context, databaseHelper);
this.jobDatabase = new JobDatabase(context, databaseHelper);
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
this.lokiAPIDatabase = new LokiAPIDatabase(context, databaseHelper);
this.lokiMessageDatabase = new LokiMessageDatabase(context, databaseHelper);
this.lokiThreadDatabase = new LokiThreadDatabase(context, databaseHelper);
this.lokiUserDatabase = new LokiUserDatabase(context, databaseHelper);
this.lokiBackupFilesDatabase = new LokiBackupFilesDatabase(context, databaseHelper);
this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper);
this.storage = new Storage(context, databaseHelper);
this.attachmentProvider = new DatabaseAttachmentProvider(context, databaseHelper);
this.sessionJobDatabase = new SessionJobDatabase(context, databaseHelper);

@ -100,7 +100,6 @@ public class DraftDatabase extends Database {
public static final String IMAGE = "image";
public static final String VIDEO = "video";
public static final String AUDIO = "audio";
public static final String LOCATION = "location";
public static final String QUOTE = "quote";
private final String type;
@ -125,7 +124,6 @@ public class DraftDatabase extends Database {
case IMAGE: return context.getString(R.string.DraftDatabase_Draft_image_snippet);
case VIDEO: return context.getString(R.string.DraftDatabase_Draft_video_snippet);
case AUDIO: return context.getString(R.string.DraftDatabase_Draft_audio_snippet);
case LOCATION: return context.getString(R.string.DraftDatabase_Draft_location_snippet);
case QUOTE: return context.getString(R.string.DraftDatabase_Draft_quote_snippet);
default: return null;
}

@ -129,7 +129,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {GROUP_ID},
MEMBERS + " = ? AND " + MMS + " = ?",
new String[] {Address.Companion.toSerializedList(members, ','), "1"},
new String[] {Address.toSerializedList(members, ','), "1"},
null, null, null);
try {
if (cursor != null && cursor.moveToNext()) {
@ -177,24 +177,6 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
return recipients;
}
public boolean isClosedGroupMember(String hexEncodedPublicKey) {
try {
Address address = Address.Companion.fromSerialized(hexEncodedPublicKey);
Reader reader = DatabaseFactory.getGroupDatabase(context).getGroups();
GroupRecord record;
while ((record = reader.getNext()) != null) {
if (record.isClosedGroup() && record.getMembers().contains(address)) {
return true;
}
}
return false;
} catch (Exception e) {
return false;
}
}
public long create(@NonNull String groupId, @Nullable String title, @NonNull List<Address> members,
@Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay, @Nullable List<Address> admins, @NonNull Long formationTimestamp)
{
@ -203,7 +185,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
ContentValues contentValues = new ContentValues();
contentValues.put(GROUP_ID, groupId);
contentValues.put(TITLE, title);
contentValues.put(MEMBERS, Address.Companion.toSerializedList(members, ','));
contentValues.put(MEMBERS, Address.toSerializedList(members, ','));
if (avatar != null) {
contentValues.put(AVATAR_ID, avatar.getId());
@ -219,12 +201,12 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
contentValues.put(MMS, GroupUtil.isMmsGroup(groupId));
if (admins != null) {
contentValues.put(ADMINS, Address.Companion.toSerializedList(admins, ','));
contentValues.put(ADMINS, Address.toSerializedList(admins, ','));
}
long threadId = databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
Recipient.applyCached(Address.Companion.fromSerialized(groupId), recipient -> {
Recipient.applyCached(Address.fromSerialized(groupId), recipient -> {
recipient.setName(title);
recipient.setGroupAvatarId(avatar != null ? avatar.getId() : null);
recipient.setParticipants(Stream.of(members).map(memberAddress -> Recipient.from(context, memberAddress, true)).toList());
@ -238,7 +220,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
int result = databaseHelper.getWritableDatabase().delete(TABLE_NAME, GROUP_ID + " = ?", new String[]{groupId});
if (result > 0) {
Recipient.removeCached(Address.Companion.fromSerialized(groupId));
Recipient.removeCached(Address.fromSerialized(groupId));
notifyConversationListListeners();
return true;
} else {
@ -262,7 +244,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
GROUP_ID + " = ?",
new String[] {groupId});
Recipient.applyCached(Address.Companion.fromSerialized(groupId), recipient -> {
Recipient.applyCached(Address.fromSerialized(groupId), recipient -> {
recipient.setName(title);
recipient.setGroupAvatarId(avatar != null ? avatar.getId() : null);
});
@ -277,7 +259,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
new String[] {groupID});
Recipient recipient = Recipient.from(context, Address.Companion.fromSerialized(groupID), false);
Recipient recipient = Recipient.from(context, Address.fromSerialized(groupID), false);
recipient.setName(newValue);
}
@ -300,20 +282,20 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
new String[] {groupID});
Recipient.applyCached(Address.Companion.fromSerialized(groupID), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId));
Recipient.applyCached(Address.fromSerialized(groupID), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId));
}
public void updateMembers(String groupId, List<Address> members) {
Collections.sort(members);
ContentValues contents = new ContentValues();
contents.put(MEMBERS, Address.Companion.toSerializedList(members, ','));
contents.put(MEMBERS, Address.toSerializedList(members, ','));
contents.put(ACTIVE, 1);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[] {groupId});
Recipient.applyCached(Address.Companion.fromSerialized(groupId), recipient -> {
Recipient.applyCached(Address.fromSerialized(groupId), recipient -> {
recipient.setParticipants(Stream.of(members).map(a -> Recipient.from(context, a, false)).toList());
});
}
@ -322,7 +304,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
Collections.sort(admins);
ContentValues contents = new ContentValues();
contents.put(ADMINS, Address.Companion.toSerializedList(admins, ','));
contents.put(ADMINS, Address.toSerializedList(admins, ','));
contents.put(ACTIVE, 1);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId});
@ -333,12 +315,12 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
currentMembers.remove(source);
ContentValues contents = new ContentValues();
contents.put(MEMBERS, Address.Companion.toSerializedList(currentMembers, ','));
contents.put(MEMBERS, Address.toSerializedList(currentMembers, ','));
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[] {groupId});
Recipient.applyCached(Address.Companion.fromSerialized(groupId), recipient -> {
Recipient.applyCached(Address.fromSerialized(groupId), recipient -> {
List<Recipient> current = recipient.getParticipants();
Recipient removal = Recipient.from(context, source, false);
@ -358,7 +340,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
if (cursor != null && cursor.moveToFirst()) {
String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS));
return Address.Companion.fromSerializedList(serializedMembers, ',');
return Address.fromSerializedList(serializedMembers, ',');
}
return new LinkedList<>();

@ -82,7 +82,7 @@ public class GroupReceiptDatabase extends Database {
try (Cursor cursor = db.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {String.valueOf(mmsId)}, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
results.add(new GroupReceiptInfo(Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))),
results.add(new GroupReceiptInfo(Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))),
cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)),
cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)),
cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED)) == 1));

@ -108,7 +108,7 @@ public class MediaDatabase extends Database {
Address address = null;
if (serializedAddress != null) {
address = Address.Companion.fromSerialized(serializedAddress);
address = Address.fromSerialized(serializedAddress);
}
long date;

@ -206,22 +206,6 @@ public class MmsDatabase extends MessagingDatabase {
return 0;
}
public long getIDForMessageAtIndex(long threadID, int index) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, THREAD_ID + " = ?", new String[] { threadID + "" }, null, null, null);
if (cursor != null && cursor.moveToPosition(index)) {
return cursor.getLong(0);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return -1;
}
public void addFailures(long messageId, List<NetworkFailure> failure) {
try {
addToDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList.class);
@ -268,7 +252,7 @@ public class MmsDatabase extends MessagingDatabase {
while (cursor.moveToNext()) {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)))) {
Address theirAddress = Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
Address theirAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
Address ourAddress = messageId.getAddress();
String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT;
@ -333,7 +317,7 @@ public class MmsDatabase extends MessagingDatabase {
String fromString = notification.getFrom() != null && notification.getFrom().getTextString() != null
? Util.toIsoString(notification.getFrom().getTextString())
: "";
Recipient recipient = Recipient.from(context, Address.Companion.fromExternal(context, fromString), false);
Recipient recipient = Recipient.from(context, Address.fromExternal(context, fromString), false);
return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
}
@ -367,17 +351,6 @@ public class MmsDatabase extends MessagingDatabase {
}
}
public void markAsOutbox(long messageId) {
long threadId = getThreadIdForMessage(messageId);
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_OUTBOX_TYPE, Optional.of(threadId));
}
public void markAsForcedSms(long messageId) {
long threadId = getThreadIdForMessage(messageId);
updateMailboxBitmask(messageId, Types.PUSH_MESSAGE_BIT, Types.MESSAGE_FORCE_SMS_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
public void markAsPendingInsecureSmsFallback(long messageId) {
long threadId = getThreadIdForMessage(messageId);
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK, Optional.of(threadId));
@ -403,47 +376,6 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
}
public void markDownloadState(long messageId, long state) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(STATUS, state);
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {messageId + ""});
notifyConversationListeners(getThreadIdForMessage(messageId));
}
public void markAsNoSession(long messageId, long threadId) {
updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
// public void markAsSecure(long messageId) {
// updateMailboxBitmask(messageId, 0, Types.SECURE_MESSAGE_BIT, Optional.<Long>absent());
// }
public void markAsInsecure(long messageId) {
updateMailboxBitmask(messageId, Types.SECURE_MESSAGE_BIT, 0, Optional.<Long>absent());
}
// public void markAsPush(long messageId) {
// updateMailboxBitmask(messageId, 0, Types.PUSH_MESSAGE_BIT, Optional.<Long>absent());
// }
public void markAsDecryptFailed(long messageId, long threadId) {
updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
public void markAsDecryptDuplicate(long messageId, long threadId) {
updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_DUPLICATE_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
public void markAsLegacyVersion(long messageId, long threadId) {
updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
@Override
public void markUnidentified(long messageId, boolean unidentified) {
ContentValues contentValues = new ContentValues();
@ -500,7 +432,7 @@ public class MmsDatabase extends MessagingDatabase {
while(cursor != null && cursor.moveToNext()) {
if (Types.isSecureType(cursor.getLong(3))) {
SyncMessageId syncMessageId = new SyncMessageId(Address.Companion.fromSerialized(cursor.getString(1)), cursor.getLong(2));
SyncMessageId syncMessageId = new SyncMessageId(Address.fromSerialized(cursor.getString(1)), cursor.getLong(2));
ExpirationInfo expirationInfo = new ExpirationInfo(cursor.getLong(0), cursor.getLong(4), cursor.getLong(5), true);
result.add(new MarkedMessageInfo(syncMessageId, expirationInfo));
@ -520,91 +452,6 @@ public class MmsDatabase extends MessagingDatabase {
return result;
}
public List<Pair<Long, Long>> setTimestampRead(SyncMessageId messageId, long proposedExpireStarted) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
List<Pair<Long, Long>> expiring = new LinkedList<>();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED, ADDRESS}, DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())}, null, null, null, null);
while (cursor.moveToNext()) {
Address theirAddress = Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
Address ourAddress = messageId.getAddress();
if (ourAddress.equals(theirAddress) || theirAddress.isGroup()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
ContentValues values = new ContentValues();
values.put(READ, 1);
if (expiresIn > 0) {
values.put(EXPIRE_STARTED, expireStarted);
expiring.add(new Pair<>(id, expiresIn));
}
database.update(TABLE_NAME, values, ID_WHERE, new String[]{String.valueOf(id)});
DatabaseFactory.getThreadDatabase(context).updateReadState(threadId);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
notifyConversationListeners(threadId);
}
}
} finally {
if (cursor != null)
cursor.close();
}
return expiring;
}
public void updateMessageBody(long messageId, String body) {
long type = 0;
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
}
private Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
MESSAGE_BOX + " = (" + MESSAGE_BOX + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
"WHERE " + ID + " = ?",
new String[] {body, messageId + ""});
long threadId = getThreadIdForMessage(messageId);
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
notifyConversationListListeners();
return new Pair<>(messageId, threadId);
}
public Optional<MmsNotificationInfo> getNotification(long messageId) {
Cursor cursor = null;
try {
cursor = rawQuery(RAW_ID_WHERE, new String[] {String.valueOf(messageId)});
if (cursor != null && cursor.moveToNext()) {
return Optional.of(new MmsNotificationInfo(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
cursor.getString(cursor.getColumnIndexOrThrow(TRANSACTION_ID)),
cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID))));
} else {
return Optional.absent();
}
} finally {
if (cursor != null)
cursor.close();
}
}
public OutgoingMediaMessage getOutgoingMessage(long messageId)
throws MmsException, NoSuchMessageException
{
@ -642,13 +489,13 @@ public class MmsDatabase extends MessagingDatabase {
.filterNot(previewAttachments::contains)
.map(a -> (Attachment)a).toList();
Recipient recipient = Recipient.from(context, Address.Companion.fromSerialized(address), false);
Recipient recipient = Recipient.from(context, Address.fromSerialized(address), false);
List<NetworkFailure> networkFailures = new LinkedList<>();
List<IdentityKeyMismatch> mismatches = new LinkedList<>();
QuoteModel quote = null;
if (quoteId > 0 && (!TextUtils.isEmpty(quoteText) || !quoteAttachments.isEmpty())) {
quote = new QuoteModel(quoteId, Address.Companion.fromSerialized(quoteAuthor), quoteText, quoteMissing, quoteAttachments);
quote = new QuoteModel(quoteId, Address.fromSerialized(quoteAuthor), quoteText, quoteMissing, quoteAttachments);
}
if (!TextUtils.isEmpty(mismatchDocument)) {
@ -766,56 +613,6 @@ public class MmsDatabase extends MessagingDatabase {
return Collections.emptyList();
}
public long copyMessageInbox(long messageId) throws MmsException {
try {
OutgoingMediaMessage request = getOutgoingMessage(messageId);
ContentValues contentValues = new ContentValues();
contentValues.put(ADDRESS, request.getRecipient().getAddress().serialize());
contentValues.put(DATE_SENT, request.getSentTimeMillis());
contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT);
contentValues.put(THREAD_ID, getThreadIdForMessage(messageId));
contentValues.put(READ, 1);
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
contentValues.put(EXPIRES_IN, request.getExpiresIn());
List<Attachment> attachments = new LinkedList<>();
for (Attachment attachment : request.getAttachments()) {
DatabaseAttachment databaseAttachment = (DatabaseAttachment)attachment;
attachments.add(new DatabaseAttachment(databaseAttachment.getAttachmentId(),
databaseAttachment.getMmsId(),
databaseAttachment.hasData(),
databaseAttachment.hasThumbnail(),
databaseAttachment.getContentType(),
AttachmentDatabase.TRANSFER_PROGRESS_DONE,
databaseAttachment.getSize(),
databaseAttachment.getFileName(),
databaseAttachment.getLocation(),
databaseAttachment.getKey(),
databaseAttachment.getRelay(),
databaseAttachment.getDigest(),
databaseAttachment.getFastPreflightId(),
databaseAttachment.isVoiceNote(),
databaseAttachment.getWidth(),
databaseAttachment.getHeight(),
databaseAttachment.isQuote(),
databaseAttachment.getCaption(),
databaseAttachment.getSticker(),
databaseAttachment.getUrl()));
}
return insertMediaMessage(request.getBody(),
attachments,
new LinkedList<>(),
request.getSharedContacts(),
request.getLinkPreviews(),
contentValues,
null);
} catch (NoSuchMessageException e) {
throw new MmsException(e);
}
}
private Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved,
String contentLocation,
long threadId, long mailbox,
@ -885,23 +682,6 @@ public class MmsDatabase extends MessagingDatabase {
return Optional.of(new InsertResult(messageId, threadId));
}
public Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved,
String contentLocation, long threadId)
throws MmsException
{
long type = Types.BASE_INBOX_TYPE;
if (retrieved.isPushMessage()) {
type |= Types.PUSH_MESSAGE_BIT;
}
if (retrieved.isExpirationUpdate()) {
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
}
return insertMessageInbox(retrieved, contentLocation, threadId, type, 0);
}
public Optional<InsertResult> insertSecureDecryptedMessageOutbox(OutgoingMediaMessage retrieved, long threadId, long serverTimestamp)
throws MmsException
{
@ -938,52 +718,6 @@ public class MmsDatabase extends MessagingDatabase {
return insertSecureDecryptedMessageInbox(retrieved, threadId, 0);
}
public Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification, int subscriptionId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long threadId = getThreadIdFor(notification);
ContentValues contentValues = new ContentValues();
ContentValuesBuilder contentBuilder = new ContentValuesBuilder(contentValues);
Log.i(TAG, "Message received type: " + notification.getMessageType());
contentBuilder.add(CONTENT_LOCATION, notification.getContentLocation());
contentBuilder.add(DATE_SENT, System.currentTimeMillis());
contentBuilder.add(EXPIRY, notification.getExpiry());
contentBuilder.add(MESSAGE_SIZE, notification.getMessageSize());
contentBuilder.add(TRANSACTION_ID, notification.getTransactionId());
contentBuilder.add(MESSAGE_TYPE, notification.getMessageType());
if (notification.getFrom() != null) {
contentValues.put(ADDRESS, Address.Companion.fromExternal(context, Util.toIsoString(notification.getFrom().getTextString())).serialize());
}
contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE);
contentValues.put(THREAD_ID, threadId);
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp());
contentValues.put(READ, Util.isDefaultSmsProvider(context) ? 0 : 1);
contentValues.put(SUBSCRIPTION_ID, subscriptionId);
if (!contentValues.containsKey(DATE_SENT))
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
long messageId = db.insert(TABLE_NAME, null, contentValues);
return new Pair<>(messageId, threadId);
}
public void markIncomingNotificationReceived(long threadId) {
notifyConversationListeners(threadId);
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
if (Util.isDefaultSmsProvider(context)) {
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
}
ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId));
}
public long insertMessageOutbox(@NonNull OutgoingMediaMessage message,
long threadId, boolean forceSms,
@Nullable SmsDatabase.InsertListener insertListener)
@ -1283,21 +1017,6 @@ public class MmsDatabase extends MessagingDatabase {
database.delete(TABLE_NAME, null, null);
}
public Cursor getCarrierMmsInformation(String apn) {
Uri uri = Uri.withAppendedPath(Uri.parse("content://telephony/carriers"), "current");
String selection = TextUtils.isEmpty(apn) ? null : "apn = ?";
String[] selectionArgs = TextUtils.isEmpty(apn) ? null : new String[] {apn.trim()};
try {
return context.getContentResolver().query(uri, null, selection, selectionArgs, null);
} catch (NullPointerException npe) {
// NOTE - This is dumb, but on some devices there's an NPE in the Android framework
// for the provider of this call, which gets rethrown back to here through a binder
// call.
throw new IllegalArgumentException(npe);
}
}
public void beginTransaction() {
databaseHelper.getWritableDatabase().beginTransaction();
}
@ -1322,39 +1041,6 @@ public class MmsDatabase extends MessagingDatabase {
public static final int DOWNLOAD_INITIALIZED = 1;
public static final int DOWNLOAD_NO_CONNECTIVITY = 2;
public static final int DOWNLOAD_CONNECTING = 3;
public static final int DOWNLOAD_SOFT_FAILURE = 4;
public static final int DOWNLOAD_HARD_FAILURE = 5;
public static final int DOWNLOAD_APN_UNAVAILABLE = 6;
}
public static class MmsNotificationInfo {
private final Address from;
private final String contentLocation;
private final String transactionId;
private final int subscriptionId;
MmsNotificationInfo(@Nullable String from, String contentLocation, String transactionId, int subscriptionId) {
this.from = from == null ? null : Address.Companion.fromSerialized(from);
this.contentLocation = contentLocation;
this.transactionId = transactionId;
this.subscriptionId = subscriptionId;
}
public String getContentLocation() {
return contentLocation;
}
public String getTransactionId() {
return transactionId;
}
public int getSubscriptionId() {
return subscriptionId;
}
public @Nullable Address getFrom() {
return from;
}
}
public class OutgoingMessageReader {
@ -1507,7 +1193,7 @@ public class MmsDatabase extends MessagingDatabase {
if (TextUtils.isEmpty(serialized) || "insert-address-token".equals(serialized)) {
address = Address.Companion.getUNKNOWN();
} else {
address = Address.Companion.fromSerialized(serialized);
address = Address.fromSerialized(serialized);
}
return Recipient.from(context, address, true);
@ -1554,7 +1240,7 @@ public class MmsDatabase extends MessagingDatabase {
SlideDeck quoteDeck = new SlideDeck(context, quoteAttachments);
if (quoteId > 0 && !TextUtils.isEmpty(quoteAuthor)) {
return new Quote(quoteId, Address.Companion.fromExternal(context, quoteAuthor), quoteText, quoteMissing, quoteDeck);
return new Quote(quoteId, Address.fromExternal(context, quoteAuthor), quoteText, quoteMissing, quoteDeck);
} else {
return null;
}
@ -1567,9 +1253,4 @@ public class MmsDatabase extends MessagingDatabase {
}
}
}
private long generatePduCompatTimestamp() {
final long time = System.currentTimeMillis();
return time - (time % 1000);
}
}

@ -48,7 +48,6 @@ public interface MmsSmsColumns {
OUTGOING_CALL_TYPE};
// Message attributes
protected static final long MESSAGE_ATTRIBUTE_MASK = 0xE0;
protected static final long MESSAGE_FORCE_SMS_BIT = 0x40;
// Key Exchange Information
@ -248,11 +247,6 @@ public interface MmsSmsColumns {
}
public static long translateFromSystemBaseType(long theirType) {
// public static final int NONE_TYPE = 0;
// public static final int INBOX_TYPE = 1;
// public static final int SENT_TYPE = 2;
// public static final int SENT_PENDING = 4;
// public static final int FAILED_TYPE = 5;
switch ((int)theirType) {
case 1: return BASE_INBOX_TYPE;
@ -265,70 +259,6 @@ public interface MmsSmsColumns {
return BASE_INBOX_TYPE;
}
public static int translateToSystemBaseType(long type) {
if (isInboxType(type)) return 1;
else if (isOutgoingMessageType(type)) return 2;
else if (isFailedMessageType(type)) return 5;
return 1;
}
//
//
//
// public static final int NONE_TYPE = 0;
// public static final int INBOX_TYPE = 1;
// public static final int SENT_TYPE = 2;
// public static final int SENT_PENDING = 4;
// public static final int FAILED_TYPE = 5;
//
// public static final int OUTBOX_TYPE = 43; // Messages are stored local encrypted and need delivery.
//
//
// public static final int ENCRYPTING_TYPE = 42; // Messages are stored local encrypted and need async encryption and delivery.
// public static final int SECURE_SENT_TYPE = 44; // Messages were sent with async encryption.
// public static final int SECURE_RECEIVED_TYPE = 45; // Messages were received with async decryption.
// public static final int FAILED_DECRYPT_TYPE = 46; // Messages were received with async encryption and failed to decrypt.
// public static final int DECRYPTING_TYPE = 47; // Messages are in the process of being asymmetricaly decrypted.
// public static final int NO_SESSION_TYPE = 48; // Messages were received with async encryption but there is no session yet.
//
// public static final int OUTGOING_KEY_EXCHANGE_TYPE = 49;
// public static final int INCOMING_KEY_EXCHANGE_TYPE = 50;
// public static final int STALE_KEY_EXCHANGE_TYPE = 51;
// public static final int PROCESSED_KEY_EXCHANGE_TYPE = 52;
//
// public static final int[] OUTGOING_MESSAGE_TYPES = {SENT_TYPE, SENT_PENDING, ENCRYPTING_TYPE,
// OUTBOX_TYPE, SECURE_SENT_TYPE,
// FAILED_TYPE, OUTGOING_KEY_EXCHANGE_TYPE};
//
// public static boolean isFailedMessageType(long type) {
// return type == FAILED_TYPE;
// }
//
// public static boolean isOutgoingMessageType(long type) {
// for (int outgoingType : OUTGOING_MESSAGE_TYPES) {
// if (type == outgoingType)
// return true;
// }
//
// return false;
// }
//
// public static boolean isPendingMessageType(long type) {
// return type == SENT_PENDING || type == ENCRYPTING_TYPE || type == OUTBOX_TYPE;
// }
//
// public static boolean isSecureType(long type) {
// return
// type == SECURE_SENT_TYPE || type == ENCRYPTING_TYPE ||
// type == SECURE_RECEIVED_TYPE || type == DECRYPTING_TYPE;
// }
//
// public static boolean isKeyExchangeType(long type) {
// return type == OUTGOING_KEY_EXCHANGE_TYPE || type == INCOMING_KEY_EXCHANGE_TYPE;
// }
}

@ -136,16 +136,6 @@ public class MmsSmsDatabase extends Database {
return getConversation(threadId, 0, 0);
}
public Cursor getIdentityConflictMessagesForThread(long threadId) {
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.MISMATCHED_IDENTITIES + " IS NOT NULL";
Cursor cursor = queryTables(PROJECTION, selection, order, null);
setNotifyConverationListeners(cursor, threadId);
return cursor;
}
public Cursor getConversationSnippet(long threadId) {
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
@ -228,26 +218,6 @@ public class MmsSmsDatabase extends Database {
return -1;
}
/**
* Retrieves the position of the message with the provided timestamp in the query results you'd
* get from calling {@link #getConversation(long)}.
*
* Note: This could give back incorrect results in the situation where multiple messages have the
* same received timestamp. However, because this was designed to determine where to scroll to,
* you'll still wind up in about the right spot.
*/
public int getMessagePositionInConversation(long threadId, long receivedTimestamp) {
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " > " + receivedTimestamp;
try (Cursor cursor = queryTables(new String[]{ "COUNT(*)" }, selection, order, null)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getInt(0);
}
}
return -1;
}
private Cursor queryTables(String[] projection, String selection, String order, String limit) {
String[] mmsProjection = {MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,

@ -1,4 +0,0 @@
package org.thoughtcrime.securesms.database;
public class NotInDirectoryException extends Throwable {
}

@ -1,81 +0,0 @@
package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.Nullable;
import org.session.libsignal.utilities.logging.Log;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.session.libsignal.utilities.Base64;
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 org.session.libsignal.libsignal.state.PreKeyRecord;
import java.io.IOException;
public class OneTimePreKeyDatabase extends Database {
private static final String TAG = OneTimePreKeyDatabase.class.getSimpleName();
public static final String TABLE_NAME = "one_time_prekeys";
private static final String ID = "_id";
public static final String KEY_ID = "key_id";
public static final String PUBLIC_KEY = "public_key";
public static final String PRIVATE_KEY = "private_key";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
" (" + ID + " INTEGER PRIMARY KEY, " +
KEY_ID + " INTEGER UNIQUE, " +
PUBLIC_KEY + " TEXT NOT NULL, " +
PRIVATE_KEY + " TEXT NOT NULL);";
OneTimePreKeyDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public @Nullable PreKeyRecord getPreKey(int keyId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
try (Cursor cursor = database.query(TABLE_NAME, null, KEY_ID + " = ?",
new String[] {String.valueOf(keyId)},
null, null, null))
{
if (cursor != null && cursor.moveToFirst()) {
try {
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PUBLIC_KEY))), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PRIVATE_KEY))));
return new PreKeyRecord(keyId, new ECKeyPair(publicKey, privateKey));
} catch (InvalidKeyException | IOException e) {
Log.w(TAG, e);
}
}
}
return null;
}
public void insertPreKey(int keyId, PreKeyRecord record) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(KEY_ID, keyId);
contentValues.put(PUBLIC_KEY, Base64.encodeBytes(record.getKeyPair().getPublicKey().serialize()));
contentValues.put(PRIVATE_KEY, Base64.encodeBytes(record.getKeyPair().getPrivateKey().serialize()));
database.replace(TABLE_NAME, null, contentValues);
}
public void removePreKey(int keyId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, KEY_ID + " = ?", new String[] {String.valueOf(keyId)});
}
}

@ -49,11 +49,11 @@ public class PushDatabase extends Database {
values.put(TYPE, envelope.getType());
values.put(SOURCE, envelope.getSource());
values.put(DEVICE_ID, envelope.getSourceDevice());
values.put(LEGACY_MSG, envelope.hasLegacyMessage() ? Base64.encodeBytes(envelope.getLegacyMessage()) : "");
values.put(LEGACY_MSG, "");
values.put(CONTENT, envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : "");
values.put(TIMESTAMP, envelope.getTimestamp());
values.put(SERVER_TIMESTAMP, envelope.getServerTimestamp());
values.put(SERVER_GUID, envelope.getUuid());
values.put(SERVER_GUID, "");
return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values);
}
@ -68,17 +68,14 @@ public class PushDatabase extends Database {
null, null, null);
if (cursor != null && cursor.moveToNext()) {
String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG));
String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT));
return new SignalServiceEnvelope(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)),
cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)),
cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID)),
cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)),
Util.isEmpty(legacyMessage) ? null : Base64.decode(legacyMessage),
Util.isEmpty(content) ? null : Base64.decode(content),
cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_TIMESTAMP)),
cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID)));
cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_TIMESTAMP)));
}
} catch (IOException e) {
Log.w(TAG, e);
@ -114,7 +111,7 @@ public class PushDatabase extends Database {
new String[] {String.valueOf(envelope.getType()),
envelope.getSource(),
String.valueOf(envelope.getSourceDevice()),
envelope.hasLegacyMessage() ? Base64.encodeBytes(envelope.getLegacyMessage()) : "",
"",
envelope.hasContent() ? Base64.encodeBytes(envelope.getContent()) : "",
String.valueOf(envelope.getTimestamp())},
null, null, null);
@ -144,16 +141,13 @@ public class PushDatabase extends Database {
int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE));
String source = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE));
int deviceId = cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE_ID));
String legacyMessage = cursor.getString(cursor.getColumnIndexOrThrow(LEGACY_MSG));
String content = cursor.getString(cursor.getColumnIndexOrThrow(CONTENT));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
long serverTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(SERVER_TIMESTAMP));
String serverGuid = cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID));
return new SignalServiceEnvelope(type, source, deviceId, timestamp,
legacyMessage != null ? Base64.decode(legacyMessage) : null,
content != null ? Base64.decode(content) : null,
serverTimestamp, serverGuid);
serverTimestamp);
} catch (IOException e) {
throw new AssertionError(e);
}

@ -104,17 +104,6 @@ public class RecipientDatabase extends Database {
super(context, databaseHelper);
}
public Cursor getBlocked() {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
return database.query(TABLE_NAME, new String[] {ID, ADDRESS}, BLOCK + " = 1",
null, null, null, null, null);
}
public RecipientReader readerForBlocked(Cursor cursor) {
return new RecipientReader(context, cursor);
}
public RecipientReader getRecipientsWithNotificationChannels() {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, new String[] {ID, ADDRESS}, NOTIFICATION_CHANNEL + " NOT NULL",
@ -195,21 +184,6 @@ public class RecipientDatabase extends Database {
forceSmsSelection));
}
public BulkOperationsHandle resetAllSystemContactInfo() {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.beginTransaction();
ContentValues contentValues = new ContentValues(1);
contentValues.put(SYSTEM_DISPLAY_NAME, (String)null);
contentValues.put(SYSTEM_PHOTO_URI, (String)null);
contentValues.put(SYSTEM_PHONE_LABEL, (String)null);
contentValues.put(SYSTEM_CONTACT_URI, (String)null);
database.update(TABLE_NAME, contentValues, null, null);
return new BulkOperationsHandle(database);
}
public void setColor(@NonNull Recipient recipient, @NonNull MaterialColor color) {
ContentValues values = new ContentValues();
values.put(COLOR, color.serialize());
@ -238,34 +212,6 @@ public class RecipientDatabase extends Database {
recipient.resolve().setBlocked(blocked);
}
public void setMessageRingtone(@NonNull Recipient recipient, @Nullable Uri notification) {
ContentValues values = new ContentValues();
values.put(NOTIFICATION, notification == null ? null : notification.toString());
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setMessageRingtone(notification);
}
public void setCallRingtone(@NonNull Recipient recipient, @Nullable Uri ringtone) {
ContentValues values = new ContentValues();
values.put(CALL_RINGTONE, ringtone == null ? null : ringtone.toString());
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setCallRingtone(ringtone);
}
public void setMessageVibrate(@NonNull Recipient recipient, @NonNull VibrateState enabled) {
ContentValues values = new ContentValues();
values.put(VIBRATE, enabled.getId());
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setMessageVibrate(enabled);
}
public void setCallVibrate(@NonNull Recipient recipient, @NonNull VibrateState enabled) {
ContentValues values = new ContentValues();
values.put(CALL_VIBRATE, enabled.getId());
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setCallVibrate(enabled);
}
public void setMuted(@NonNull Recipient recipient, long until) {
ContentValues values = new ContentValues();
values.put(MUTE_UNTIL, until);
@ -296,13 +242,6 @@ public class RecipientDatabase extends Database {
recipient.resolve().setProfileKey(profileKey);
}
public void setProfileName(@NonNull Recipient recipient, @Nullable String profileName) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(SIGNAL_PROFILE_NAME, profileName);
updateOrInsert(recipient.getAddress(), contentValues);
recipient.resolve().setProfileName(profileName);
}
public void setProfileAvatar(@NonNull Recipient recipient, @Nullable String profileAvatar) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(SIGNAL_PROFILE_AVATAR, profileAvatar);
@ -324,19 +263,6 @@ public class RecipientDatabase extends Database {
recipient.setNotificationChannel(notificationChannel);
}
public Set<Address> getAllAddresses() {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Set<Address> results = new HashSet<>();
try (Cursor cursor = db.query(TABLE_NAME, new String[] {ADDRESS}, null, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
results.add(Address.Companion.fromExternal(context, cursor.getString(0)));
}
}
return results;
}
public void setRegistered(@NonNull Recipient recipient, RegisteredState registeredState) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(REGISTERED, registeredState.getId());
@ -344,91 +270,6 @@ public class RecipientDatabase extends Database {
recipient.setRegistered(registeredState);
}
public void setRegistered(@NonNull List<Address> activeAddresses,
@NonNull List<Address> inactiveAddresses)
{
for (Address activeAddress : activeAddresses) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(REGISTERED, RegisteredState.REGISTERED.getId());
updateOrInsert(activeAddress, contentValues);
Recipient.applyCached(activeAddress, recipient -> recipient.setRegistered(RegisteredState.REGISTERED));
}
for (Address inactiveAddress : inactiveAddresses) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(REGISTERED, RegisteredState.NOT_REGISTERED.getId());
updateOrInsert(inactiveAddress, contentValues);
Recipient.applyCached(inactiveAddress, recipient -> recipient.setRegistered(RegisteredState.NOT_REGISTERED));
}
}
public List<Address> getRegistered() {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
List<Address> results = new LinkedList<>();
try (Cursor cursor = db.query(TABLE_NAME, new String[] {ADDRESS}, REGISTERED + " = ?", new String[] {"1"}, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
results.add(Address.Companion.fromSerialized(cursor.getString(0)));
}
}
return results;
}
public List<Address> getSystemContacts() {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
List<Address> results = new LinkedList<>();
try (Cursor cursor = db.query(TABLE_NAME, new String[] {ADDRESS}, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
results.add(Address.Companion.fromSerialized(cursor.getString(0)));
}
}
return results;
}
public void updateSystemContactColors(@NonNull ColorUpdater updater) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Map<Address, MaterialColor> updates = new HashMap<>();
db.beginTransaction();
try (Cursor cursor = db.query(TABLE_NAME, new String[] {ADDRESS, COLOR, SYSTEM_DISPLAY_NAME}, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
Address address = Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
MaterialColor newColor = updater.update(cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndexOrThrow(COLOR)));
ContentValues contentValues = new ContentValues(1);
contentValues.put(COLOR, newColor.serialize());
db.update(TABLE_NAME, contentValues, ADDRESS + " = ?", new String[]{address.serialize()});
updates.put(address, newColor);
}
} finally {
db.setTransactionSuccessful();
db.endTransaction();
Stream.of(updates.entrySet()).forEach(entry -> {
Recipient.applyCached(entry.getKey(), recipient -> {
recipient.setColor(entry.getValue());
});
});
}
}
// XXX This shouldn't be here, and is just a temporary workaround
public RegisteredState isRegistered(@NonNull Address address) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
try (Cursor cursor = db.query(TABLE_NAME, new String[] {REGISTERED}, ADDRESS + " = ?", new String[] {address.serialize()}, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) return RegisteredState.fromId(cursor.getInt(0));
else return RegisteredState.UNKNOWN;
}
}
private void updateOrInsert(Address address, ContentValues contentValues) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
@ -446,45 +287,6 @@ public class RecipientDatabase extends Database {
database.endTransaction();
}
public class BulkOperationsHandle {
private final SQLiteDatabase database;
private final Map<Address, PendingContactInfo> pendingContactInfoMap = new HashMap<>();
BulkOperationsHandle(SQLiteDatabase database) {
this.database = database;
}
public void setSystemContactInfo(@NonNull Address address, @Nullable String displayName, @Nullable String photoUri, @Nullable String systemPhoneLabel, @Nullable String systemContactUri) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(SYSTEM_DISPLAY_NAME, displayName);
contentValues.put(SYSTEM_PHOTO_URI, photoUri);
contentValues.put(SYSTEM_PHONE_LABEL, systemPhoneLabel);
contentValues.put(SYSTEM_CONTACT_URI, systemContactUri);
updateOrInsert(address, contentValues);
pendingContactInfoMap.put(address, new PendingContactInfo(displayName, photoUri, systemPhoneLabel, systemContactUri));
}
public void finish() {
database.setTransactionSuccessful();
database.endTransaction();
Stream.of(pendingContactInfoMap.entrySet())
.forEach(entry -> Recipient.applyCached(entry.getKey(), recipient -> {
recipient.setName(entry.getValue().displayName);
recipient.setSystemContactPhoto(Util.uri(entry.getValue().photoUri));
recipient.setCustomLabel(entry.getValue().phoneLabel);
recipient.setContactUri(Util.uri(entry.getValue().contactUri));
}));
}
}
public interface ColorUpdater {
MaterialColor update(@NonNull String name, @Nullable String color);
}
public static class RecipientReader implements Closeable {
private final Context context;
@ -497,7 +299,7 @@ public class RecipientDatabase extends Database {
public @NonNull Recipient getCurrent() {
String serialized = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
return Recipient.from(context, Address.Companion.fromSerialized(serialized), false);
return Recipient.from(context, Address.fromSerialized(serialized), false);
}
public @Nullable Recipient getNext() {
@ -512,20 +314,4 @@ public class RecipientDatabase extends Database {
cursor.close();
}
}
private static class PendingContactInfo {
private final String displayName;
private final String photoUri;
private final String phoneLabel;
private final String contactUri;
private PendingContactInfo(String displayName, String photoUri, String phoneLabel, String contactUri) {
this.displayName = displayName;
this.photoUri = photoUri;
this.phoneLabel = phoneLabel;
this.contactUri = contactUri;
}
}
}

@ -1,173 +0,0 @@
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.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.Address;
import org.session.libsignal.libsignal.state.SessionRecord;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class SessionDatabase extends Database {
private static final String TAG = SessionDatabase.class.getSimpleName();
public static final String TABLE_NAME = "sessions";
private static final String ID = "_id";
public static final String ADDRESS = "address";
public static final String DEVICE = "device";
public static final String RECORD = "record";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
"(" + ID + " INTEGER PRIMARY KEY, " + ADDRESS + " TEXT NOT NULL, " +
DEVICE + " INTEGER NOT NULL, " + RECORD + " BLOB NOT NULL, " +
"UNIQUE(" + ADDRESS + "," + DEVICE + ") ON CONFLICT REPLACE);";
SessionDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public void store(@NonNull Address address, int deviceId, @NonNull SessionRecord record) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(ADDRESS, address.serialize());
values.put(DEVICE, deviceId);
values.put(RECORD, record.serialize());
database.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
public @Nullable SessionRecord load(@NonNull Address address, int deviceId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
try (Cursor cursor = database.query(TABLE_NAME, new String[]{RECORD},
ADDRESS + " = ? AND " + DEVICE + " = ?",
new String[] {address.serialize(), String.valueOf(deviceId)},
null, null, null))
{
if (cursor != null && cursor.moveToFirst()) {
try {
return new SessionRecord(cursor.getBlob(cursor.getColumnIndexOrThrow(RECORD)));
} catch (IOException e) {
Log.w(TAG, e);
}
}
}
return null;
}
public @NonNull List<SessionRow> getAllFor(@NonNull Address address) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
List<SessionRow> results = new LinkedList<>();
try (Cursor cursor = database.query(TABLE_NAME, null,
ADDRESS + " = ?",
new String[] {address.serialize()},
null, null, null))
{
while (cursor != null && cursor.moveToNext()) {
try {
results.add(new SessionRow(address,
cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE)),
new SessionRecord(cursor.getBlob(cursor.getColumnIndexOrThrow(RECORD)))));
} catch (IOException e) {
Log.w(TAG, e);
}
}
}
return results;
}
public @NonNull List<SessionRow> getAll() {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
List<SessionRow> results = new LinkedList<>();
try (Cursor cursor = database.query(TABLE_NAME, null, null, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
try {
results.add(new SessionRow(Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))),
cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE)),
new SessionRecord(cursor.getBlob(cursor.getColumnIndexOrThrow(RECORD)))));
} catch (IOException e) {
Log.w(TAG, e);
}
}
}
return results;
}
public @NonNull List<Integer> getSubDevices(@NonNull Address address) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
List<Integer> results = new LinkedList<>();
try (Cursor cursor = database.query(TABLE_NAME, new String[] {DEVICE},
ADDRESS + " = ?",
new String[] {address.serialize()},
null, null, null))
{
while (cursor != null && cursor.moveToNext()) {
int device = cursor.getInt(cursor.getColumnIndexOrThrow(DEVICE));
if (device != SignalServiceAddress.DEFAULT_DEVICE_ID) {
results.add(device);
}
}
}
return results;
}
public void delete(@NonNull Address address, int deviceId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, ADDRESS + " = ? AND " + DEVICE + " = ?",
new String[] {address.serialize(), String.valueOf(deviceId)});
}
public void deleteAllFor(@NonNull Address address) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, ADDRESS + " = ?", new String[] {address.serialize()});
}
public static final class SessionRow {
private final Address address;
private final int deviceId;
private final SessionRecord record;
public SessionRow(Address address, int deviceId, SessionRecord record) {
this.address = address;
this.deviceId = deviceId;
this.record = record;
}
public Address getAddress() {
return address;
}
public int getDeviceId() {
return deviceId;
}
public SessionRecord getRecord() {
return record;
}
}
}

@ -1,117 +0,0 @@
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.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.utilities.Base64;
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 org.session.libsignal.libsignal.state.SignedPreKeyRecord;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class SignedPreKeyDatabase extends Database {
private static final String TAG = SignedPreKeyDatabase.class.getSimpleName();
public static final String TABLE_NAME = "signed_prekeys";
private static final String ID = "_id";
public static final String KEY_ID = "key_id";
public static final String PUBLIC_KEY = "public_key";
public static final String PRIVATE_KEY = "private_key";
public static final String SIGNATURE = "signature";
public static final String TIMESTAMP = "timestamp";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
" (" + 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);";
SignedPreKeyDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public @Nullable SignedPreKeyRecord getSignedPreKey(int keyId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
try (Cursor cursor = database.query(TABLE_NAME, null, KEY_ID + " = ?",
new String[] {String.valueOf(keyId)},
null, null, null))
{
if (cursor != null && cursor.moveToFirst()) {
try {
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PUBLIC_KEY))), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PRIVATE_KEY))));
byte[] signature = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(SIGNATURE)));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
return new SignedPreKeyRecord(keyId, timestamp, new ECKeyPair(publicKey, privateKey), signature);
} catch (InvalidKeyException | IOException e) {
Log.w(TAG, e);
}
}
}
return null;
}
public @NonNull List<SignedPreKeyRecord> getAllSignedPreKeys() {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
List<SignedPreKeyRecord> results = new LinkedList<>();
try (Cursor cursor = database.query(TABLE_NAME, null, null, null, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
try {
int keyId = cursor.getInt(cursor.getColumnIndexOrThrow(KEY_ID));
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PUBLIC_KEY))), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(PRIVATE_KEY))));
byte[] signature = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(SIGNATURE)));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
results.add(new SignedPreKeyRecord(keyId, timestamp, new ECKeyPair(publicKey, privateKey), signature));
} catch (InvalidKeyException | IOException e) {
Log.w(TAG, e);
}
}
}
return results;
}
public void insertSignedPreKey(int keyId, SignedPreKeyRecord record) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(KEY_ID, keyId);
contentValues.put(PUBLIC_KEY, Base64.encodeBytes(record.getKeyPair().getPublicKey().serialize()));
contentValues.put(PRIVATE_KEY, Base64.encodeBytes(record.getKeyPair().getPrivateKey().serialize()));
contentValues.put(SIGNATURE, Base64.encodeBytes(record.getSignature()));
contentValues.put(TIMESTAMP, record.getTimestamp());
database.replace(TABLE_NAME, null, contentValues);
}
public void removeSignedPreKey(int keyId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, KEY_ID + " = ? AND " + SIGNATURE + " IS NOT NULL", new String[] {String.valueOf(keyId)});
}
}

@ -149,21 +149,6 @@ public class SmsDatabase extends MessagingDatabase {
}
}
public int getMessageCount() {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = db.query(TABLE_NAME, new String[] {"COUNT(*)"}, null, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) return cursor.getInt(0);
else return 0;
} finally {
if (cursor != null)
cursor.close();
}
}
public int getMessageCountForThread(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = null;
@ -182,99 +167,10 @@ public class SmsDatabase extends MessagingDatabase {
return 0;
}
public long getIDForMessageAtIndex(long threadID, int index) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, THREAD_ID + " = ?", new String[] { threadID + "" }, null, null, null);
if (cursor != null && cursor.moveToPosition(index)) {
return cursor.getLong(0);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return -1;
}
public Set<Long> getAllMessageIDs(long threadID) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
Set<Long> messageIDs = new HashSet<>();
try {
cursor = database.query(TABLE_NAME, null, THREAD_ID + " = ?", new String[] { threadID + "" }, null, null, null);
while (cursor != null && cursor.moveToNext()) {
messageIDs.add(cursor.getLong(0));
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return messageIDs;
}
public void markAsEndSession(long id) {
updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.END_SESSION_BIT);
}
public void markAsPreKeyBundle(long id) {
updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT);
}
public void markAsInvalidVersionKeyExchange(long id) {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_INVALID_VERSION_BIT);
}
public void markAsSecure(long id) {
updateTypeBitmask(id, 0, Types.SECURE_MESSAGE_BIT);
}
public void markAsInsecure(long id) {
updateTypeBitmask(id, Types.SECURE_MESSAGE_BIT, 0);
}
public void markAsPush(long id) {
updateTypeBitmask(id, 0, Types.PUSH_MESSAGE_BIT);
}
public void markAsForcedSms(long id) {
updateTypeBitmask(id, Types.PUSH_MESSAGE_BIT, Types.MESSAGE_FORCE_SMS_BIT);
}
public void markAsDecryptFailed(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT);
}
public void markAsDecryptDuplicate(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_DUPLICATE_BIT);
}
public void markAsNoSession(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT);
}
public void markAsSentLokiSessionRestorationRequest(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_LOKI_SESSION_RESTORE_SENT_BIT);
}
public void markAsLokiSessionRestorationDone(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_LOKI_SESSION_RESTORE_DONE_BIT);
}
public void markAsLegacyVersion(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT);
}
public void markAsOutbox(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_OUTBOX_TYPE);
}
public void markAsPendingInsecureSmsFallback(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK);
}
@Override
public void markAsSent(long id, boolean isSecure) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENT_TYPE | (isSecure ? Types.PUSH_MESSAGE_BIT | Types.SECURE_MESSAGE_BIT : 0));
@ -284,10 +180,6 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE);
}
public void markAsMissedCall(long id) {
updateTypeBitmask(id, Types.TOTAL_MASK, Types.MISSED_CALL_TYPE);
}
@Override
public void markUnidentified(long id, boolean unidentified) {
ContentValues contentValues = new ContentValues(1);
@ -316,19 +208,6 @@ public class SmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
}
public void markStatus(long id, int status) {
Log.i("MessageDatabase", "Updating ID: " + id + " to status: " + status);
ContentValues contentValues = new ContentValues();
contentValues.put(STATUS, status);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {id+""});
long threadId = getThreadIdForMessage(id);
DatabaseFactory.getThreadDatabase(context).update(threadId, false);
notifyConversationListeners(threadId);
}
public void markAsSentFailed(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENT_FAILED_TYPE);
}
@ -377,7 +256,7 @@ public class SmsDatabase extends MessagingDatabase {
while (cursor.moveToNext()) {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) {
Address theirAddress = messageId.getAddress();
Address ourAddress = Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
Address ourAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
String columnName = deliveryReceipt ? DELIVERY_RECEIPT_COUNT : READ_RECEIPT_COUNT;
if (ourAddress.equals(theirAddress)) {
@ -406,50 +285,6 @@ public class SmsDatabase extends MessagingDatabase {
}
}
public List<Pair<Long, Long>> setTimestampRead(SyncMessageId messageId, long proposedExpireStarted) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
List<Pair<Long, Long>> expiring = new LinkedList<>();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, ADDRESS, TYPE, EXPIRES_IN, EXPIRE_STARTED},
DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())},
null, null, null, null);
while (cursor.moveToNext()) {
Address theirAddress = messageId.getAddress();
Address ourAddress = Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
if (ourAddress.equals(theirAddress)) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
ContentValues contentValues = new ContentValues();
contentValues.put(READ, 1);
if (expiresIn > 0) {
contentValues.put(EXPIRE_STARTED, expireStarted);
expiring.add(new Pair<>(id, expiresIn));
}
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {cursor.getLong(cursor.getColumnIndexOrThrow(ID)) + ""});
DatabaseFactory.getThreadDatabase(context).updateReadState(threadId);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
notifyConversationListeners(threadId);
}
}
} finally {
if (cursor != null) cursor.close();
}
return expiring;
}
public List<MarkedMessageInfo> setMessagesRead(long threadId) {
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
}
@ -469,7 +304,7 @@ public class SmsDatabase extends MessagingDatabase {
while (cursor != null && cursor.moveToNext()) {
if (Types.isSecureType(cursor.getLong(3))) {
SyncMessageId syncMessageId = new SyncMessageId(Address.Companion.fromSerialized(cursor.getString(1)), cursor.getLong(2));
SyncMessageId syncMessageId = new SyncMessageId(Address.fromSerialized(cursor.getString(1)), cursor.getLong(2));
ExpirationInfo expirationInfo = new ExpirationInfo(cursor.getLong(0), cursor.getLong(4), cursor.getLong(5), false);
results.add(new MarkedMessageInfo(syncMessageId, expirationInfo));
@ -494,11 +329,6 @@ public class SmsDatabase extends MessagingDatabase {
return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type);
}
public void updateMessageBody(long messageId, String body) {
long type = 0;
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
}
private Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
@ -515,97 +345,16 @@ public class SmsDatabase extends MessagingDatabase {
return new Pair<>(messageId, threadId);
}
public Pair<Long, Long> copyMessageInbox(long messageId) {
try {
SmsMessageRecord record = getMessage(messageId);
ContentValues contentValues = new ContentValues();
contentValues.put(TYPE, (record.getType() & ~Types.BASE_TYPE_MASK) | Types.BASE_INBOX_TYPE);
contentValues.put(ADDRESS, record.getIndividualRecipient().getAddress().serialize());
contentValues.put(ADDRESS_DEVICE_ID, record.getRecipientDeviceId());
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
contentValues.put(DATE_SENT, record.getDateSent());
contentValues.put(PROTOCOL, 31337);
contentValues.put(READ, 0);
contentValues.put(BODY, record.getBody());
contentValues.put(THREAD_ID, record.getThreadId());
contentValues.put(EXPIRES_IN, record.getExpiresIn());
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long newMessageId = db.insert(TABLE_NAME, null, contentValues);
DatabaseFactory.getThreadDatabase(context).update(record.getThreadId(), true);
notifyConversationListeners(record.getThreadId());
ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(record.getThreadId()));
return new Pair<>(newMessageId, record.getThreadId());
} catch (NoSuchMessageException e) {
throw new AssertionError(e);
}
}
public @NonNull Pair<Long, Long> insertReceivedCall(@NonNull Address address) {
return insertCallLog(address, Types.INCOMING_CALL_TYPE, false);
}
public @NonNull Pair<Long, Long> insertOutgoingCall(@NonNull Address address) {
return insertCallLog(address, Types.OUTGOING_CALL_TYPE, false);
}
public @NonNull Pair<Long, Long> insertMissedCall(@NonNull Address address) {
return insertCallLog(address, Types.MISSED_CALL_TYPE, true);
}
private @NonNull Pair<Long, Long> insertCallLog(@NonNull Address address, long type, boolean unread) {
Recipient recipient = Recipient.from(context, address, true);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
ContentValues values = new ContentValues(6);
values.put(ADDRESS, address.serialize());
values.put(ADDRESS_DEVICE_ID, 1);
values.put(DATE_RECEIVED, System.currentTimeMillis());
values.put(DATE_SENT, System.currentTimeMillis());
values.put(READ, unread ? 0 : 1);
values.put(TYPE, type);
values.put(THREAD_ID, threadId);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, null, values);
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId));
if (unread) {
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
}
return new Pair<>(messageId, threadId);
}
protected Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp) {
if (message.isJoined()) {
type = (type & (Types.TOTAL_MASK - Types.BASE_TYPE_MASK)) | Types.JOINED_TYPE;
} else if (message.isPreKeyBundle()) {
type |= Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT;
} else if (message.isSecureMessage()) {
if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;
} else if (message.isGroup()) {
type |= Types.SECURE_MESSAGE_BIT;
if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT;
else if (((IncomingGroupMessage)message).isQuit()) type |= Types.GROUP_QUIT_BIT;
} else if (message.isEndSession()) {
type |= Types.SECURE_MESSAGE_BIT;
type |= Types.END_SESSION_BIT;
}
if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT;
if (message.isIdentityUpdate()) type |= Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT;
if (message.isContentPreKeyBundle()) type |= Types.KEY_EXCHANGE_CONTENT_FORMAT;
if (message.isIdentityVerified()) type |= Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
else if (message.isIdentityDefault()) type |= Types.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT;
Recipient recipient = Recipient.from(context, message.getSender(), true);
@ -618,8 +367,7 @@ public class SmsDatabase extends MessagingDatabase {
}
boolean unread = (Util.isDefaultSmsProvider(context) ||
message.isSecureMessage() || message.isGroup() || message.isPreKeyBundle()) &&
!message.isIdentityUpdate() && !message.isIdentityDefault() && !message.isIdentityVerified();
message.isSecureMessage() || message.isGroup());
long threadId;
@ -660,9 +408,7 @@ public class SmsDatabase extends MessagingDatabase {
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
}
if (!message.isIdentityUpdate() && !message.isIdentityVerified() && !message.isIdentityDefault()) {
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
}
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
if (message.getSubscriptionId() != -1) {
DatabaseFactory.getRecipientDatabase(context).setDefaultSubscriptionId(recipient, message.getSubscriptionId());
@ -670,9 +416,7 @@ public class SmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
if (!message.isIdentityUpdate() && !message.isIdentityVerified() && !message.isIdentityDefault()) {
ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId));
}
ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId));
return Optional.of(new InsertResult(messageId, threadId));
}
@ -703,14 +447,9 @@ public class SmsDatabase extends MessagingDatabase {
{
long type = Types.BASE_SENDING_TYPE;
if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT;
else if (message.isSecureMessage()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
else if (message.isEndSession()) type |= Types.END_SESSION_BIT;
if (message.isSecureMessage()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
if (message.isIdentityVerified()) type |= Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
else if (message.isIdentityDefault()) type |= Types.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT;
Address address = message.getRecipient().getAddress();
Map<Address, Long> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date);
Map<Address, Long> earlyReadReceipts = earlyReadReceiptCache.remove(date);
@ -734,33 +473,18 @@ public class SmsDatabase extends MessagingDatabase {
insertListener.onComplete();
}
if (!message.isIdentityVerified() && !message.isIdentityDefault()) {
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
}
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
notifyConversationListeners(threadId);
if (!message.isIdentityVerified() && !message.isIdentityDefault()) {
ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId));
}
ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId));
return messageId;
}
Cursor getMessages(int skip, int limit) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
return db.query(TABLE_NAME, MESSAGE_PROJECTION, null, null, null, null, ID, skip + "," + limit);
}
Cursor getOutgoingMessages() {
String outgoingSelection = TYPE + " & " + Types.BASE_TYPE_MASK + " = " + Types.BASE_OUTBOX_TYPE;
SQLiteDatabase db = databaseHelper.getReadableDatabase();
return db.query(TABLE_NAME, MESSAGE_PROJECTION, outgoingSelection, null, null, null, null);
}
public Cursor getExpirationStartedMessages() {
String where = EXPIRE_STARTED + " > 0";
SQLiteDatabase db = databaseHelper.getReadableDatabase();
@ -796,10 +520,6 @@ public class SmsDatabase extends MessagingDatabase {
return threadDeleted;
}
public void ensureMigration() {
databaseHelper.getWritableDatabase();
}
private boolean isDuplicate(IncomingTextMessage message, long threadId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?",
@ -937,7 +657,7 @@ public class SmsDatabase extends MessagingDatabase {
public SmsMessageRecord getCurrent() {
long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
Address address = Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS)));
Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS)));
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS_DEVICE_ID));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_RECEIVED));

@ -1,275 +0,0 @@
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import androidx.annotation.Nullable;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteStatement;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.TextSecurePreferences;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
public class SmsMigrator {
private static final String TAG = SmsMigrator.class.getSimpleName();
private static void addStringToStatement(SQLiteStatement statement, Cursor cursor,
int index, String key)
{
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (cursor.isNull(columnIndex)) {
statement.bindNull(index);
} else {
statement.bindString(index, cursor.getString(columnIndex));
}
}
private static void addIntToStatement(SQLiteStatement statement, Cursor cursor,
int index, String key)
{
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (cursor.isNull(columnIndex)) {
statement.bindNull(index);
} else {
statement.bindLong(index, cursor.getLong(columnIndex));
}
}
@SuppressWarnings("SameParameterValue")
private static void addTranslatedTypeToStatement(SQLiteStatement statement, Cursor cursor, int index, String key)
{
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (cursor.isNull(columnIndex)) {
statement.bindLong(index, SmsDatabase.Types.BASE_INBOX_TYPE);
} else {
long theirType = cursor.getLong(columnIndex);
statement.bindLong(index, SmsDatabase.Types.translateFromSystemBaseType(theirType));
}
}
private static boolean isAppropriateTypeForMigration(Cursor cursor, int columnIndex) {
long systemType = cursor.getLong(columnIndex);
long ourType = SmsDatabase.Types.translateFromSystemBaseType(systemType);
return ourType == MmsSmsColumns.Types.BASE_INBOX_TYPE ||
ourType == MmsSmsColumns.Types.BASE_SENT_TYPE ||
ourType == MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE;
}
private static void getContentValuesForRow(Context context, Cursor cursor,
long threadId, SQLiteStatement statement)
{
String theirAddress = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
statement.bindString(1, Address.Companion.fromExternal(context, theirAddress).serialize());
addIntToStatement(statement, cursor, 2, SmsDatabase.PERSON);
addIntToStatement(statement, cursor, 3, SmsDatabase.DATE_RECEIVED);
addIntToStatement(statement, cursor, 4, SmsDatabase.DATE_RECEIVED);
addIntToStatement(statement, cursor, 5, SmsDatabase.PROTOCOL);
addIntToStatement(statement, cursor, 6, SmsDatabase.READ);
addIntToStatement(statement, cursor, 7, SmsDatabase.STATUS);
addTranslatedTypeToStatement(statement, cursor, 8, SmsDatabase.TYPE);
addIntToStatement(statement, cursor, 9, SmsDatabase.REPLY_PATH_PRESENT);
addStringToStatement(statement, cursor, 10, SmsDatabase.SUBJECT);
addStringToStatement(statement, cursor, 11, SmsDatabase.BODY);
addStringToStatement(statement, cursor, 12, SmsDatabase.SERVICE_CENTER);
statement.bindLong(13, threadId);
}
private static String getTheirCanonicalAddress(Context context, String theirRecipientId) {
Uri uri = Uri.parse("content://mms-sms/canonical-address/" + theirRecipientId);
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0);
} else {
return null;
}
} catch (IllegalStateException iae) {
Log.w("SmsMigrator", iae);
return null;
} finally {
if (cursor != null)
cursor.close();
}
}
private static @Nullable Set<Recipient> getOurRecipients(Context context, String theirRecipients) {
StringTokenizer tokenizer = new StringTokenizer(theirRecipients.trim(), " ");
Set<Recipient> recipientList = new HashSet<>();
while (tokenizer.hasMoreTokens()) {
String theirRecipientId = tokenizer.nextToken();
String address = getTheirCanonicalAddress(context, theirRecipientId);
if (address != null) {
recipientList.add(Recipient.from(context, Address.Companion.fromExternal(context, address), true));
}
}
if (recipientList.isEmpty()) return null;
else return recipientList;
}
private static void migrateConversation(Context context, SmsMigrationProgressListener listener,
ProgressDescription progress,
long theirThreadId, long ourThreadId)
{
SmsDatabase ourSmsDatabase = DatabaseFactory.getSmsDatabase(context);
Cursor cursor = null;
SQLiteStatement statement = null;
try {
Uri uri = Uri.parse("content://sms/conversations/" + theirThreadId);
try {
cursor = context.getContentResolver().query(uri, null, null, null, null);
} catch (SQLiteException e) {
/// Work around for weird sony-specific (?) bug: #4309
Log.w(TAG, e);
return;
}
SQLiteDatabase transaction = ourSmsDatabase.beginTransaction();
statement = ourSmsDatabase.createInsertStatement(transaction);
while (cursor != null && cursor.moveToNext()) {
int typeColumn = cursor.getColumnIndex(SmsDatabase.TYPE);
if (cursor.isNull(typeColumn) || isAppropriateTypeForMigration(cursor, typeColumn)) {
getContentValuesForRow(context, cursor, ourThreadId, statement);
statement.execute();
}
listener.progressUpdate(new ProgressDescription(progress, cursor.getCount(), cursor.getPosition()));
}
ourSmsDatabase.endTransaction(transaction);
DatabaseFactory.getThreadDatabase(context).update(ourThreadId, true);
DatabaseFactory.getThreadDatabase(context).notifyConversationListeners(ourThreadId);
} finally {
if (statement != null)
statement.close();
if (cursor != null)
cursor.close();
}
}
public static void migrateDatabase(Context context, SmsMigrationProgressListener listener)
{
// if (context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).getBoolean("migrated", false))
// return;
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Cursor cursor = null;
try {
Uri threadListUri = Uri.parse("content://mms-sms/conversations?simple=true");
cursor = context.getContentResolver().query(threadListUri, null, null, null, "date ASC");
while (cursor != null && cursor.moveToNext()) {
long theirThreadId = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
String theirRecipients = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
Set<Recipient> ourRecipients = getOurRecipients(context, theirRecipients);
ProgressDescription progress = new ProgressDescription(cursor.getCount(), cursor.getPosition(), 100, 0);
if (ourRecipients != null) {
if (ourRecipients.size() == 1) {
long ourThreadId = threadDatabase.getOrCreateThreadIdFor(ourRecipients.iterator().next());
migrateConversation(context, listener, progress, theirThreadId, ourThreadId);
} else if (ourRecipients.size() > 1) {
ourRecipients.add(Recipient.from(context, Address.Companion.fromSerialized(TextSecurePreferences.getLocalNumber(context)), true));
List<Address> memberAddresses = new LinkedList<>();
for (Recipient recipient : ourRecipients) {
memberAddresses.add(recipient.getAddress());
}
String ourGroupId = DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(memberAddresses, null);
Recipient ourGroupRecipient = Recipient.from(context, Address.Companion.fromSerialized(ourGroupId), true);
long ourThreadId = threadDatabase.getOrCreateThreadIdFor(ourGroupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
migrateConversation(context, listener, progress, theirThreadId, ourThreadId);
}
}
progress.incrementPrimaryComplete();
listener.progressUpdate(progress);
}
} finally {
if (cursor != null)
cursor.close();
}
context.getSharedPreferences("SecureSMS", Context.MODE_PRIVATE).edit()
.putBoolean("migrated", true).apply();
}
public interface SmsMigrationProgressListener {
void progressUpdate(ProgressDescription description);
}
public static class ProgressDescription {
public final int primaryTotal;
public int primaryComplete;
public final int secondaryTotal;
public final int secondaryComplete;
ProgressDescription(int primaryTotal, int primaryComplete,
int secondaryTotal, int secondaryComplete)
{
this.primaryTotal = primaryTotal;
this.primaryComplete = primaryComplete;
this.secondaryTotal = secondaryTotal;
this.secondaryComplete = secondaryComplete;
}
ProgressDescription(ProgressDescription that, int secondaryTotal, int secondaryComplete) {
this.primaryComplete = that.primaryComplete;
this.primaryTotal = that.primaryTotal;
this.secondaryComplete = secondaryComplete;
this.secondaryTotal = secondaryTotal;
}
void incrementPrimaryComplete() {
primaryComplete += 1;
}
}
}

@ -1,481 +0,0 @@
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 android.text.TextUtils;
import android.util.Pair;
import net.sqlcipher.database.SQLiteDatabase;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.IncomingSticker;
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.stickers.BlessedPacks;
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent;
import org.session.libsession.utilities.Util;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class StickerDatabase extends Database {
private static final String TAG = Log.tag(StickerDatabase.class);
public static final String TABLE_NAME = "sticker";
public static final String _ID = "_id";
static final String PACK_ID = "pack_id";
private static final String PACK_KEY = "pack_key";
private static final String PACK_TITLE = "pack_title";
private static final String PACK_AUTHOR = "pack_author";
private static final String STICKER_ID = "sticker_id";
private static final String EMOJI = "emoji";
private static final String COVER = "cover";
private static final String INSTALLED = "installed";
private static final String LAST_USED = "last_used";
public static final String FILE_PATH = "file_path";
public static final String FILE_LENGTH = "file_length";
public static final String FILE_RANDOM = "file_random";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + _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)";
public static final String[] CREATE_INDEXES = {
"CREATE INDEX IF NOT EXISTS sticker_pack_id_index ON " + TABLE_NAME + " (" + PACK_ID + ");",
"CREATE INDEX IF NOT EXISTS sticker_sticker_id_index ON " + TABLE_NAME + " (" + STICKER_ID + ");"
};
private static final String DIRECTORY = "stickers";
private final AttachmentSecret attachmentSecret;
public StickerDatabase(Context context, SQLCipherOpenHelper databaseHelper, AttachmentSecret attachmentSecret) {
super(context, databaseHelper);
this.attachmentSecret = attachmentSecret;
}
public void insertSticker(@NonNull IncomingSticker sticker, @NonNull InputStream dataStream) throws IOException {
FileInfo fileInfo = saveStickerImage(dataStream);
ContentValues contentValues = new ContentValues();
contentValues.put(PACK_ID, sticker.getPackId());
contentValues.put(PACK_KEY, sticker.getPackKey());
contentValues.put(PACK_TITLE, sticker.getPackTitle());
contentValues.put(PACK_AUTHOR, sticker.getPackAuthor());
contentValues.put(STICKER_ID, sticker.getStickerId());
contentValues.put(EMOJI, sticker.getEmoji());
contentValues.put(COVER, sticker.isCover() ? 1 : 0);
contentValues.put(INSTALLED, sticker.isInstalled() ? 1 : 0);
contentValues.put(FILE_PATH, fileInfo.getFile().getAbsolutePath());
contentValues.put(FILE_LENGTH, fileInfo.getLength());
contentValues.put(FILE_RANDOM, fileInfo.getRandom());
long id = databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
if (id > 0) {
notifyStickerListeners();
if (sticker.isCover()) {
notifyStickerPackListeners();
if (sticker.isInstalled()) {
broadcastInstallEvent(sticker.getPackId());
}
}
}
}
public @Nullable StickerRecord getSticker(@NonNull String packId, int stickerId, boolean isCover) {
String selection = PACK_ID + " = ? AND " + STICKER_ID + " = ? AND " + COVER + " = ?";
String[] args = new String[] { packId, String.valueOf(stickerId), String.valueOf(isCover ? 1 : 0) };
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, "1")) {
return new StickerRecordReader(cursor).getNext();
}
}
public @Nullable StickerPackRecord getStickerPack(@NonNull String packId) {
String query = PACK_ID + " = ? AND " + COVER + " = ?";
String[] args = new String[] { packId, "1" };
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, query, args, null, null, null, "1")) {
return new StickerPackRecordReader(cursor).getNext();
}
}
public @Nullable Cursor getInstalledStickerPacks() {
String selection = COVER + " = ? AND " + INSTALLED + " = ?";
String[] args = new String[] { "1", "1" };
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null);
setNotifyStickerPackListeners(cursor);
return cursor;
}
public @Nullable Cursor getStickersByEmoji(@NonNull String emoji) {
String selection = EMOJI + " LIKE ? AND " + COVER + " = ?";
String[] args = new String[] { "%"+emoji+"%", "0" };
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null);
setNotifyStickerListeners(cursor);
return cursor;
}
public @Nullable Cursor getAllStickerPacks() {
return getAllStickerPacks(null);
}
public @Nullable Cursor getAllStickerPacks(@Nullable String limit) {
String query = COVER + " = ?";
String[] args = new String[] { "1" };
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, query, args, null, null, null, limit);
setNotifyStickerPackListeners(cursor);
return cursor;
}
public @Nullable Cursor getStickersForPack(@NonNull String packId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String selection = PACK_ID + " = ? AND " + COVER + " = ?";
String[] args = new String[] { packId, "0" };
Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null);
setNotifyStickerListeners(cursor);
return cursor;
}
public @Nullable Cursor getRecentlyUsedStickers(int limit) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String selection = LAST_USED + " > ? AND " + COVER + " = ?";
String[] args = new String[] { "0", "0" };
Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, LAST_USED + " DESC", String.valueOf(limit));
setNotifyStickerListeners(cursor);
return cursor;
}
public @Nullable InputStream getStickerStream(long rowId) throws IOException {
String selection = _ID + " = ?";
String[] args = new String[] { String.valueOf(rowId) };
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, selection, args, null, null, null)) {
if (cursor != null && cursor.moveToNext()) {
String path = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH));
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(FILE_RANDOM));
if (path != null) {
return ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(path), 0);
} else {
Log.w(TAG, "getStickerStream("+rowId+") - No sticker data");
}
} else {
Log.i(TAG, "getStickerStream("+rowId+") - Sticker not found.");
}
}
return null;
}
public boolean isPackInstalled(@NonNull String packId) {
StickerPackRecord record = getStickerPack(packId);
return (record != null && record.isInstalled());
}
public boolean isPackAvailableAsReference(@NonNull String packId) {
return getStickerPack(packId) != null;
}
public void updateStickerLastUsedTime(long rowId, long lastUsed) {
String selection = _ID + " = ?";
String[] args = new String[] { String.valueOf(rowId) };
ContentValues values = new ContentValues();
values.put(LAST_USED, lastUsed);
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, selection, args);
notifyStickerListeners();
notifyStickerPackListeners();
}
public void markPackAsInstalled(@NonNull String packKey) {
updatePackInstalled(databaseHelper.getWritableDatabase(), packKey, true);
notifyStickerPackListeners();
}
public void deleteOrphanedPacks() {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
String query = "SELECT " + PACK_ID + " FROM " + TABLE_NAME + " WHERE " + INSTALLED + " = ? AND " +
PACK_ID + " NOT IN (" +
"SELECT DISTINCT " + AttachmentDatabase.STICKER_PACK_ID + " FROM " + AttachmentDatabase.TABLE_NAME + " " +
"WHERE " + AttachmentDatabase.STICKER_PACK_ID + " NOT NULL" +
")";
String[] args = new String[] { "0" };
db.beginTransaction();
try {
boolean performedDelete = false;
try (Cursor cursor = db.rawQuery(query, args)) {
while (cursor != null && cursor.moveToNext()) {
String packId = cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID));
if (!BlessedPacks.contains(packId)) {
deletePack(db, packId);
performedDelete = true;
}
}
}
db.setTransactionSuccessful();
if (performedDelete) {
notifyStickerPackListeners();
notifyStickerListeners();
}
} finally {
db.endTransaction();
}
}
public void uninstallPack(@NonNull String packId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.beginTransaction();
try {
updatePackInstalled(db, packId, false);
deleteStickersInPackExceptCover(db, packId);
db.setTransactionSuccessful();
notifyStickerPackListeners();
notifyStickerListeners();
} finally {
db.endTransaction();
}
}
private void updatePackInstalled(@NonNull SQLiteDatabase db, @NonNull String packId, boolean installed) {
StickerPackRecord existing = getStickerPack(packId);
if (existing != null && existing.isInstalled() == installed) {
return;
}
String selection = PACK_ID + " = ?";
String[] args = new String[]{ packId };
ContentValues values = new ContentValues(1);
values.put(INSTALLED, installed ? 1 : 0);
db.update(TABLE_NAME, values, selection, args);
if (installed) {
broadcastInstallEvent(packId);
}
}
private FileInfo saveStickerImage(@NonNull InputStream inputStream) throws IOException {
File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
File file = File.createTempFile("sticker", ".mms", partsDirectory);
Pair<byte[], OutputStream> out = ModernEncryptingPartOutputStream.createFor(attachmentSecret, file, false);
long length = Util.copy(inputStream, out.second);
return new FileInfo(file, length, out.first);
}
private void deleteSticker(@NonNull SQLiteDatabase db, long rowId, @Nullable String filePath) {
String selection = _ID + " = ?";
String[] args = new String[] { String.valueOf(rowId) };
db.delete(TABLE_NAME, selection, args);
if (!TextUtils.isEmpty(filePath)) {
new File(filePath).delete();
}
}
private void deletePack(@NonNull SQLiteDatabase db, @NonNull String packId) {
String selection = PACK_ID + " = ?";
String[] args = new String[] { packId };
db.delete(TABLE_NAME, selection, args);
deleteStickersInPack(db, packId);
}
private void deleteStickersInPack(@NonNull SQLiteDatabase db, @NonNull String packId) {
String selection = PACK_ID + " = ?";
String[] args = new String[] { packId };
db.beginTransaction();
try {
try (Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
String filePath = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH));
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
deleteSticker(db, rowId, filePath);
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
db.delete(TABLE_NAME, selection, args);
}
private void deleteStickersInPackExceptCover(@NonNull SQLiteDatabase db, @NonNull String packId) {
String selection = PACK_ID + " = ? AND " + COVER + " = ?";
String[] args = new String[] { packId, "0" };
db.beginTransaction();
try {
try (Cursor cursor = db.query(TABLE_NAME, null, selection, args, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
String filePath = cursor.getString(cursor.getColumnIndexOrThrow(FILE_PATH));
deleteSticker(db, rowId, filePath);
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
private void broadcastInstallEvent(@NonNull String packId) {
StickerPackRecord pack = getStickerPack(packId);
if (pack != null) {
EventBus.getDefault().postSticky(new StickerPackInstallEvent(new DecryptableUri(pack.getCover().getUri())));
}
}
private static final class FileInfo {
private final File file;
private final long length;
private final byte[] random;
private FileInfo(@NonNull File file, long length, @NonNull byte[] random) {
this.file = file;
this.length = length;
this.random = random;
}
public File getFile() {
return file;
}
public long getLength() {
return length;
}
public byte[] getRandom() {
return random;
}
}
public static final class StickerRecordReader implements Closeable {
private final Cursor cursor;
public StickerRecordReader(@Nullable Cursor cursor) {
this.cursor = cursor;
}
public @Nullable StickerRecord getNext() {
if (cursor == null || !cursor.moveToNext()) {
return null;
}
return getCurrent();
}
public @NonNull StickerRecord getCurrent() {
return new StickerRecord(cursor.getLong(cursor.getColumnIndexOrThrow(_ID)),
cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID)),
cursor.getString(cursor.getColumnIndexOrThrow(PACK_KEY)),
cursor.getInt(cursor.getColumnIndexOrThrow(STICKER_ID)),
cursor.getString(cursor.getColumnIndexOrThrow(EMOJI)),
cursor.getLong(cursor.getColumnIndexOrThrow(FILE_LENGTH)),
cursor.getInt(cursor.getColumnIndexOrThrow(COVER)) == 1);
}
@Override
public void close() {
if (cursor != null) {
cursor.close();
}
}
}
public static final class StickerPackRecordReader implements Closeable {
private final Cursor cursor;
public StickerPackRecordReader(@Nullable Cursor cursor) {
this.cursor = cursor;
}
public @Nullable StickerPackRecord getNext() {
if (cursor == null || !cursor.moveToNext()) {
return null;
}
return getCurrent();
}
public @NonNull StickerPackRecord getCurrent() {
StickerRecord cover = new StickerRecordReader(cursor).getCurrent();
return new StickerPackRecord(cursor.getString(cursor.getColumnIndexOrThrow(PACK_ID)),
cursor.getString(cursor.getColumnIndexOrThrow(PACK_KEY)),
cursor.getString(cursor.getColumnIndexOrThrow(PACK_TITLE)),
cursor.getString(cursor.getColumnIndexOrThrow(PACK_AUTHOR)),
cover,
cursor.getInt(cursor.getColumnIndexOrThrow(INSTALLED)) == 1);
}
@Override
public void close() {
if (cursor != null) {
cursor.close();
}
}
}
}

@ -417,9 +417,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
override fun isClosedGroup(publicKey: String): Boolean {
val isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(context).isSSKBasedClosedGroup(publicKey)
val isClosedGroup = DatabaseFactory.getLokiAPIDatabase(context).isClosedGroup(publicKey)
val address = Address.fromSerialized(publicKey)
return address.isClosedGroup || isSSKBasedClosedGroup
return address.isClosedGroup || isClosedGroup
}
override fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList<ECKeyPair> {
@ -431,7 +431,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
override fun getAllClosedGroupPublicKeys(): Set<String> {
return DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()
return DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys()
}
override fun addClosedGroupPublicKey(groupPublicKey: String) {

@ -265,25 +265,6 @@ public class ThreadDatabase extends Database {
}
}
public List<MarkedMessageInfo> setAllThreadsRead() {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1);
contentValues.put(READ, 1);
contentValues.put(UNREAD_COUNT, 0);
db.update(TABLE_NAME, contentValues, null, null);
final List<MarkedMessageInfo> smsRecords = DatabaseFactory.getSmsDatabase(context).setAllMessagesRead();
final List<MarkedMessageInfo> mmsRecords = DatabaseFactory.getMmsDatabase(context).setAllMessagesRead();
notifyConversationListListeners();
return new LinkedList<MarkedMessageInfo>() {{
addAll(smsRecords);
addAll(mmsRecords);
}};
}
public List<MarkedMessageInfo> setRead(long threadId, boolean lastSeen) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(READ, 1);
@ -401,43 +382,6 @@ public class ThreadDatabase extends Database {
return db.rawQuery(query, null);
}
public int getArchivedConversationListCount() {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = db.query(TABLE_NAME, new String[] {"COUNT(*)"}, ARCHIVED + " = ?",
new String[] {"1"}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getInt(0);
}
} finally {
if (cursor != null) cursor.close();
}
return 0;
}
public void archiveConversation(long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1);
contentValues.put(ARCHIVED, 1);
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""});
notifyConversationListListeners();
}
public void unarchiveConversation(long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1);
contentValues.put(ARCHIVED, 0);
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId + ""});
notifyConversationListListeners();
}
public void setLastSeen(long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1);
@ -471,22 +415,6 @@ public class ThreadDatabase extends Database {
notifyConversationListListeners();
}
public void deleteConversations(Set<Long> selectedConversations) {
DatabaseFactory.getSmsDatabase(context).deleteThreads(selectedConversations);
DatabaseFactory.getMmsDatabase(context).deleteThreads(selectedConversations);
DatabaseFactory.getDraftDatabase(context).clearDrafts(selectedConversations);
deleteThreads(selectedConversations);
notifyConversationListeners(selectedConversations);
notifyConversationListListeners();
}
public void deleteAllConversations() {
DatabaseFactory.getSmsDatabase(context).deleteAllThreads();
DatabaseFactory.getMmsDatabase(context).deleteAllThreads();
DatabaseFactory.getDraftDatabase(context).clearAllDrafts();
deleteAllThreads();
}
public boolean hasThread(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, new String[]{ ID }, ID_WHERE, new String[]{ String.valueOf(threadId) }, null, null, null);
@ -556,7 +484,7 @@ public class ThreadDatabase extends Database {
cursor = db.query(TABLE_NAME, null, ID + " = ?", new String[] {threadId+""}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
Address address = Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
addressCache.put(threadId, address);
return Recipient.from(context, address, false);
}
@ -578,19 +506,6 @@ public class ThreadDatabase extends Database {
notifyConversationListeners(threadId);
}
void updateReadState(long threadId) {
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);
ContentValues contentValues = new ContentValues();
contentValues.put(READ, unreadCount == 0);
contentValues.put(UNREAD_COUNT, unreadCount);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues,ID_WHERE,
new String[] {String.valueOf(threadId)});
notifyConversationListListeners();
}
public boolean update(long threadId, boolean unarchive) {
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
long count = mmsSmsDatabase.getConversationCount(threadId);
@ -624,34 +539,6 @@ public class ThreadDatabase extends Database {
}
}
/**
* A lightweight utility method to retrieve the complete list of non-archived threads coupled with their recipient address.
* @return a tuple with non-null values: thread id, recipient address.
*/
public @NonNull List<Tuple2<Long, String>> getConversationListQuick() {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
ArrayList<Tuple2<Long, String>> result = new ArrayList<>();
try (Cursor cursor = db.query(
TABLE_NAME,
new String[]{ID, ADDRESS},
ARCHIVED + " = 0 AND " + MESSAGE_COUNT + " != 0 AND " + ADDRESS + " IS NOT NULL",
null,
null,
null,
null)) {
while (cursor != null && cursor.moveToNext()) {
result.add(new Tuple2<>(
cursor.getLong(cursor.getColumnIndexOrThrow(ID)),
cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))
));
}
}
return result;
}
private @NonNull String getFormattedBodyFor(@NonNull MessageRecord messageRecord) {
if (messageRecord.isMms()) {
MmsMessageRecord record = (MmsMessageRecord) messageRecord;
@ -735,7 +622,7 @@ public class ThreadDatabase extends Database {
public ThreadRecord getCurrent() {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.ID));
int distributionType = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.TYPE));
Address address = Address.Companion.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.ADDRESS)));
Address address = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.ADDRESS)));
Optional<RecipientSettings> settings;
Optional<GroupRecord> groupRecord;

@ -41,7 +41,7 @@ public class IdentityKeyMismatch {
@JsonIgnore
public Address getAddress() {
return Address.Companion.fromSerialized(address);
return Address.fromSerialized(address);
}
public IdentityKey getIdentityKey() {

@ -18,7 +18,7 @@ public class NetworkFailure {
@JsonIgnore
public Address getAddress() {
return Address.Companion.fromSerialized(address);
return Address.fromSerialized(address);
}
@Override

@ -17,14 +17,10 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.JobDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SearchDatabase;
import org.thoughtcrime.securesms.database.SessionDatabase;
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
@ -32,7 +28,6 @@ import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
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.loki.protocol.ClosedGroupsMigration;
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
@ -56,9 +51,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV18_CLEAR_BG_POLL_JOBS = 39;
private static final int lokiV19 = 40;
private static final int lokiV20 = 41;
private static final int lokiV21 = 42;
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV20;
private static final int DATABASE_VERSION = lokiV21;
private static final String DATABASE_NAME = "signal.db";
private final Context context;
@ -94,17 +90,12 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(GroupDatabase.CREATE_TABLE);
db.execSQL(RecipientDatabase.CREATE_TABLE);
db.execSQL(GroupReceiptDatabase.CREATE_TABLE);
db.execSQL(OneTimePreKeyDatabase.CREATE_TABLE);
db.execSQL(SignedPreKeyDatabase.CREATE_TABLE);
db.execSQL(SessionDatabase.CREATE_TABLE);
for (String sql : SearchDatabase.CREATE_TABLE) {
db.execSQL(sql);
}
for (String sql : JobDatabase.CREATE_TABLE) {
db.execSQL(sql);
}
db.execSQL(StickerDatabase.CREATE_TABLE);
db.execSQL(LokiAPIDatabase.getCreateSnodePoolTableCommand());
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathTableCommand());
db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand());
@ -130,9 +121,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
db.execSQL(LokiBackupFilesDatabase.getCreateTableCommand());
db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand());
db.execSQL(SharedSenderKeysDatabase.getCreateCurrentClosedGroupRatchetTableCommand());
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand());
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
@ -141,7 +129,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
executeStatements(db, DraftDatabase.CREATE_INDEXS);
executeStatements(db, GroupDatabase.CREATE_INDEXS);
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
executeStatements(db, StickerDatabase.CREATE_INDEXES);
}
@Override
@ -185,8 +172,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
if (oldVersion < lokiV12) {
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
db.execSQL(SharedSenderKeysDatabase.getCreateCurrentClosedGroupRatchetTableCommand());
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand());
db.execSQL(ClosedGroupsMigration.getCreateCurrentClosedGroupRatchetTableCommand());
db.execSQL(ClosedGroupsMigration.getCreateClosedGroupPrivateKeyTableCommand());
}
if (oldVersion < lokiV13) {
@ -198,7 +185,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
}
if (oldVersion < lokiV15) {
db.execSQL(SharedSenderKeysDatabase.getCreateOldClosedGroupRatchetTableCommand());
db.execSQL(ClosedGroupsMigration.getCreateOldClosedGroupRatchetTableCommand());
}
if (oldVersion < lokiV16) {
@ -252,6 +239,22 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
);
}
if (oldVersion < lokiV21) {
deleteJobRecords(db,
"ClosedGroupUpdateMessageSendJob",
"NullMessageSendJob",
"StickerDownloadJob",
"StickerPackDownloadJob",
"MmsSendJob",
"MmsReceiveJob",
"MmsDownloadJob",
"SmsSendJob",
"SmsSentJob",
"SmsReceiveJob",
"PushGroupUpdateJob",
"ResetThreadSessionJob");
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();

@ -1,21 +0,0 @@
package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import android.database.Cursor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
public class BlockedContactsLoader extends AbstractCursorLoader {
public BlockedContactsLoader(Context context) {
super(context);
}
@Override
public Cursor getCursor() {
return DatabaseFactory.getRecipientDatabase(getContext())
.getBlocked();
}
}

@ -1,81 +0,0 @@
package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
import java.util.LinkedList;
import java.util.List;
public class ConversationListLoader extends AbstractCursorLoader {
private final String filter;
private final boolean archived;
public ConversationListLoader(Context context, String filter, boolean archived) {
super(context);
this.filter = filter;
this.archived = archived;
}
@Override
public Cursor getCursor() {
if (filter != null && filter.trim().length() != 0) return getFilteredConversationList(filter);
else if (!archived) return getUnarchivedConversationList();
else return getArchivedConversationList();
}
private Cursor getUnarchivedConversationList() {
List<Cursor> cursorList = new LinkedList<>();
cursorList.add(DatabaseFactory.getThreadDatabase(context).getConversationList());
int archivedCount = DatabaseFactory.getThreadDatabase(context)
.getArchivedConversationListCount();
if (archivedCount > 0) {
MatrixCursor switchToArchiveCursor = new MatrixCursor(new String[] {
ThreadDatabase.ID, ThreadDatabase.DATE, ThreadDatabase.MESSAGE_COUNT,
ThreadDatabase.ADDRESS, ThreadDatabase.SNIPPET, ThreadDatabase.READ, ThreadDatabase.UNREAD_COUNT,
ThreadDatabase.TYPE, ThreadDatabase.SNIPPET_TYPE, ThreadDatabase.SNIPPET_URI,
ThreadDatabase.ARCHIVED, ThreadDatabase.STATUS, ThreadDatabase.DELIVERY_RECEIPT_COUNT,
ThreadDatabase.EXPIRES_IN, ThreadDatabase.LAST_SEEN, ThreadDatabase.READ_RECEIPT_COUNT}, 1);
if (cursorList.get(0).getCount() <= 0) {
switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount,
"-1", null, 1, 0, ThreadDatabase.DistributionTypes.INBOX_ZERO,
0, null, 0, -1, 0, 0, 0, -1});
}
switchToArchiveCursor.addRow(new Object[] {-1L, System.currentTimeMillis(), archivedCount,
"-1", null, 1, 0, ThreadDatabase.DistributionTypes.ARCHIVE,
0, null, 0, -1, 0, 0, 0, -1});
cursorList.add(switchToArchiveCursor);
}
return new MergeCursor(cursorList.toArray(new Cursor[0]));
}
private Cursor getArchivedConversationList() {
return DatabaseFactory.getThreadDatabase(context).getArchivedConversationList();
}
private Cursor getFilteredConversationList(String filter) {
List<String> numbers = ContactAccessor.getInstance().getNumbersForThreadSearchFilter(context, filter);
List<Address> addresses = new LinkedList<>();
for (String number : numbers) {
addresses.add(Address.Companion.fromExternal(context, number));
}
return DatabaseFactory.getThreadDatabase(context).getFilteredConversationList(addresses);
}
}

@ -1,28 +0,0 @@
package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import androidx.loader.content.AsyncTaskLoader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Map;
public class CountryListLoader extends AsyncTaskLoader<ArrayList<Map<String, String>>> {
public CountryListLoader(Context context) {
super(context);
}
@Override
public ArrayList<Map<String, String>> loadInBackground() {
return new ArrayList<>();
}
private static class RegionComparator implements Comparator<Map<String, String>> {
@Override
public int compare(Map<String, String> lhs, Map<String, String> rhs) {
return lhs.get("country_name").compareTo(rhs.get("country_name"));
}
}
}

@ -1,66 +0,0 @@
package org.thoughtcrime.securesms.database.model;
import androidx.annotation.NonNull;
public class IncomingSticker {
private final String packKey;
private final String packId;
private final String packTitle;
private final String packAuthor;
private final int stickerId;
private final String emoji;
private final boolean isCover;
private final boolean isInstalled;
public IncomingSticker(@NonNull String packId,
@NonNull String packKey,
@NonNull String packTitle,
@NonNull String packAuthor,
int stickerId,
@NonNull String emoji,
boolean isCover,
boolean isInstalled)
{
this.packId = packId;
this.packKey = packKey;
this.packTitle = packTitle;
this.packAuthor = packAuthor;
this.stickerId = stickerId;
this.emoji = emoji;
this.isCover = isCover;
this.isInstalled = isInstalled;
}
public @NonNull String getPackKey() {
return packKey;
}
public @NonNull String getPackId() {
return packId;
}
public @NonNull String getPackTitle() {
return packTitle;
}
public @NonNull String getPackAuthor() {
return packAuthor;
}
public int getStickerId() {
return stickerId;
}
public @NonNull String getEmoji() {
return emoji;
}
public boolean isCover() {
return isCover;
}
public boolean isInstalled() {
return isInstalled;
}
}

@ -1,40 +0,0 @@
package org.thoughtcrime.securesms.database.model;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
public class Sticker {
private final String packId;
private final String packKey;
private final int stickerId;
private final Attachment attachment;
public Sticker(@NonNull String packId,
@NonNull String packKey,
int stickerId,
@NonNull Attachment attachment)
{
this.packId = packId;
this.packKey = packKey;
this.stickerId = stickerId;
this.attachment = attachment;
}
public @NonNull String getPackId() {
return packId;
}
public @NonNull String getPackKey() {
return packKey;
}
public int getStickerId() {
return stickerId;
}
public @NonNull Attachment getAttachment() {
return attachment;
}
}

@ -1,78 +0,0 @@
package org.thoughtcrime.securesms.database.model;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.util.Objects;
/**
* Represents a record for a sticker pack in the {@link org.thoughtcrime.securesms.database.StickerDatabase}.
*/
public final class StickerPackRecord {
private final String packId;
private final String packKey;
private final Optional<String> title;
private final Optional<String> author;
private final StickerRecord cover;
private final boolean installed;
public StickerPackRecord(@NonNull String packId,
@NonNull String packKey,
@NonNull String title,
@NonNull String author,
@NonNull StickerRecord cover,
boolean installed)
{
this.packId = packId;
this.packKey = packKey;
this.title = TextUtils.isEmpty(title) ? Optional.absent() : Optional.of(title);
this.author = TextUtils.isEmpty(author) ? Optional.absent() : Optional.of(author);
this.cover = cover;
this.installed = installed;
}
public @NonNull String getPackId() {
return packId;
}
public @NonNull String getPackKey() {
return packKey;
}
public @NonNull Optional<String> getTitle() {
return title;
}
public @NonNull Optional<String> getAuthor() {
return author;
}
public @NonNull StickerRecord getCover() {
return cover;
}
public boolean isInstalled() {
return installed;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StickerPackRecord record = (StickerPackRecord) o;
return installed == record.installed &&
packId.equals(record.packId) &&
packKey.equals(record.packKey) &&
title.equals(record.title) &&
author.equals(record.author) &&
cover.equals(record.cover);
}
@Override
public int hashCode() {
return Objects.hash(packId, packKey, title, author, cover, installed);
}
}

@ -1,90 +0,0 @@
package org.thoughtcrime.securesms.database.model;
import android.net.Uri;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.mms.PartAuthority;
import java.util.Objects;
/**
* Represents a record for a sticker pack in the {@link org.thoughtcrime.securesms.database.StickerDatabase}.
*/
public final class StickerRecord {
private final long rowId;
private final String packId;
private final String packKey;
private final int stickerId;
private final String emoji;
private final long size;
private final boolean isCover;
public StickerRecord(long rowId,
@NonNull String packId,
@NonNull String packKey,
int stickerId,
@NonNull String emoji,
long size,
boolean isCover)
{
this.rowId = rowId;
this.packId = packId;
this.packKey = packKey;
this.stickerId = stickerId;
this.emoji = emoji;
this.size = size;
this.isCover = isCover;
}
public long getRowId() {
return rowId;
}
public @NonNull String getPackId() {
return packId;
}
public @NonNull String getPackKey() {
return packKey;
}
public int getStickerId() {
return stickerId;
}
public @NonNull Uri getUri() {
return PartAuthority.getStickerUri(rowId);
}
public @NonNull String getEmoji() {
return emoji;
}
public long getSize() {
return size;
}
public boolean isCover() {
return isCover;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StickerRecord that = (StickerRecord) o;
return rowId == that.rowId &&
stickerId == that.stickerId &&
size == that.size &&
isCover == that.isCover &&
packId.equals(that.packId) &&
packKey.equals(that.packKey) &&
emoji.equals(that.emoji);
}
@Override
public int hashCode() {
return Objects.hash(rowId, packId, packKey, stickerId, emoji, size, isCover);
}
}

@ -2,14 +2,8 @@ package org.thoughtcrime.securesms.dependencies;
import android.content.Context;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.util.CredentialsProvider;
import org.session.libsignal.service.api.util.SleepTimer;
import org.session.libsignal.service.api.util.UptimeSleepTimer;
import org.session.libsignal.service.api.websocket.ConnectivityListener;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -18,42 +12,26 @@ import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
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.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.jobs.SendDeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.jobs.StickerDownloadJob;
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.session.libsignal.utilities.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;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.stickers.StickerPackPreviewRepository;
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
import org.thoughtcrime.securesms.util.RealtimeSleepTimer;
import org.session.libsession.utilities.TextSecurePreferences;
import dagger.Module;
import dagger.Provides;
import network.loki.messenger.BuildConfig;
@Module(complete = false, injects = {PushGroupSendJob.class,
PushTextSendJob.class,
PushMediaSendJob.class,
AttachmentDownloadJob.class,
IncomingMessageObserver.class,
PushNotificationReceiveJob.class,
RequestGroupInfoJob.class,
PushGroupUpdateJob.class,
AvatarDownloadJob.class,
RetrieveProfileAvatarJob.class,
SendReadReceiptJob.class,
@ -62,54 +40,31 @@ import network.loki.messenger.BuildConfig;
TypingSendJob.class,
AttachmentUploadJob.class,
PushDecryptJob.class,
StickerDownloadJob.class,
StickerPackPreviewRepository.class,
StickerRemoteUriLoader.Factory.class,
StickerPackDownloadJob.class,
LinkPreviewRepository.class})
public class SignalCommunicationModule {
private static final String TAG = SignalCommunicationModule.class.getSimpleName();
private final Context context;
private final SignalServiceNetworkAccess networkAccess;
private SignalServiceAccountManager accountManager;
private SignalServiceMessageSender messageSender;
private SignalServiceMessageReceiver messageReceiver;
public SignalCommunicationModule(Context context, SignalServiceNetworkAccess networkAccess) {
public SignalCommunicationModule(Context context) {
this.context = context;
this.networkAccess = networkAccess;
}
@Provides
public synchronized SignalServiceMessageSender provideSignalMessageSender() {
if (this.messageSender == null) {
this.messageSender = new SignalServiceMessageSender(networkAccess.getConfiguration(context),
new DynamicCredentialsProvider(context),
new SignalProtocolStoreImpl(context),
BuildConfig.USER_AGENT,
TextSecurePreferences.isMultiDevice(context),
Optional.fromNullable(IncomingMessageObserver.getPipe()),
Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()),
Optional.of(new MessageSenderEventListener(context)),
this.messageSender = new SignalServiceMessageSender(new SignalProtocolStoreImpl(context),
TextSecurePreferences.getLocalNumber(context),
DatabaseFactory.getLokiAPIDatabase(context),
DatabaseFactory.getSSKDatabase(context),
DatabaseFactory.getLokiThreadDatabase(context),
DatabaseFactory.getLokiMessageDatabase(context),
null, // DatabaseFactory.getLokiPreKeyBundleDatabase(context)
new SessionProtocolImpl(context),
new SessionResetImplementation(context),
DatabaseFactory.getLokiUserDatabase(context),
DatabaseFactory.getGroupDatabase(context),
((ApplicationContext)context.getApplicationContext()).broadcaster);
} else {
this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe(), IncomingMessageObserver.getUnidentifiedPipe());
this.messageSender.setIsMultiDevice(TextSecurePreferences.isMultiDevice(context));
}
return this.messageSender;
@ -118,70 +73,9 @@ public class SignalCommunicationModule {
@Provides
synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() {
if (this.messageReceiver == null) {
SleepTimer sleepTimer = TextSecurePreferences.isFcmDisabled(context) ? new RealtimeSleepTimer(context) : new UptimeSleepTimer();
this.messageReceiver = new SignalServiceMessageReceiver(networkAccess.getConfiguration(context),
new DynamicCredentialsProvider(context),
BuildConfig.USER_AGENT,
new PipeConnectivityListener(),
sleepTimer);
this.messageReceiver = new SignalServiceMessageReceiver();
}
return this.messageReceiver;
}
@Provides
synchronized SignalServiceNetworkAccess provideSignalServiceNetworkAccess() {
return networkAccess;
}
private static class DynamicCredentialsProvider implements CredentialsProvider {
private final Context context;
private DynamicCredentialsProvider(Context context) {
this.context = context.getApplicationContext();
}
@Override
public String getUser() {
return TextSecurePreferences.getLocalNumber(context);
}
@Override
public String getPassword() {
return TextSecurePreferences.getPushServerPassword(context);
}
@Override
public String getSignalingKey() {
return TextSecurePreferences.getSignalingKey(context);
}
}
private class PipeConnectivityListener implements ConnectivityListener {
@Override
public void onConnected() {
Log.i(TAG, "onConnected()");
}
@Override
public void onConnecting() {
Log.i(TAG, "onConnecting()");
}
@Override
public void onDisconnected() {
Log.w(TAG, "onDisconnected()");
}
@Override
public void onAuthenticationFailure() {
Log.w(TAG, "onAuthenticationFailure()");
TextSecurePreferences.setUnauthorizedReceived(context, true);
}
}
}

@ -25,7 +25,6 @@ import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.session.libsignal.service.api.util.InvalidNumberException;
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
import java.util.Collection;
@ -44,46 +43,10 @@ public class GroupManager {
}
public static long getThreadIDFromGroupID(String groupID, @NonNull Context context) {
final Recipient groupRecipient = Recipient.from(context, Address.Companion.fromSerialized(groupID), false);
final Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupID), false);
return DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient);
}
public static @NonNull GroupActionResult createGroup(@NonNull Context context,
@NonNull Set<Recipient> members,
@Nullable Bitmap avatar,
@Nullable String name,
@NonNull Set<Recipient> admins)
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
String id = GroupUtil.getEncodedClosedGroupID(database.allocateGroupId()); // TODO: The group id is double encoded here 1
return createGroup(id, context, members, avatar, name, admins);
}
public static @NonNull GroupActionResult createGroup(@NonNull String id,
@NonNull Context context,
@NonNull Set<Recipient> members,
@Nullable Bitmap avatar,
@Nullable String name,
@NonNull Set<Recipient> admins)
{
final byte[] avatarBytes = BitmapUtil.toByteArray(avatar);
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
final String groupId = GroupUtil.getEncodedClosedGroupID(id.getBytes()); // TODO: The group id is double encoded here 2
final Recipient groupRecipient = Recipient.from(context, Address.Companion.fromSerialized(groupId), false);
final Set<Address> memberAddresses = getMemberAddresses(members);
final Set<Address> adminAddresses = getMemberAddresses(admins);
String masterPublicKeyOrNull = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
String masterPublicKey = masterPublicKeyOrNull != null ? masterPublicKeyOrNull : TextSecurePreferences.getLocalNumber(context);
memberAddresses.add(Address.Companion.fromSerialized(masterPublicKey));
groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null, new LinkedList<>(adminAddresses), System.currentTimeMillis());
groupDatabase.updateProfilePicture(groupId, avatarBytes);
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient, true);
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
}
public static @NonNull GroupActionResult createOpenGroup(@NonNull String id,
@NonNull Context context,
@Nullable Bitmap avatar,
@ -100,10 +63,10 @@ public class GroupManager {
{
final byte[] avatarBytes = BitmapUtil.toByteArray(avatar);
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
final Recipient groupRecipient = Recipient.from(context, Address.Companion.fromSerialized(groupId), false);
final Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), false);
final Set<Address> memberAddresses = new HashSet<>();
memberAddresses.add(Address.Companion.fromSerialized(Objects.requireNonNull(TextSecurePreferences.getLocalNumber(context))));
memberAddresses.add(Address.fromSerialized(Objects.requireNonNull(TextSecurePreferences.getLocalNumber(context))));
groupDatabase.create(groupId, name, new LinkedList<>(memberAddresses), null, null, new LinkedList<>(), System.currentTimeMillis());
groupDatabase.updateProfilePicture(groupId, avatarBytes);
@ -118,7 +81,7 @@ public class GroupManager {
{
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
final ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
final Recipient groupRecipient = Recipient.from(context, Address.Companion.fromSerialized(groupId), false);
final Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), false);
if (!groupDatabase.getGroup(groupId).isPresent()) {
return false;
@ -138,14 +101,13 @@ public class GroupManager {
@Nullable Bitmap avatar,
@Nullable String name,
@NonNull Set<Recipient> admins)
throws InvalidNumberException
{
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
final Set<Address> memberAddresses = getMemberAddresses(members);
final Set<Address> adminAddresses = getMemberAddresses(admins);
final byte[] avatarBytes = BitmapUtil.toByteArray(avatar);
memberAddresses.add(Address.Companion.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
memberAddresses.add(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
groupDatabase.updateMembers(groupId, new LinkedList<>(memberAddresses));
groupDatabase.updateAdmins(groupId, new LinkedList<>(adminAddresses));
groupDatabase.updateTitle(groupId, name);
@ -154,7 +116,7 @@ public class GroupManager {
if (!GroupUtil.isMmsGroup(groupId)) {
return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes, adminAddresses);
} else {
Recipient groupRecipient = Recipient.from(context, Address.Companion.fromSerialized(groupId), true);
Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(groupId), true);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(groupRecipient);
return new GroupActionResult(groupRecipient, threadId);
}
@ -168,7 +130,7 @@ public class GroupManager {
@NonNull Set<Address> admins)
{
Attachment avatarAttachment = null;
Address groupAddress = Address.Companion.fromSerialized(groupId);
Address groupAddress = Address.fromSerialized(groupId);
Recipient groupRecipient = Recipient.from(context, groupAddress, false);
List<String> numbers = new LinkedList<>();
@ -191,7 +153,7 @@ public class GroupManager {
if (avatar != null) {
Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory();
avatarAttachment = new UriAttachment(avatarUri, MediaTypes.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null);
avatarAttachment = new UriAttachment(avatarUri, MediaTypes.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null);
}
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, 0, null, Collections.emptyList(), Collections.emptyList());

@ -1,335 +0,0 @@
package org.thoughtcrime.securesms.groups;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.GroupRecord;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsignal.utilities.Base64;
import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceContent;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.SignalServiceGroup.Type;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static org.session.libsignal.service.internal.push.SignalServiceProtos.AttachmentPointer;
import static org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
public class GroupMessageProcessor {
private static final String TAG = GroupMessageProcessor.class.getSimpleName();
public static @Nullable Long process(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
boolean outgoing)
{
if (!message.getGroupInfo().isPresent() || message.getGroupInfo().get().getGroupId() == null) {
Log.w(TAG, "Received group message with no id! Ignoring...");
return null;
}
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
SignalServiceGroup group = message.getGroupInfo().get();
String id = GroupUtil.getEncodedId(group);
Optional<GroupRecord> record = database.getGroup(id);
if (record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupUpdate(context, content, group, record.get(), outgoing);
} else if (!record.isPresent() && group.getType() == Type.UPDATE) {
return handleGroupCreate(context, content, group, outgoing, message.getTimestamp());
} else if (record.isPresent() && group.getType() == Type.QUIT) {
return handleGroupLeave(context, content, group, record.get(), outgoing);
} else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) {
return handleGroupInfoRequest(context, content, group, record.get());
} else {
Log.w(TAG, "Received unknown type, ignoring...");
return null;
}
}
private static @Nullable Long handleGroupCreate(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group,
boolean outgoing,
Long formationTimestamp)
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
String id = GroupUtil.getEncodedId(group);
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.UPDATE);
SignalServiceAttachment avatar = group.getAvatar().orNull();
List<Address> members = group.getMembers().isPresent() ? new LinkedList<>() : null;
List<Address> admins = group.getAdmins().isPresent() ? new LinkedList<>() : null;
if (group.getMembers().isPresent()) {
for (String member : group.getMembers().get()) {
members.add(Address.Companion.fromExternal(context, member));
}
}
// Loki - Parse admins
if (group.getAdmins().isPresent()) {
for (String admin : group.getAdmins().get()) {
admins.add(Address.Companion.fromExternal(context, admin));
}
}
database.create(id, group.getName().orNull(), members,
avatar != null && avatar.isPointer() ? avatar.asPointer() : null, null, admins, formationTimestamp);
if (group.getMembers().isPresent()) {
ClosedGroupsProtocol.establishSessionsWithMembersIfNeeded(context, group.getMembers().get());
}
return storeMessage(context, content, group, builder.build(), outgoing);
}
private static @Nullable Long handleGroupUpdate(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group,
@NonNull GroupRecord groupRecord,
boolean outgoing)
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
String id = GroupUtil.getEncodedId(group);
Address address = Address.Companion.fromExternal(context, GroupUtil.getEncodedId(group));
Recipient recipient = Recipient.from(context, address, false);
String userMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
if (userMasterDevice == null) { userMasterDevice = TextSecurePreferences.getLocalNumber(context); }
if (content.getSender().equals(userMasterDevice)) {
long threadId = threadDatabase.getThreadIdIfExistsFor(recipient);
return threadId == -1 ? null : threadId;
}
if (group.getGroupType() == SignalServiceGroup.GroupType.SIGNAL) {
// Loki - Only update the group if the group admin sent the message
String masterDevice = MultiDeviceProtocol.shared.getMasterDevice(content.getSender());
if (masterDevice == null) { masterDevice = content.getSender(); }
if (!groupRecord.getAdmins().contains(Address.Companion.fromSerialized(masterDevice))) {
Log.d("Loki", "Received a group update message from a non-admin user for: " + id +"; ignoring.");
return null;
}
// Loki - Only process update messages if we're part of the group
Address userMasterDeviceAddress = Address.Companion.fromSerialized(userMasterDevice);
if (!groupRecord.getMembers().contains(userMasterDeviceAddress) &&
!group.getMembers().or(Collections.emptyList()).contains(userMasterDevice)) {
Log.d("Loki", "Received a group update message from a group we're not a member of: " + id + "; ignoring.");
database.setActive(id, false);
return null;
}
}
Set<Address> currentMembers = new HashSet<>(groupRecord.getMembers());
Set<Address> newMembers = new HashSet<>();
for (String messageMember : group.getMembers().get()) {
newMembers.add(Address.Companion.fromExternal(context, messageMember));
}
// Added members are the members who are present in newMembers but not in currentMembers
Set<Address> addedMembers = new HashSet<>(newMembers);
addedMembers.removeAll(currentMembers);
// Kicked members are members who are present in currentMembers but not in newMembers
Set<Address> removedMembers = new HashSet<>(currentMembers);
removedMembers.removeAll(newMembers);
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.UPDATE);
// Update our group members if they're different
if (!currentMembers.equals(newMembers)) {
database.updateMembers(id, new LinkedList<>(newMembers));
}
// Add any new or removed members to the group context.
// This will allow us later to iterate over them to check if they left or were added for UI purposes.
for (Address addedMember : addedMembers) {
builder.addNewMembers(addedMember.serialize());
}
for (Address removedMember : removedMembers) {
builder.addRemovedMembers(removedMember.serialize());
}
if (group.getName().isPresent() || group.getAvatar().isPresent()) {
SignalServiceAttachment avatar = group.getAvatar().orNull();
database.update(id, group.getName().orNull(), avatar != null ? avatar.asPointer() : null);
}
if (group.getName().isPresent() && group.getName().get().equals(groupRecord.getTitle())) {
builder.clearName();
}
// If we were removed then we need to disable the chat
if (removedMembers.contains(Address.Companion.fromSerialized(userMasterDevice))) {
database.setActive(id, false);
} else {
if (!groupRecord.isActive()) database.setActive(id, true);
if (group.getMembers().isPresent()) {
ClosedGroupsProtocol.establishSessionsWithMembersIfNeeded(context, group.getMembers().get());
}
}
return storeMessage(context, content, group, builder.build(), outgoing);
}
private static Long handleGroupInfoRequest(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group,
@NonNull GroupRecord record)
{
String masterDevice = MultiDeviceProtocol.shared.getMasterDevice(content.getSender());
if (masterDevice == null) { masterDevice = content.getSender(); }
if (record.getMembers().contains(Address.Companion.fromSerialized(masterDevice))) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new PushGroupUpdateJob(content.getSender(), group.getGroupId()));
}
return null;
}
private static Long handleGroupLeave(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group,
@NonNull GroupRecord record,
boolean outgoing)
{
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
String id = GroupUtil.getEncodedId(group);
List<Address> members = record.getMembers();
GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.QUIT);
String masterDevice = MultiDeviceProtocol.shared.getMasterDevice(content.getSender());
if (masterDevice == null) { masterDevice = content.getSender(); }
if (members.contains(Address.Companion.fromExternal(context, masterDevice))) {
database.removeMember(id, Address.Companion.fromExternal(context, masterDevice));
if (outgoing) database.setActive(id, false);
return storeMessage(context, content, group, builder.build(), outgoing);
}
return null;
}
private static @Nullable Long storeMessage(@NonNull Context context,
@NonNull SignalServiceContent content,
@NonNull SignalServiceGroup group,
@NonNull GroupContext storage,
boolean outgoing)
{
if (group.getAvatar().isPresent()) {
ApplicationContext.getInstance(context).getJobManager()
.add(new AvatarDownloadJob(GroupUtil.getEncodedId(group)));
}
try {
if (outgoing) {
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
Address address = Address.Companion.fromExternal(context, GroupUtil.getEncodedId(group));
Recipient recipient = Recipient.from(context, address, false);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, 0, null, Collections.emptyList(), Collections.emptyList());
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
Address senderAddress = Address.Companion.fromExternal(context, content.getSender());
MessageRecord existingMessage = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(content.getTimestamp(), senderAddress);
long messageId;
if (existingMessage != null) {
messageId = existingMessage.getId();
} else {
messageId = mmsDatabase.insertMessageOutbox(outgoingMessage, threadId, false, null);
}
mmsDatabase.markAsSent(messageId, true);
return threadId;
} else {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
String body = Base64.encodeBytes(storage.toByteArray());
IncomingTextMessage incoming = new IncomingTextMessage(Address.Companion.fromExternal(context, content.getSender()), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, content.isNeedsReceipt());
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(groupMessage);
if (insertResult.isPresent()) {
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, insertResult.get().getThreadId());
return insertResult.get().getThreadId();
} else {
return null;
}
}
} catch (MmsException e) {
Log.w(TAG, e);
}
return null;
}
private static GroupContext.Builder createGroupContext(SignalServiceGroup group) {
GroupContext.Builder builder = GroupContext.newBuilder();
builder.setId(ByteString.copyFrom(group.getGroupId()));
if (group.getAvatar().isPresent() && group.getAvatar().get().isPointer()) {
builder.setAvatar(AttachmentPointer.newBuilder()
.setId(group.getAvatar().get().asPointer().getId())
.setKey(ByteString.copyFrom(group.getAvatar().get().asPointer().getKey()))
.setContentType(group.getAvatar().get().getContentType()));
}
if (group.getName().isPresent()) {
builder.setName(group.getName().get());
}
if (group.getMembers().isPresent()) {
builder.addAllMembers(group.getMembers().get());
}
if (group.getAdmins().isPresent()) {
builder.addAllAdmins(group.getAdmins().get());
}
return builder;
}
}

@ -1,169 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migration;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsignal.utilities.logging.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.HashMap;
import java.util.Map;
/**
* Takes a persisted data blob stored by WorkManager and converts it to our {@link Data} class.
*/
final class DataMigrator {
private static final String TAG = Log.tag(DataMigrator.class);
static final Data convert(@NonNull byte[] workManagerData) {
Map<String, Object> values = parseWorkManagerDataMap(workManagerData);
Data.Builder builder = new Data.Builder();
for (Map.Entry<String, Object> entry : values.entrySet()) {
Object value = entry.getValue();
if (value == null) {
builder.putString(entry.getKey(), null);
} else {
Class type = value.getClass();
if (type == String.class) {
builder.putString(entry.getKey(), (String) value);
} else if (type == String[].class) {
builder.putStringArray(entry.getKey(), (String[]) value);
} else if (type == Integer.class || type == int.class) {
builder.putInt(entry.getKey(), (int) value);
} else if (type == Integer[].class || type == int[].class) {
builder.putIntArray(entry.getKey(), convertToIntArray(value, type));
} else if (type == Long.class || type == long.class) {
builder.putLong(entry.getKey(), (long) value);
} else if (type == Long[].class || type == long[].class) {
builder.putLongArray(entry.getKey(), convertToLongArray(value, type));
} else if (type == Float.class || type == float.class) {
builder.putFloat(entry.getKey(), (float) value);
} else if (type == Float[].class || type == float[].class) {
builder.putFloatArray(entry.getKey(), convertToFloatArray(value, type));
} else if (type == Double.class || type == double.class) {
builder.putDouble(entry.getKey(), (double) value);
} else if (type == Double[].class || type == double[].class) {
builder.putDoubleArray(entry.getKey(), convertToDoubleArray(value, type));
} else if (type == Boolean.class || type == boolean.class) {
builder.putBoolean(entry.getKey(), (boolean) value);
} else if (type == Boolean[].class || type == boolean[].class) {
builder.putBooleanArray(entry.getKey(), convertToBooleanArray(value, type));
} else {
Log.w(TAG, "Encountered unexpected type '" + type + "'. Skipping.");
}
}
}
return builder.build();
}
private static @NonNull Map<String, Object> parseWorkManagerDataMap(@NonNull byte[] bytes) throws IllegalStateException {
Map<String, Object> map = new HashMap<>();
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(inputStream);
for (int i = objectInputStream.readInt(); i > 0; i--) {
map.put(objectInputStream.readUTF(), objectInputStream.readObject());
}
} catch (IOException | ClassNotFoundException e) {
Log.w(TAG, "Failed to read WorkManager data.", e);
} finally {
try {
inputStream.close();
if (objectInputStream != null) {
objectInputStream.close();
}
} catch (IOException e) {
Log.e(TAG, "Failed to close streams after reading WorkManager data.", e);
}
}
return map;
}
private static int[] convertToIntArray(Object value, Class type) {
if (type == int[].class) {
return (int[]) value;
}
Integer[] casted = (Integer[]) value;
int[] output = new int[casted.length];
for (int i = 0; i < casted.length; i++) {
output[i] = casted[i];
}
return output;
}
private static long[] convertToLongArray(Object value, Class type) {
if (type == long[].class) {
return (long[]) value;
}
Long[] casted = (Long[]) value;
long[] output = new long[casted.length];
for (int i = 0; i < casted.length; i++) {
output[i] = casted[i];
}
return output;
}
private static float[] convertToFloatArray(Object value, Class type) {
if (type == float[].class) {
return (float[]) value;
}
Float[] casted = (Float[]) value;
float[] output = new float[casted.length];
for (int i = 0; i < casted.length; i++) {
output[i] = casted[i];
}
return output;
}
private static double[] convertToDoubleArray(Object value, Class type) {
if (type == double[].class) {
return (double[]) value;
}
Double[] casted = (Double[]) value;
double[] output = new double[casted.length];
for (int i = 0; i < casted.length; i++) {
output[i] = casted[i];
}
return output;
}
private static boolean[] convertToBooleanArray(Object value, Class type) {
if (type == boolean[].class) {
return (boolean[]) value;
}
Boolean[] casted = (Boolean[]) value;
boolean[] output = new boolean[casted.length];
for (int i = 0; i < casted.length; i++) {
output[i] = casted[i];
}
return output;
}
}

@ -94,8 +94,8 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment);
SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment);
SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker(), new SignalServiceAddress(destination.serialize()));
attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get();
SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), false, new SignalServiceAddress(destination.serialize()));
attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), databaseAttachment.getFastPreflightId()).get();
} catch (Exception e) {
// On any error make sure we mark the related DB record's transfer state as failed.
database.updateAttachmentAfterUploadFailed(databaseAttachment.getAttachmentId());
@ -163,7 +163,7 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
public static final class Factory implements Job.Factory<AttachmentUploadJob> {
@Override
public @NonNull AttachmentUploadJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new AttachmentUploadJob(parameters, new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)), Address.Companion.fromSerialized(data.getString(KEY_DESTINATION)));
return new AttachmentUploadJob(parameters, new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)), Address.fromSerialized(data.getString(KEY_DESTINATION)));
}
}
}

@ -15,15 +15,11 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
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.ClosedGroupUpdateMessageSendJobV2;
import org.thoughtcrime.securesms.loki.protocol.NullMessageSendJob;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -37,34 +33,21 @@ public final class JobManagerFactories {
put(AttachmentDownloadJob.KEY, new AttachmentDownloadJob.Factory());
put(AttachmentUploadJob.KEY, new AttachmentUploadJob.Factory());
put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory());
put(ClosedGroupUpdateMessageSendJob.KEY, new ClosedGroupUpdateMessageSendJob.Factory());
put(ClosedGroupUpdateMessageSendJobV2.KEY, new ClosedGroupUpdateMessageSendJobV2.Factory());
put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
put(MmsDownloadJob.KEY, new MmsDownloadJob.Factory());
put(MmsReceiveJob.KEY, new MmsReceiveJob.Factory());
put(MmsSendJob.KEY, new MmsSendJob.Factory());
put(NullMessageSendJob.KEY, new NullMessageSendJob.Factory());
put(PushContentReceiveJob.KEY, new PushContentReceiveJob.Factory());
put(PushDecryptJob.KEY, new PushDecryptJob.Factory());
put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory());
put(PushGroupUpdateJob.KEY, new PushGroupUpdateJob.Factory());
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
put(PushNotificationReceiveJob.KEY, new PushNotificationReceiveJob.Factory());
put(PushTextSendJob.KEY, new PushTextSendJob.Factory());
put(RequestGroupInfoJob.KEY, new RequestGroupInfoJob.Factory());
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application));
put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory());
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory());
put(SmsReceiveJob.KEY, new SmsReceiveJob.Factory());
put(SmsSendJob.KEY, new SmsSendJob.Factory());
put(SmsSentJob.KEY, new SmsSentJob.Factory());
put(StickerDownloadJob.KEY, new StickerDownloadJob.Factory());
put(StickerPackDownloadJob.KEY, new StickerPackDownloadJob.Factory());
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
put(TypingSendJob.KEY, new TypingSendJob.Factory());
put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
put(PrepareAttachmentAudioExtrasJob.KEY, new PrepareAttachmentAudioExtrasJob.Factory());
put(ResetThreadSessionJob.KEY, new ResetThreadSessionJob.Factory());
}};
factoryKeys.addAll(factoryHashMap.keySet());
return factoryHashMap;

@ -1,285 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.mms.pdu_alt.CharacterSets;
import com.google.android.mms.pdu_alt.EncodedStringValue;
import com.google.android.mms.pdu_alt.PduBody;
import com.google.android.mms.pdu_alt.PduPart;
import com.google.android.mms.pdu_alt.RetrieveConf;
import org.session.libsession.messaging.jobs.Data;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
import org.thoughtcrime.securesms.mms.CompatMmsConnection;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.MmsRadioException;
import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.session.libsignal.libsignal.DuplicateMessageException;
import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.LegacyMessageException;
import org.session.libsignal.libsignal.NoSessionException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class MmsDownloadJob extends BaseJob {
public static final String KEY = "MmsDownloadJob";
private static final String TAG = MmsDownloadJob.class.getSimpleName();
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_THREAD_ID = "thread_id";
private static final String KEY_AUTOMATIC = "automatic";
private long messageId;
private long threadId;
private boolean automatic;
private MessageNotifier messageNotifier;
public MmsDownloadJob(long messageId, long threadId, boolean automatic) {
this(new Job.Parameters.Builder()
.setQueue("mms-operation")
.setMaxAttempts(25)
.build(),
messageId,
threadId,
automatic);
}
private MmsDownloadJob(@NonNull Job.Parameters parameters, long messageId, long threadId, boolean automatic) {
super(parameters);
this.messageId = messageId;
this.threadId = threadId;
this.automatic = automatic;
this.messageNotifier = ApplicationContext.getInstance(context).messageNotifier;
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId)
.putLong(KEY_THREAD_ID, threadId)
.putBoolean(KEY_AUTOMATIC, automatic)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onAdded() {
if (automatic && KeyCachingService.isLocked(context)) {
DatabaseFactory.getMmsDatabase(context).markIncomingNotificationReceived(threadId);
messageNotifier.updateNotification(context);
}
}
@Override
public void onRun() {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Optional<MmsDatabase.MmsNotificationInfo> notification = database.getNotification(messageId);
if (!notification.isPresent()) {
Log.w(TAG, "No notification for ID: " + messageId);
return;
}
try {
if (notification.get().getContentLocation() == null) {
throw new MmsException("Notification content location was null.");
}
if (!TextSecurePreferences.isPushRegistered(context)) {
throw new MmsException("Not registered");
}
database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_CONNECTING);
String contentLocation = notification.get().getContentLocation();
byte[] transactionId = new byte[0];
try {
if (notification.get().getTransactionId() != null) {
transactionId = notification.get().getTransactionId().getBytes(CharacterSets.MIMENAME_ISO_8859_1);
} else {
Log.w(TAG, "No transaction ID!");
}
} catch (UnsupportedEncodingException e) {
Log.w(TAG, e);
}
Log.i(TAG, "Downloading mms at " + Uri.parse(contentLocation).getHost() + ", subscription ID: " + notification.get().getSubscriptionId());
RetrieveConf retrieveConf = new CompatMmsConnection(context).retrieve(contentLocation, transactionId, notification.get().getSubscriptionId());
if (retrieveConf == null) {
throw new MmsException("RetrieveConf was null");
}
storeRetrievedMms(contentLocation, messageId, threadId, retrieveConf, notification.get().getSubscriptionId(), notification.get().getFrom());
} catch (ApnUnavailableException e) {
Log.w(TAG, e);
handleDownloadError(messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
automatic);
} catch (MmsException e) {
Log.w(TAG, e);
handleDownloadError(messageId, threadId,
MmsDatabase.Status.DOWNLOAD_HARD_FAILURE,
automatic);
} catch (MmsRadioException | IOException e) {
Log.w(TAG, e);
handleDownloadError(messageId, threadId,
MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE,
automatic);
} catch (DuplicateMessageException e) {
Log.w(TAG, e);
database.markAsDecryptDuplicate(messageId, threadId);
} catch (LegacyMessageException e) {
Log.w(TAG, e);
database.markAsLegacyVersion(messageId, threadId);
} catch (NoSessionException e) {
Log.w(TAG, e);
database.markAsNoSession(messageId, threadId);
} catch (InvalidMessageException e) {
Log.w(TAG, e);
database.markAsDecryptFailed(messageId, threadId);
}
}
@Override
public void onCanceled() {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_SOFT_FAILURE);
if (automatic) {
database.markIncomingNotificationReceived(threadId);
messageNotifier.updateNotification(context, threadId);
}
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
return false;
}
private void storeRetrievedMms(String contentLocation,
long messageId, long threadId, RetrieveConf retrieved,
int subscriptionId, @Nullable Address notificationFrom)
throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException,
LegacyMessageException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Optional<Address> group = Optional.absent();
Set<Address> members = new HashSet<>();
String body = null;
List<Attachment> attachments = new LinkedList<>();
Address from;
if (retrieved.getFrom() != null) {
from = Address.Companion.fromExternal(context, Util.toIsoString(retrieved.getFrom().getTextString()));
} else if (notificationFrom != null) {
from = notificationFrom;
} else {
from = Address.Companion.getUNKNOWN();
}
if (retrieved.getTo() != null) {
for (EncodedStringValue toValue : retrieved.getTo()) {
members.add(Address.Companion.fromExternal(context, Util.toIsoString(toValue.getTextString())));
}
}
if (retrieved.getCc() != null) {
for (EncodedStringValue ccValue : retrieved.getCc()) {
members.add(Address.Companion.fromExternal(context, Util.toIsoString(ccValue.getTextString())));
}
}
members.add(from);
members.add(Address.Companion.fromExternal(context, TextSecurePreferences.getLocalNumber(context)));
if (retrieved.getBody() != null) {
body = PartParser.getMessageText(retrieved.getBody());
PduBody media = PartParser.getSupportedMediaParts(retrieved.getBody());
for (int i=0;i<media.getPartsNum();i++) {
PduPart part = media.getPart(i);
if (part.getData() != null) {
Uri uri = BlobProvider.getInstance().forData(part.getData()).createForSingleUseInMemory();
String name = null;
if (part.getName() != null) name = Util.toIsoString(part.getName());
attachments.add(new UriAttachment(uri, Util.toIsoString(part.getContentType()),
AttachmentDatabase.TRANSFER_PROGRESS_DONE,
part.getData().length, name, false, false, null, null));
}
}
}
if (members.size() > 2) {
group = Optional.of(Address.Companion.fromSerialized(DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(new LinkedList<>(members), new LinkedList<>())));
}
IncomingMediaMessage message = new IncomingMediaMessage(from, group, body, retrieved.getDate() * 1000L, attachments, subscriptionId, 0, false, false);
Optional<InsertResult> insertResult = database.insertMessageInbox(message, contentLocation, threadId);
if (insertResult.isPresent()) {
database.delete(messageId);
messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
}
private void handleDownloadError(long messageId, long threadId, int downloadStatus, boolean automatic)
{
MmsDatabase db = DatabaseFactory.getMmsDatabase(context);
db.markDownloadState(messageId, downloadStatus);
if (automatic) {
db.markIncomingNotificationReceived(threadId);
messageNotifier.updateNotification(context, threadId);
}
}
public static final class Factory implements Job.Factory<MmsDownloadJob> {
@Override
public @NonNull MmsDownloadJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new MmsDownloadJob(parameters,
data.getLong(KEY_MESSAGE_ID),
data.getLong(KEY_THREAD_ID),
data.getBoolean(KEY_AUTOMATIC));
}
}
}

@ -1,126 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import org.session.libsession.messaging.jobs.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.session.libsignal.utilities.logging.Log;
import androidx.annotation.NonNull;
import android.util.Pair;
import com.google.android.mms.pdu_alt.GenericPdu;
import com.google.android.mms.pdu_alt.NotificationInd;
import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.android.mms.pdu_alt.PduParser;
import org.thoughtcrime.securesms.ApplicationContext;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsignal.utilities.Base64;
import org.session.libsession.utilities.Util;
import java.io.IOException;
public class MmsReceiveJob extends BaseJob {
public static final String KEY = "MmsReceiveJob";
private static final String TAG = MmsReceiveJob.class.getSimpleName();
private static final String KEY_DATA = "data";
private static final String KEY_SUBSCRIPTION_ID = "subscription_id";
private byte[] data;
private int subscriptionId;
public MmsReceiveJob(byte[] data, int subscriptionId) {
this(new Job.Parameters.Builder().setMaxAttempts(25).build(), data, subscriptionId);
}
private MmsReceiveJob(@NonNull Job.Parameters parameters, byte[] data, int subscriptionId) {
super(parameters);
this.data = data;
this.subscriptionId = subscriptionId;
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder().putString(KEY_DATA, Base64.encodeBytes(data))
.putInt(KEY_SUBSCRIPTION_ID, subscriptionId)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() {
if (data == null) {
Log.w(TAG, "Received NULL pdu, ignoring...");
return;
}
PduParser parser = new PduParser(data);
GenericPdu pdu = null;
try {
pdu = parser.parse();
} catch (RuntimeException e) {
Log.w(TAG, e);
}
if (isNotification(pdu) && !isBlocked(pdu)) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu, subscriptionId);
Log.i(TAG, "Inserted received MMS notification...");
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MmsDownloadJob(messageAndThreadId.first,
messageAndThreadId.second,
true));
} else if (isNotification(pdu)) {
Log.w(TAG, "*** Received blocked MMS, ignoring...");
}
}
@Override
public void onCanceled() {
// TODO
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
return false;
}
private boolean isBlocked(GenericPdu pdu) {
if (pdu.getFrom() != null && pdu.getFrom().getTextString() != null) {
Recipient recipients = Recipient.from(context, Address.Companion.fromExternal(context, Util.toIsoString(pdu.getFrom().getTextString())), false);
return recipients.isBlocked();
}
return false;
}
private boolean isNotification(GenericPdu pdu) {
return pdu != null && pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
}
public static final class Factory implements Job.Factory<MmsReceiveJob> {
@Override
public @NonNull MmsReceiveJob create(@NonNull Parameters parameters, @NonNull Data data) {
try {
return new MmsReceiveJob(parameters, Base64.decode(data.getString(KEY_DATA)), data.getInt(KEY_SUBSCRIPTION_ID));
} catch (IOException e) {
throw new AssertionError(e);
}
}
}
}

@ -1,326 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import com.android.mms.dom.smil.parser.SmilXmlSerializer;
import com.google.android.mms.ContentType;
import com.google.android.mms.InvalidHeaderValueException;
import com.google.android.mms.pdu_alt.CharacterSets;
import com.google.android.mms.pdu_alt.EncodedStringValue;
import com.google.android.mms.pdu_alt.PduBody;
import com.google.android.mms.pdu_alt.PduComposer;
import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.android.mms.pdu_alt.PduPart;
import com.google.android.mms.pdu_alt.SendConf;
import com.google.android.mms.pdu_alt.SendReq;
import com.google.android.mms.smil.SmilHelper;
import com.klinker.android.send_message.Utils;
import org.session.libsession.messaging.jobs.Data;
import org.thoughtcrime.securesms.ApplicationContext;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.mms.CompatMmsConnection;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.session.libsignal.utilities.Hex;
import org.session.libsession.utilities.NumberUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class MmsSendJob extends SendJob {
public static final String KEY = "MmsSendJob";
private static final String TAG = MmsSendJob.class.getSimpleName();
private static final String KEY_MESSAGE_ID = "message_id";
private long messageId;
public MmsSendJob(long messageId) {
this(new Job.Parameters.Builder()
.setQueue("mms-operation")
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(25)
.build(),
messageId);
}
private MmsSendJob(@NonNull Job.Parameters parameters, long messageId) {
super(parameters);
this.messageId = messageId;
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId).build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onSend() throws MmsException, NoSuchMessageException, IOException {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
if (database.isSent(messageId)) {
Log.w(TAG, "Message " + messageId + " was already sent. Ignoring.");
return;
}
try {
Log.i(TAG, "Sending message: " + messageId);
SendReq pdu = constructSendPdu(message);
validateDestinations(message, pdu);
final byte[] pduBytes = getPduBytes(pdu);
final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, message.getSubscriptionId());
final MmsSendResult result = getSendResult(sendConf, pdu);
database.markAsSent(messageId, false);
markAttachmentsUploaded(messageId, message.getAttachments());
Log.i(TAG, "Sent message: " + messageId);
} catch (UndeliverableMessageException | IOException e) {
Log.w(TAG, e);
database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
} catch (InsecureFallbackApprovalException e) {
Log.w(TAG, e);
database.markAsPendingInsecureSmsFallback(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
return false;
}
@Override
public void onCanceled() {
Log.i(TAG, "onCanceled() messageId: " + messageId);
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
private byte[] getPduBytes(SendReq message)
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
{
byte[] pduBytes = new PduComposer(context, message).make();
if (pduBytes == null) {
throw new UndeliverableMessageException("PDU composition failed, null payload");
}
return pduBytes;
}
private MmsSendResult getSendResult(SendConf conf, SendReq message)
throws UndeliverableMessageException
{
if (conf == null) {
throw new UndeliverableMessageException("No M-Send.conf received in response to send.");
} else if (conf.getResponseStatus() != PduHeaders.RESPONSE_STATUS_OK) {
throw new UndeliverableMessageException("Got bad response: " + conf.getResponseStatus());
} else if (isInconsistentResponse(message, conf)) {
throw new UndeliverableMessageException("Mismatched response!");
} else {
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus());
}
}
private boolean isInconsistentResponse(SendReq message, SendConf response) {
Log.i(TAG, "Comparing: " + Hex.toString(message.getTransactionId()));
Log.i(TAG, "With: " + Hex.toString(response.getTransactionId()));
return !Arrays.equals(message.getTransactionId(), response.getTransactionId());
}
private void validateDestinations(EncodedStringValue[] destinations) throws UndeliverableMessageException {
if (destinations == null) return;
for (EncodedStringValue destination : destinations) {
if (destination == null || !NumberUtil.isValidSmsOrEmail(destination.getString())) {
throw new UndeliverableMessageException("Invalid destination: " +
(destination == null ? null : destination.getString()));
}
}
}
private void validateDestinations(OutgoingMediaMessage media, SendReq message) throws UndeliverableMessageException {
validateDestinations(message.getTo());
validateDestinations(message.getCc());
validateDestinations(message.getBcc());
if (message.getTo() == null && message.getCc() == null && message.getBcc() == null) {
throw new UndeliverableMessageException("No to, cc, or bcc specified!");
}
if (media.isSecure()) {
throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
}
}
private SendReq constructSendPdu(OutgoingMediaMessage message)
throws UndeliverableMessageException
{
SendReq req = new SendReq();
String lineNumber = getMyNumber(context);
Address destination = message.getRecipient().getAddress();
MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(message.getSubscriptionId());
List<Attachment> scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments());
if (!TextUtils.isEmpty(lineNumber)) {
req.setFrom(new EncodedStringValue(lineNumber));
} else {
req.setFrom(new EncodedStringValue(TextSecurePreferences.getLocalNumber(context)));
}
if (destination.isMmsGroup()) {
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(destination.toGroupString(), false);
for (Recipient member : members) {
if (message.getDistributionType() == ThreadDatabase.DistributionTypes.BROADCAST) {
req.addBcc(new EncodedStringValue(member.getAddress().serialize()));
} else {
req.addTo(new EncodedStringValue(member.getAddress().serialize()));
}
}
} else {
req.addTo(new EncodedStringValue(destination.serialize()));
}
req.setDate(System.currentTimeMillis() / 1000);
PduBody body = new PduBody();
int size = 0;
if (!TextUtils.isEmpty(message.getBody())) {
PduPart part = new PduPart();
String name = String.valueOf(System.currentTimeMillis());
part.setData(Util.toUtf8Bytes(message.getBody()));
part.setCharset(CharacterSets.UTF_8);
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
part.setContentId(name.getBytes());
part.setContentLocation((name + ".txt").getBytes());
part.setName((name + ".txt").getBytes());
body.addPart(part);
size += getPartSize(part);
}
for (Attachment attachment : scaledAttachments) {
try {
if (attachment.getDataUri() == null) throw new IOException("Assertion failed, attachment for outgoing MMS has no data!");
String fileName = attachment.getFileName();
PduPart part = new PduPart();
if (fileName == null) {
fileName = String.valueOf(Math.abs(Util.getSecureRandom().nextLong()));
String fileExtension = MimeTypeMap.getSingleton().getExtensionFromMimeType(attachment.getContentType());
if (fileExtension != null) fileName = fileName + "." + fileExtension;
}
if (attachment.getContentType().startsWith("text")) {
part.setCharset(CharacterSets.UTF_8);
}
part.setContentType(attachment.getContentType().getBytes());
part.setContentLocation(fileName.getBytes());
part.setName(fileName.getBytes());
int index = fileName.lastIndexOf(".");
String contentId = (index == -1) ? fileName : fileName.substring(0, index);
part.setContentId(contentId.getBytes());
part.setData(Util.readFully(PartAuthority.getAttachmentStream(context, attachment.getDataUri())));
body.addPart(part);
size += getPartSize(part);
} catch (IOException e) {
Log.w(TAG, e);
}
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
SmilXmlSerializer.serialize(SmilHelper.createSmilDocument(body), out);
PduPart smilPart = new PduPart();
smilPart.setContentId("smil".getBytes());
smilPart.setContentLocation("smil.xml".getBytes());
smilPart.setContentType(ContentType.APP_SMIL.getBytes());
smilPart.setData(out.toByteArray());
body.addPart(0, smilPart);
req.setBody(body);
req.setMessageSize(size);
req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
req.setExpiry(7 * 24 * 60 * 60);
try {
req.setPriority(PduHeaders.PRIORITY_NORMAL);
req.setDeliveryReport(PduHeaders.VALUE_NO);
req.setReadReport(PduHeaders.VALUE_NO);
} catch (InvalidHeaderValueException e) {}
return req;
}
private long getPartSize(PduPart part) {
return part.getName().length + part.getContentLocation().length +
part.getContentType().length + part.getData().length +
part.getContentId().length;
}
private void notifyMediaMessageDeliveryFailed(Context context, long messageId) {
long threadId = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId);
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
if (recipient != null) {
ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
}
}
private String getMyNumber(Context context) throws UndeliverableMessageException {
try {
return Utils.getMyPhoneNumber(context);
} catch (SecurityException e) {
throw new UndeliverableMessageException(e);
}
}
public static class Factory implements Job.Factory<MmsSendJob> {
@Override
public @NonNull MmsSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new MmsSendJob(parameters, data.getLong(KEY_MESSAGE_ID));
}
}
}

@ -15,30 +15,17 @@ import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.utilities.MediaTypes;
import org.session.libsignal.metadata.InvalidMetadataMessageException;
import org.session.libsignal.metadata.InvalidMetadataVersionException;
import org.session.libsignal.metadata.ProtocolDuplicateMessageException;
import org.session.libsignal.metadata.ProtocolInvalidKeyException;
import org.session.libsignal.metadata.ProtocolInvalidKeyIdException;
import org.session.libsignal.metadata.ProtocolInvalidMessageException;
import org.session.libsignal.metadata.ProtocolInvalidVersionException;
import org.session.libsignal.metadata.ProtocolLegacyMessageException;
import org.session.libsignal.metadata.ProtocolNoSessionException;
import org.session.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.session.libsignal.metadata.SelfSendException;
import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
import org.session.libsignal.utilities.PromiseUtilities;
import org.session.libsignal.service.api.crypto.SignalServiceCipher;
import org.thoughtcrime.securesms.ApplicationContext;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment;
import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.sending_receiving.attachments.StickerLocator;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
@ -47,28 +34,20 @@ import org.session.libsession.utilities.TextSecurePreferences;
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.linkpreview.Link;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.session.libsignal.utilities.logging.Log;
@ -76,31 +55,17 @@ import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl;
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2;
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.session.libsignal.utilities.Hex;
import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.loki.SessionResetProtocol;
import org.session.libsignal.libsignal.state.SignalProtocolStore;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.messages.SignalServiceContent;
@ -110,22 +75,14 @@ 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.SentTranscriptMessage;
import org.session.libsignal.service.api.messages.multidevice.StickerPackOperationMessage;
import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
import org.session.libsignal.service.loki.crypto.LokiServiceCipher;
import org.session.libsignal.service.loki.protocol.mentions.MentionsManager;
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
import org.session.libsignal.service.loki.utilities.PublicKeyValidation;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
@ -246,11 +203,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional<Long> smsMessageId, boolean isPushNotification) {
try {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(context);
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, DatabaseFactory.getSSKDatabase(context), new SessionProtocolImpl(context), sessionResetProtocol, apiDB, UnidentifiedAccessUtil.getCertificateValidator());
SignalServiceCipher cipher = new SignalServiceCipher(new SessionProtocolImpl(context), apiDB);
SignalServiceContent content = cipher.decrypt(envelope);
@ -259,64 +213,36 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return;
}
SessionManagementProtocol.handlePreKeyBundleMessageIfNeeded(context, content);
SessionMetaProtocol.handleProfileUpdateIfNeeded(context, content);
if (content.configurationMessageProto.isPresent()) {
MultiDeviceProtocol.handleConfigurationMessage(context, content.configurationMessageProto.get(), content.getSender(), content.getTimestamp());
} else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
if (message.isDeviceUnlinkingRequest()) {
throw new UnsupportedOperationException("Device link operations are not supported!");
} else {
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent();
if (message.getClosedGroupUpdateV2().isPresent()) {
ClosedGroupsProtocolV2.handleMessage(context, message.getClosedGroupUpdateV2().get(), message.getTimestamp(), envelope.getSource(), content.getSender());
}
if (message.isEndSession()) {
handleEndSessionMessage(content, smsMessageId);
} else if (message.isGroupUpdate()) {
handleGroupMessage(content, message, smsMessageId);
} else if (message.isExpirationUpdate()) {
handleExpirationUpdate(content, message, smsMessageId);
} else if (isMediaMessage) {
handleMediaMessage(content, message, smsMessageId, Optional.absent());
} else if (message.getBody().isPresent()) {
handleTextMessage(content, message, smsMessageId, Optional.absent());
}
if (message.getClosedGroupControlMessage().isPresent()) {
ClosedGroupsProtocolV2.handleMessage(context, message.getClosedGroupControlMessage().get(), message.getTimestamp(), envelope.getSource(), content.getSender());
}
if (message.isExpirationUpdate()) {
handleExpirationUpdate(content, message, smsMessageId);
} else if (isMediaMessage) {
handleMediaMessage(content, message, smsMessageId, Optional.absent());
} else if (message.getBody().isPresent()) {
handleTextMessage(content, message, smsMessageId, Optional.absent());
}
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
handleUnknownGroupMessage(content, message.getGroupInfo().get());
}
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get()))) {
handleUnknownGroupMessage(content, message.getGroupInfo().get());
}
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
SessionMetaProtocol.handleProfileKeyUpdate(context, content);
}
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
SessionMetaProtocol.handleProfileKeyUpdate(context, content);
}
if (SessionMetaProtocol.shouldSendDeliveryReceipt(message, Address.Companion.fromSerialized(content.getSender()))) {
handleNeedsDeliveryReceipt(content, message);
}
if (SessionMetaProtocol.shouldSendDeliveryReceipt(message, Address.fromSerialized(content.getSender()))) {
handleNeedsDeliveryReceipt(content, message);
}
} else if (content.getSyncMessage().isPresent()) {
throw new UnsupportedOperationException("Device link operations are not supported!");
// TextSecurePreferences.setMultiDevice(context, true);
//
// SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
//
// if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(content, syncMessage.getSent().get());
// else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(syncMessage.getRequest().get());
// else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(syncMessage.getRead().get(), content.getTimestamp());
// else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(syncMessage.getVerified().get());
// else if (syncMessage.getStickerPackOperations().isPresent()) handleSynchronizeStickerPackOperation(syncMessage.getStickerPackOperations().get());
// else if (syncMessage.getContacts().isPresent()) SyncMessagesProtocol.handleContactSyncMessage(context, content, syncMessage.getContacts().get());
// else if (syncMessage.getGroups().isPresent()) SyncMessagesProtocol.handleClosedGroupSyncMessage(context, content, syncMessage.getGroups().get());
// else if (syncMessage.getOpenGroups().isPresent()) SyncMessagesProtocol.handleOpenGroupSyncMessage(context, content, syncMessage.getOpenGroups().get());
// else if (syncMessage.getBlockedList().isPresent()) SyncMessagesProtocol.handleBlockedContactsSyncMessage(context, content, syncMessage.getBlockedList().get());
// else Log.w(TAG, "Contains no known sync types...");
} else if (content.getReceiptMessage().isPresent()) {
SignalServiceReceiptMessage message = content.getReceiptMessage().get();
@ -328,87 +254,17 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
Log.w(TAG, "Got unrecognized message...");
}
resetRecipientToPush(Recipient.from(context, Address.Companion.fromSerialized(content.getSender()), false));
// if (envelope.isPreKeySignalMessage()) {
// ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob());
// }
} catch (ProtocolInvalidVersionException e) {
Log.w(TAG, e);
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
resetRecipientToPush(Recipient.from(context, Address.fromSerialized(content.getSender()), false));
} catch (ProtocolInvalidMessageException e) {
Log.w(TAG, e);
if (!isPushNotification) { // This can be triggered if a PN encrypted with an old session comes in after the user performed a session reset
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e);
}
} catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
Log.w(TAG, e);
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e);
} catch (StorageFailedException e) {
}catch (StorageFailedException e) {
Log.w(TAG, e);
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e);
} catch (ProtocolNoSessionException e) {
Log.w(TAG, e);
handleNoSessionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (ProtocolLegacyMessageException e) {
Log.w(TAG, e);
handleLegacyMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (ProtocolDuplicateMessageException e) {
} catch (InvalidMetadataMessageException e) {
Log.w(TAG, e);
handleDuplicateMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (InvalidMetadataVersionException | InvalidMetadataMessageException e) {
Log.w(TAG, e);
} catch (SelfSendException e) {
Log.i(TAG, "Dropping UD message from self.");
} catch (IOException e) {
Log.i(TAG, "IOException during message decryption.");
} catch (SessionProtocol.Exception e) {
Log.i(TAG, "Couldn't handle message due to error: " + e.getDescription());
}
}
private void handleEndSessionMessage(@NonNull SignalServiceContent content,
@NonNull Optional<Long> smsMessageId)
{
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(Address.Companion.fromSerialized(content.getSender()),
content.getSenderDevice(),
content.getTimestamp(),
"", Optional.absent(), 0,
content.isNeedsReceipt());
Long threadId;
if (!smsMessageId.isPresent()) {
IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(incomingEndSessionMessage);
if (insertResult.isPresent()) threadId = insertResult.get().getThreadId();
else threadId = null;
} else {
smsDatabase.markAsEndSession(smsMessageId.get());
threadId = smsDatabase.getThreadIdForMessage(smsMessageId.get());
}
if (threadId != null) {
SessionManagementProtocol.handleEndSessionMessageIfNeeded(context, content);
messageNotifier.updateNotification(context, threadId);
}
}
private void handleGroupMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId)
throws StorageFailedException
{
GroupMessageProcessor.process(context, content, message, false);
if (message.getExpiresInSeconds() != 0 && message.getExpiresInSeconds() != getMessageDestination(content, message).getExpireMessages()) {
handleExpirationUpdate(content, message, Optional.absent());
}
if (smsMessageId.isPresent()) {
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
}
}
@ -439,7 +295,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
Optional.absent(),
Optional.absent(),
Optional.absent(),
Optional.absent(),
Optional.absent());
database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
@ -470,7 +325,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts());
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
Optional<Attachment> sticker = getStickerAttachment(message.getSticker());
Address masterAddress = masterRecipient.getAddress();
@ -482,7 +336,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (syncTarget != null && !syncTarget.isEmpty() || TextSecurePreferences.getLocalNumber(context).equals(content.getSender())) {
Address targetAddress = masterRecipient.getAddress();
if (message.getGroupInfo().isPresent()) {
targetAddress = Address.Companion.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get()));
targetAddress = Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get()));
} else if (syncTarget != null && !syncTarget.isEmpty()) {
targetAddress = Address.fromSerialized(syncTarget);
}
@ -521,10 +375,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (insertResult.isPresent()) {
List<DatabaseAttachment> allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId());
List<DatabaseAttachment> stickerAttachments = Stream.of(allAttachments).filter(Attachment::isSticker).toList();
List<DatabaseAttachment> dbAttachments = Stream.of(allAttachments).filterNot(Attachment::isSticker).toList();
forceStickerDownloadIfNecessary(stickerAttachments);
List<DatabaseAttachment> dbAttachments = Stream.of(allAttachments).toList();
for (DatabaseAttachment attachment : dbAttachments) {
ApplicationContext.getInstance(context).getJobManager().add(new AttachmentDownloadJob(insertResult.get().getMessageId(), attachment.getAttachmentId(), false));
@ -545,7 +396,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else {
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
quote, sharedContacts, linkPreviews, sticker);
quote, sharedContacts, linkPreviews);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
database.beginTransaction();
@ -565,10 +416,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (insertResult.isPresent()) {
List<DatabaseAttachment> allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(insertResult.get().getMessageId());
List<DatabaseAttachment> stickerAttachments = Stream.of(allAttachments).filter(Attachment::isSticker).toList();
List<DatabaseAttachment> attachments = Stream.of(allAttachments).filterNot(Attachment::isSticker).toList();
forceStickerDownloadIfNecessary(stickerAttachments);
List<DatabaseAttachment> attachments = Stream.of(allAttachments).toList();
for (DatabaseAttachment attachment : attachments) {
ApplicationContext.getInstance(context).getJobManager().add(new AttachmentDownloadJob(insertResult.get().getMessageId(), attachment.getAttachmentId(), false));
@ -616,110 +464,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getSyncMessageMasterDestination(message);
OutgoingExpirationUpdateMessage expirationUpdateMessage = new OutgoingExpirationUpdateMessage(recipient,
message.getTimestamp(),
message.getMessage().getExpiresInSeconds() * 1000L);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
long messageId = database.insertMessageOutbox(expirationUpdateMessage, threadId, false, null);
database.markAsSent(messageId, true);
DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, message.getMessage().getExpiresInSeconds());
return threadId;
}
public long handleSynchronizeSentMediaMessage(@NonNull SentTranscriptMessage message)
throws MmsException
{
if (SessionMetaProtocol.shouldIgnoreMessage(message.getTimestamp())) { return -1; }
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipients = getSyncMessageMasterDestination(message);
Optional<QuoteModel> quote = getValidatedQuote(message.getMessage().getQuote());
Optional<Attachment> sticker = getStickerAttachment(message.getMessage().getSticker());
Optional<List<Contact>> sharedContacts = getContacts(message.getMessage().getSharedContacts());
Optional<List<LinkPreview>> previews = getLinkPreviews(message.getMessage().getPreviews(), message.getMessage().getBody().or(""));
List<Attachment> syncAttachments = PointerAttachment.forPointers(message.getMessage().getAttachments());
if (sticker.isPresent()) {
syncAttachments.add(sticker.get());
}
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipients, message.getMessage().getBody().orNull(),
syncAttachments,
message.getTimestamp(), -1,
message.getMessage().getExpiresInSeconds() * 1000,
ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(),
sharedContacts.or(Collections.emptyList()),
previews.or(Collections.emptyList()),
Collections.emptyList(), Collections.emptyList());
mediaMessage = new OutgoingSecureMediaMessage(mediaMessage);
if (recipients.getExpireMessages() != message.getMessage().getExpiresInSeconds()) {
handleSynchronizeSentExpirationUpdate(message);
}
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipients);
database.beginTransaction();
try {
long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, null);
if (message.messageServerID >= 0) { DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageId, message.messageServerID); }
if (recipients.getAddress().isGroup()) {
GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipients.getAddress().toGroupString(), false);
for (Recipient member : members) {
receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize()));
}
}
database.markAsSent(messageId, true);
database.markUnidentified(messageId, message.isUnidentified(recipients.getAddress().serialize()));
List<DatabaseAttachment> allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageId);
List<DatabaseAttachment> stickerAttachments = Stream.of(allAttachments).filter(Attachment::isSticker).toList();
List<DatabaseAttachment> attachments = Stream.of(allAttachments).filterNot(Attachment::isSticker).toList();
forceStickerDownloadIfNecessary(stickerAttachments);
for (DatabaseAttachment attachment : attachments) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(messageId, attachment.getAttachmentId(), false));
}
if (message.getMessage().getExpiresInSeconds() > 0) {
database.markExpireStarted(messageId, message.getExpirationStartTimestamp());
ApplicationContext.getInstance(context)
.getExpiringMessageManager()
.scheduleDeletion(messageId, true,
message.getExpirationStartTimestamp(),
message.getMessage().getExpiresInSeconds() * 1000L);
}
if (recipients.isLocalNumber()) {
SyncMessageId id = new SyncMessageId(recipients.getAddress(), message.getTimestamp());
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis());
}
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
return threadId;
}
public void handleTextMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId,
@ -743,7 +487,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else if (syncTarget != null && !syncTarget.isEmpty() || TextSecurePreferences.getLocalNumber(context).equals(content.getSender())) {
Address targetAddress = masterRecipient.getAddress();
if (message.getGroupInfo().isPresent()) {
targetAddress = Address.Companion.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get()));
targetAddress = Address.fromSerialized(GroupUtil.getEncodedId(message.getGroupInfo().get()));
} else if (syncTarget != null && !syncTarget.isEmpty()) {
targetAddress = Address.fromSerialized(syncTarget);
}
@ -846,86 +590,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
public long handleSynchronizeSentTextMessage(@NonNull SentTranscriptMessage message)
throws MmsException
{
if (SessionMetaProtocol.shouldIgnoreMessage(message.getTimestamp())) { return -1; }
Recipient recipient = getSyncMessageMasterDestination(message);
String body = message.getMessage().getBody().or("");
long expiresInMillis = message.getMessage().getExpiresInSeconds() * 1000L;
// Ignore the message if it has no body
if (body.isEmpty()) { return -1; }
if (recipient.getExpireMessages() != message.getMessage().getExpiresInSeconds()) {
handleSynchronizeSentExpirationUpdate(message);
}
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
boolean isGroup = recipient.getAddress().isGroup();
MessagingDatabase database;
long messageId;
if (isGroup) {
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getMessage().getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), Collections.emptyList());
outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage);
messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null,message.getTimestamp());
if (message.messageServerID >= 0) { DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageId, message.messageServerID); }
database = DatabaseFactory.getMmsDatabase(context);
GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.getAddress().toGroupString(), false);
for (Recipient member : members) {
receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize()));
}
} else {
OutgoingTextMessage outgoingTextMessage = new OutgoingEncryptedMessage(recipient, body, expiresInMillis);
messageId = DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoingTextMessage, false, message.getTimestamp(), null);
database = DatabaseFactory.getSmsDatabase(context);
database.markUnidentified(messageId, message.isUnidentified(recipient.getAddress().serialize()));
}
database.markAsSent(messageId, true);
if (expiresInMillis > 0) {
database.markExpireStarted(messageId, message.getExpirationStartTimestamp());
ApplicationContext.getInstance(context)
.getExpiringMessageManager()
.scheduleDeletion(messageId, isGroup, message.getExpirationStartTimestamp(), expiresInMillis);
}
if (recipient.isLocalNumber()) {
SyncMessageId id = new SyncMessageId(recipient.getAddress(), message.getTimestamp());
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis());
}
return threadId;
}
private void handleInvalidVersionMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId)
{
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) {
smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId());
messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else {
smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get());
}
}
private void handleCorruptMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId, @NonNull Throwable e)
{
@ -942,85 +606,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
smsDatabase.markAsDecryptFailed(smsMessageId.get());
}
}
if (canRecoverAutomatically(e)) {
Recipient recipient = Recipient.from(context, Address.Companion.fromSerialized(sender), false);
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(context);
long threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
threadDB.addSessionRestoreDevice(threadID, sender);
SessionManagementProtocol.startSessionReset(context, sender);
} else {
SessionManagementProtocol.triggerSessionRestorationUI(context, sender, timestamp);
}
}
private boolean canRecoverAutomatically(Throwable e) {
// Corrupt message exception
if (e.getCause() != null) {
Throwable e2 = e.getCause();
if (e2.getCause() != null) {
Throwable e3 = e2.getCause();
if (e3 instanceof InvalidMessageException) {
String message = e3.getMessage();
return (message != null && message.startsWith("Bad Mac!"));
}
}
}
return false;
}
private void handleNoSessionMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId)
{
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!SessionMetaProtocol.shouldIgnoreDecryptionException(context, timestamp)) {
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) {
smsDatabase.markAsNoSession(insertResult.get().getMessageId());
messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else {
smsDatabase.markAsNoSession(smsMessageId.get());
}
}
// Attempt to recover automatically
ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(sender);
}
private void handleLegacyMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId)
{
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) {
smsDatabase.markAsLegacyVersion(insertResult.get().getMessageId());
messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else {
smsDatabase.markAsLegacyVersion(smsMessageId.get());
}
}
@SuppressWarnings("unused")
private void handleDuplicateMessage(@NonNull String sender, int senderDeviceId, long timestamp,
@NonNull Optional<Long> smsMessageId)
{
// Let's start ignoring these now
// SmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
//
// if (smsMessageId <= 0) {
// Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
// smsDatabase.markAsDecryptDuplicate(messageAndThreadId.first);
// MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
// } else {
// smsDatabase.markAsDecryptDuplicate(smsMessageId);
// }
}
private void handleNeedsDeliveryReceipt(@NonNull SignalServiceContent content,
@ -1028,7 +613,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
{
ApplicationContext.getInstance(context)
.getJobManager()
.add(new SendDeliveryReceiptJob(Address.Companion.fromSerialized(content.getSender()), message.getTimestamp()));
.add(new SendDeliveryReceiptJob(Address.fromSerialized(content.getSender()), message.getTimestamp()));
}
@SuppressLint("DefaultLocale")
@ -1036,7 +621,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
@NonNull SignalServiceReceiptMessage message)
{
// Redirect message to master device conversation
Address masterAddress = Address.Companion.fromSerialized(content.getSender());
Address masterAddress = Address.fromSerialized(content.getSender());
if (masterAddress.isContact()) {
Recipient masterRecipient = getMessageMasterDestination(content.getSender());
@ -1057,7 +642,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (TextSecurePreferences.isReadReceiptsEnabled(context)) {
// Redirect message to master device conversation
Address masterAddress = Address.Companion.fromSerialized(content.getSender());
Address masterAddress = Address.fromSerialized(content.getSender());
if (masterAddress.isContact()) {
Recipient masterRecipient = getMessageMasterDestination(content.getSender());
@ -1079,22 +664,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (!TextSecurePreferences.isTypingIndicatorsEnabled(context)) {
return;
}
Recipient author = Recipient.from(context, Address.Companion.fromSerialized(content.getSender()), false);
long threadId;
if (typingMessage.getGroupId().isPresent()) {
// Typing messages should only apply to closed groups, thus we use `getEncodedId`
Address groupAddress = Address.Companion.fromSerialized(GroupUtil.getEncodedClosedGroupID(typingMessage.getGroupId().get()));
Recipient groupRecipient = Recipient.from(context, groupAddress, false);
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient);
} else {
// See if we need to redirect the message
author = getMessageMasterDestination(content.getSender());
threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(author);
}
Recipient author = getMessageMasterDestination(content.getSender());
threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(author);
if (threadId <= 0) {
Log.w(TAG, "Couldn't find a matching thread for a typing message.");
@ -1123,7 +696,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return Optional.absent();
}
Address author = Address.Companion.fromSerialized(quote.get().getAuthor().getNumber());
Address author = Address.fromSerialized(quote.get().getAuthor().getNumber());
MessageRecord message = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(quote.get().getId(), author);
if (message != null) {
@ -1153,42 +726,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
PointerAttachment.forPointersOfDataMessage(quote.get().getAttachments())));
}
private Optional<Attachment> getStickerAttachment(Optional<SignalServiceDataMessage.Sticker> sticker) {
if (!sticker.isPresent()) {
return Optional.absent();
}
if (sticker.get().getPackId() == null || sticker.get().getPackKey() == null || sticker.get().getAttachment() == null) {
Log.w(TAG, "Malformed sticker!");
return Optional.absent();
}
String packId = Hex.toStringCondensed(sticker.get().getPackId());
String packKey = Hex.toStringCondensed(sticker.get().getPackKey());
int stickerId = sticker.get().getStickerId();
StickerLocator stickerLocator = new StickerLocator(packId, packKey, stickerId);
StickerDatabase stickerDatabase = DatabaseFactory.getStickerDatabase(context);
StickerRecord stickerRecord = stickerDatabase.getSticker(stickerLocator.getPackId(), stickerLocator.getStickerId(), false);
if (stickerRecord != null) {
return Optional.of(new UriAttachment(stickerRecord.getUri(),
stickerRecord.getUri(),
MediaTypes.IMAGE_WEBP,
AttachmentDatabase.TRANSFER_PROGRESS_DONE,
stickerRecord.getSize(),
StickerSlide.WIDTH,
StickerSlide.HEIGHT,
null,
String.valueOf(new SecureRandom().nextLong()),
false,
false,
null,
stickerLocator));
} else {
return Optional.of(PointerAttachment.forPointer(Optional.of(sticker.get().getAttachment()), stickerLocator).get());
}
}
private Optional<List<Contact>> getContacts(Optional<List<SharedContact>> sharedContacts) {
if (!sharedContacts.isPresent()) return Optional.absent();
@ -1236,73 +773,29 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return database.insertMessageInbox(textMessage);
}
private Recipient getSyncMessageDestination(SentTranscriptMessage message) {
if (message.getMessage().isGroupMessage()) {
return Recipient.from(context, Address.Companion.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get())), false);
} else {
return Recipient.from(context, Address.Companion.fromSerialized(message.getDestination().get()), false);
}
}
private Recipient getSyncMessageMasterDestination(SentTranscriptMessage message) {
if (message.getMessage().isGroupMessage()) {
return Recipient.from(context, Address.Companion.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get())), false);
} else {
String publicKey = message.getDestination().get();
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
Set<String> allUserDevices = org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
if (allUserDevices.contains(publicKey)) {
return Recipient.from(context, Address.Companion.fromSerialized(userPublicKey), false);
} else {
try {
// TODO: Burn this with fire when we can
PromiseUtilities.timeout(FileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
String masterPublicKey = org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
if (masterPublicKey == null) {
masterPublicKey = publicKey;
}
return Recipient.from(context, Address.Companion.fromSerialized(masterPublicKey), false);
} catch (Exception e) {
return Recipient.from(context, Address.Companion.fromSerialized(publicKey), false);
}
}
}
}
private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) {
if (message.getGroupInfo().isPresent()) {
return Recipient.from(context, Address.Companion.fromExternal(context, GroupUtil.getEncodedClosedGroupID(message.getGroupInfo().get().getGroupId())), false);
return Recipient.from(context, Address.fromExternal(context, GroupUtil.getEncodedClosedGroupID(message.getGroupInfo().get().getGroupId())), false);
} else {
return Recipient.from(context, Address.Companion.fromExternal(context, content.getSender()), false);
return Recipient.from(context, Address.fromExternal(context, content.getSender()), false);
}
}
private Recipient getMessageMasterDestination(String publicKey) {
if (!PublicKeyValidation.isValid(publicKey)) {
return Recipient.from(context, Address.Companion.fromSerialized(publicKey), false);
return Recipient.from(context, Address.fromSerialized(publicKey), false);
} else {
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
Set<String> allUserDevices = org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
if (allUserDevices.contains(publicKey)) {
return Recipient.from(context, Address.Companion.fromSerialized(userPublicKey), false);
if (publicKey.equals(userPublicKey)) {
return Recipient.from(context, Address.fromSerialized(userPublicKey), false);
} else {
try {
// TODO: Burn this with fire when we can
PromiseUtilities.timeout(FileServerAPI.shared.getDeviceLinks(publicKey, false), 6000).get();
String masterPublicKey = org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol.shared.getMasterDevice(publicKey);
if (masterPublicKey == null) {
masterPublicKey = publicKey;
}
return Recipient.from(context, Address.Companion.fromSerialized(masterPublicKey), false);
} catch (Exception e) {
return Recipient.from(context, Address.Companion.fromSerialized(publicKey), false);
}
return Recipient.from(context, Address.fromSerialized(publicKey), false);
}
}
}
private void notifyTypingStoppedFromIncomingMessage(@NonNull Recipient conversationRecipient, @NonNull String sender, int device) {
Recipient author = Recipient.from(context, Address.Companion.fromSerialized(sender), false);
Recipient author = Recipient.from(context, Address.fromSerialized(sender), false);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(conversationRecipient);
if (threadId > 0) {
@ -1328,11 +821,9 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return true;
}
Recipient sender = Recipient.from(context, Address.Companion.fromSerialized(content.getSender()), false);
Recipient sender = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
if (content.getDeviceLink().isPresent()) {
return false;
} else if (content.getDataMessage().isPresent()) {
if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
Recipient conversation = getMessageDestination(content, message);
@ -1354,47 +845,21 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get());
boolean isLeaveMessage = message.getGroupInfo().isPresent() && message.getGroupInfo().get().getType() == SignalServiceGroup.Type.QUIT;
boolean shouldIgnoreContentMessage = ClosedGroupsProtocol.shouldIgnoreContentMessage(context, conversation.getAddress(), groupId.orNull(), content.getSender());
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage) || (isContentMessage && shouldIgnoreContentMessage);
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage);
} else {
return sender.isBlocked();
}
} else if (content.getSyncMessage().isPresent()) {
throw new UnsupportedOperationException("Device link operations are not supported!");
}
return false;
}
private boolean isGroupChatMessage(SignalServiceContent content) {
return content.getDataMessage().isPresent() && content.getDataMessage().get().isGroupMessage();
}
private void resetRecipientToPush(@NonNull Recipient recipient) {
if (recipient.isForceSmsSelection()) {
DatabaseFactory.getRecipientDatabase(context).setForceSmsSelection(recipient, false);
}
}
private void forceStickerDownloadIfNecessary(List<DatabaseAttachment> stickerAttachments) {
if (stickerAttachments.isEmpty()) return;
DatabaseAttachment stickerAttachment = stickerAttachments.get(0);
if (stickerAttachment.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE) {
AttachmentDownloadJob downloadJob = new AttachmentDownloadJob(messageId, stickerAttachment.getAttachmentId(), true);
try {
ApplicationContext.getInstance(context).injectDependencies(downloadJob);
downloadJob.setContext(context);
downloadJob.doWork();
} catch (Exception e) {
Log.w(TAG, "Failed to download sticker inline. Scheduling.");
ApplicationContext.getInstance(context).getJobManager().add(downloadJob);
}
}
}
@SuppressWarnings("WeakerAccess")
private static class StorageFailedException extends Exception {
private final String sender;

@ -15,7 +15,7 @@ import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -28,16 +28,13 @@ import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.crypto.UnidentifiedAccessPair;
import org.session.libsignal.service.api.crypto.UntrustedIdentityException;
import org.session.libsignal.service.api.messages.SendMessageResult;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
@ -137,16 +134,13 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
@Override
public void onPushSend()
throws IOException, MmsException, NoSuchMessageException, RetryLaterException
throws MmsException, NoSuchMessageException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(messageId);
List<NetworkFailure> existingNetworkFailures = message.getNetworkFailures();
List<IdentityKeyMismatch> existingIdentityMismatches = message.getIdentityKeyMismatches();
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
SignalServiceAddress localAddress = new SignalServiceAddress(userPublicKey);
if (database.isSent(messageId)) {
log(TAG, "Message " + messageId + " was already sent. Ignoring.");
return;
@ -157,14 +151,14 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
List<Address> targets;
if (filterAddress != null) targets = Collections.singletonList(Address.Companion.fromSerialized(filterAddress));
if (filterAddress != null) targets = Collections.singletonList(Address.fromSerialized(filterAddress));
else if (!existingNetworkFailures.isEmpty()) targets = Stream.of(existingNetworkFailures).map(NetworkFailure::getAddress).toList();
else targets = ClosedGroupsProtocol.getMessageDestinations(context, message.getRecipient().getAddress().toGroupString());
else targets = ClosedGroupsProtocolV2.getMessageDestinations(context, message.getRecipient().getAddress().toGroupString());
List<SendMessageResult> results = deliver(message, targets);
List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Address.Companion.fromSerialized(result.getAddress().getNumber()))).toList();
List<IdentityKeyMismatch> identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Address.Companion.fromSerialized(result.getAddress().getNumber()), result.getIdentityFailure().getIdentityKey())).toList();
Set<Address> successAddresses = Stream.of(results).filter(result -> result.getSuccess() != null).map(result -> Address.Companion.fromSerialized(result.getAddress().getNumber())).collect(Collectors.toSet());
List<NetworkFailure> networkFailures = Stream.of(results).filter(SendMessageResult::isNetworkFailure).map(result -> new NetworkFailure(Address.fromSerialized(result.getAddress().getNumber()))).toList();
List<IdentityKeyMismatch> identityMismatches = Stream.of(results).filter(result -> result.getIdentityFailure() != null).map(result -> new IdentityKeyMismatch(Address.fromSerialized(result.getAddress().getNumber()), result.getIdentityFailure().getIdentityKey())).toList();
Set<Address> successAddresses = Stream.of(results).filter(result -> result.getSuccess() != null).map(result -> Address.fromSerialized(result.getAddress().getNumber())).collect(Collectors.toSet());
List<NetworkFailure> resolvedNetworkFailures = Stream.of(existingNetworkFailures).filter(failure -> successAddresses.contains(failure.getAddress())).toList();
List<IdentityKeyMismatch> resolvedIdentityFailures = Stream.of(existingIdentityMismatches).filter(failure -> successAddresses.contains(failure.getAddress())).toList();
List<SendMessageResult> successes = Stream.of(results).filter(result -> result.getSuccess() != null).toList();
@ -188,7 +182,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
}
for (SendMessageResult success : successes) {
DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Address.Companion.fromSerialized(success.getAddress().getNumber()),
DatabaseFactory.getGroupReceiptDatabase(context).setUnidentified(Address.fromSerialized(success.getAddress().getNumber()),
messageId,
success.getSuccess().isUnidentified());
}
@ -230,21 +224,13 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
}
private List<SendMessageResult> deliver(OutgoingMediaMessage message, @NonNull List<Address> destinations)
throws IOException, UntrustedIdentityException, UndeliverableMessageException {
throws IOException {
// Loki - The user shouldn't be able to message RSS feeds
Address address = message.getRecipient().getAddress();
// if (address.isRSSFeed()) {
// List<SendMessageResult> results = new ArrayList<>();
// for (Address destination : destinations) {
// results.add(SendMessageResult.networkFailure(new SignalServiceAddress(destination.toPhoneString())));
// }
// return results;
// }
List<SignalServiceAddress> addresses = Stream.of(destinations).map(this::getPushAddress).toList();
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess = Stream.of(addresses)
.map(a -> Address.Companion.fromSerialized(a.getNumber()))
List<Optional<UnidentifiedAccess>> unidentifiedAccess = Stream.of(addresses)
.map(a -> Address.fromSerialized(a.getNumber()))
.map(a -> Recipient.from(context, a, false))
.map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient))
.toList();
@ -252,7 +238,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
if (message.isGroup() && address.isClosedGroup()) {
SignalServiceGroup.GroupType groupType = address.isOpenGroup() ? SignalServiceGroup.GroupType.PUBLIC_CHAT : SignalServiceGroup.GroupType.SIGNAL;
String groupId = address.toGroupString();
List<Attachment> attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList();
List<Attachment> attachments = Stream.of(message.getAttachments()).toList();
List<SignalServiceAttachment> attachmentPointers = getAttachmentPointersFor(attachments);
// Loki - Only send GroupUpdate or GroupQuit messages to closed groups
OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message;
@ -274,17 +260,16 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
}
}
public SignalServiceDataMessage.Builder getDataMessage(Address address, OutgoingMediaMessage message) throws IOException {
public SignalServiceDataMessage.Builder getDataMessage(Address address, OutgoingMediaMessage message) {
SignalServiceGroup.GroupType groupType = address.isOpenGroup() ? SignalServiceGroup.GroupType.PUBLIC_CHAT : SignalServiceGroup.GroupType.SIGNAL;
String groupId = address.toGroupString();
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
Optional<Quote> quote = getQuoteFor(message);
Optional<SignalServiceDataMessage.Sticker> sticker = getStickerFor(message);
List<SharedContact> sharedContacts = getSharedContactsFor(message);
List<Preview> previews = getPreviewsFor(message);
List<Attachment> attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList();
List<Attachment> attachments = Stream.of(message.getAttachments()).toList();
List<SignalServiceAttachment> attachmentPointers = getAttachmentPointersFor(attachments);
SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(groupId), groupType);
@ -297,7 +282,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
.asExpirationUpdate(message.isExpirationUpdate())
.withProfileKey(profileKey.orNull())
.withQuote(quote.orNull())
.withSticker(sticker.orNull())
.withSharedContacts(sharedContacts)
.withPreviews(previews);
}
@ -306,7 +290,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
@Override
public @NonNull PushGroupSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
String address = data.getString(KEY_FILTER_ADDRESS);
Address filter = address != null ? Address.Companion.fromSerialized(data.getString(KEY_FILTER_ADDRESS)) : null;
Address filter = address != null ? Address.fromSerialized(data.getString(KEY_FILTER_ADDRESS)) : null;
return new PushGroupSendJob(parameters, data.getLong(KEY_MESSAGE_ID), filter);
}

@ -1,152 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.GroupRecord;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.GroupUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.crypto.UntrustedIdentityException;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.SignalServiceGroup.Type;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class PushGroupUpdateJob extends BaseJob implements InjectableType {
public static final String KEY = "PushGroupUpdateJob";
private static final String TAG = PushGroupUpdateJob.class.getSimpleName();
private static final String KEY_SOURCE = "source";
private static final String KEY_GROUP_ID = "group_id";
@Inject SignalServiceMessageSender messageSender;
private String source;
private byte[] groupId;
public PushGroupUpdateJob(String source, byte[] groupId) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
source,
groupId);
}
private PushGroupUpdateJob(@NonNull Job.Parameters parameters, String source, byte[] groupId) {
super(parameters);
this.source = source;
this.groupId = groupId;
}
@Override
public @NonNull
Data serialize() {
return new Data.Builder().putString(KEY_SOURCE, source)
.putString(KEY_GROUP_ID, GroupUtil.getEncodedClosedGroupID(groupId))
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException, UntrustedIdentityException {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
Optional<GroupRecord> record = groupDatabase.getGroup(GroupUtil.getEncodedClosedGroupID(groupId));
SignalServiceAttachment avatar = null;
if (record == null) {
Log.w(TAG, "No information for group record info request: " + new String(groupId));
return;
}
if (record.get().getAvatar() != null) {
avatar = SignalServiceAttachmentStream.newStreamBuilder()
.withContentType("image/jpeg")
.withStream(new ByteArrayInputStream(record.get().getAvatar()))
.withLength(record.get().getAvatar().length)
.build();
}
List<String> members = new LinkedList<>();
for (Address member : record.get().getMembers()) {
members.add(member.serialize());
}
List<String> admins = new LinkedList<>();
for (Address admin : record.get().getAdmins()) {
admins.add(admin.serialize());
}
SignalServiceGroup groupContext = SignalServiceGroup.newBuilder(Type.UPDATE)
.withAvatar(avatar)
.withId(groupId, SignalServiceGroup.GroupType.SIGNAL)
.withMembers(members)
.withAdmins(admins)
.withName(record.get().getTitle())
.build();
Address groupAddress = Address.Companion.fromSerialized(GroupUtil.getEncodedClosedGroupID(groupId));
Recipient groupRecipient = Recipient.from(context, groupAddress, false);
SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder()
.asGroupMessage(groupContext)
.withTimestamp(System.currentTimeMillis())
.withExpiration(groupRecipient.getExpireMessages())
.build();
messageSender.sendMessage(0, new SignalServiceAddress(source),
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.Companion.fromSerialized(source), false)),
message, false);
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
Log.w(TAG, e);
return e instanceof PushNetworkException;
}
@Override
public void onCanceled() {
}
public static final class Factory implements Job.Factory<PushGroupUpdateJob> {
@Override
public @NonNull PushGroupUpdateJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new PushGroupUpdateJob(parameters,
data.getString(KEY_SOURCE),
GroupUtil.getDecodedGroupIDAsData(data.getString(KEY_GROUP_ID)));
}
}
}

@ -13,6 +13,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt
import org.session.libsession.messaging.threads.recipients.Recipient.UnidentifiedAccessMode;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -28,23 +29,17 @@ import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.crypto.UnidentifiedAccessPair;
import org.session.libsignal.service.api.crypto.UntrustedIdentityException;
import org.session.libsignal.service.api.messages.SendMessageResult;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Preview;
import org.session.libsignal.service.api.messages.multidevice.SignalServiceSyncMessage;
import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.UnregisteredUserException;
import org.session.libsignal.service.loki.api.SnodeAPI;
import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol;
import java.io.FileNotFoundException;
import java.io.IOException;
@ -70,10 +65,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
private long templateMessageId;
private Address destination;
public PushMediaSendJob(long messageId, Address destination) {
this(messageId, messageId, destination);
}
public PushMediaSendJob(long templateMessageId, long messageId, Address destination) {
this(constructParameters(destination), templateMessageId, messageId, destination);
}
@ -183,17 +174,15 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis());
}
if (TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {
log(TAG, "Marking recipient as UD-unrestricted following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) {
log(TAG, "Marking recipient as UD-enabled following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
log(TAG, "Marking recipient as UD-disabled following a non-UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
}
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {
log(TAG, "Marking recipient as UD-unrestricted following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) {
log(TAG, "Marking recipient as UD-enabled following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
log(TAG, "Marking recipient as UD-disabled following a non-UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
}
if (messageId > 0 && message.getExpiresIn() > 0 && !message.isExpirationUpdate()) {
@ -203,18 +192,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
log(TAG, "Sent message: " + messageId);
} catch (InsecureFallbackApprovalException ifae) {
warn(TAG, "Failure", ifae);
if (messageId >= 0) {
database.markAsPendingInsecureSmsFallback(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}
} catch (UntrustedIdentityException uie) {
warn(TAG, "Failure", uie);
if (messageId >= 0) {
database.addMismatchedIdentity(messageId, Address.Companion.fromSerialized(uie.getE164Number()), uie.getIdentityKey());
database.markAsSentFailed(messageId);
}
} catch (SnodeAPI.Error e) {
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
if (messageId >= 0) {
@ -240,23 +217,21 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
}
private boolean deliver(OutgoingMediaMessage message)
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
UndeliverableMessageException, SnodeAPI.Error
throws RetryLaterException, UndeliverableMessageException, SnodeAPI.Error
{
try {
Recipient recipient = Recipient.from(context, destination, false);
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
SignalServiceAddress address = getPushAddress(recipient.getAddress());
SignalServiceAddress localAddress = new SignalServiceAddress(userPublicKey);
List<Attachment> attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList();
List<Attachment> attachments = Stream.of(message.getAttachments()).toList();
List<SignalServiceAttachment> serviceAttachments = getAttachmentPointersFor(attachments);
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
Optional<SignalServiceDataMessage.Quote> quote = getQuoteFor(message);
Optional<SignalServiceDataMessage.Sticker> sticker = getStickerFor(message);
List<SharedContact> sharedContacts = getSharedContactsFor(message);
List<Preview> previews = getPreviewsFor(message);
Optional<UnidentifiedAccessPair> unidentifiedAccessPair = UnidentifiedAccessUtil.getAccessFor(context, recipient);
Optional<UnidentifiedAccess> unidentifiedAccessPair = UnidentifiedAccessUtil.getAccessFor(context, recipient);
SignalServiceDataMessage mediaMessage = SignalServiceDataMessage.newBuilder()
.withBody(message.getBody())
@ -265,7 +240,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
.withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull())
.withQuote(quote.orNull())
.withSticker(sticker.orNull())
.withSharedContacts(sharedContacts)
.withPreviews(previews)
.asExpirationUpdate(message.isExpirationUpdate())
@ -279,13 +253,12 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
.withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull())
.withQuote(quote.orNull())
.withSticker(sticker.orNull())
.withSharedContacts(sharedContacts)
.withPreviews(previews)
.asExpirationUpdate(message.isExpirationUpdate())
.build();
if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) {
if (userPublicKey == address.getNumber()) {
// Loki - Device link messages don't go through here
SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccessPair, mediaMessage, true);
if (result.getLokiAPIError() != null) {
@ -302,7 +275,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
try {
// send to ourselves to sync multi-device
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
Optional<UnidentifiedAccess> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
SendMessageResult selfSendResult = messageSender.sendMessage(messageId, localAddress, syncAccess, mediaSelfSendMessage, true);
if (selfSendResult.getLokiAPIError() != null) {
throw selfSendResult.getLokiAPIError();
@ -314,9 +287,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
return isUnidentified;
}
}
} catch (UnregisteredUserException e) {
warn(TAG, e);
throw new InsecureFallbackApprovalException(e);
} catch (FileNotFoundException e) {
warn(TAG, e);
throw new UndeliverableMessageException(e);
@ -331,7 +301,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
public @NonNull PushMediaSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
long templateMessageID = data.getLong(KEY_TEMPLATE_MESSAGE_ID);
long messageID = data.getLong(KEY_MESSAGE_ID);
Address destination = Address.Companion.fromSerialized(data.getString(KEY_DESTINATION));
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
return new PushMediaSendJob(parameters, templateMessageID, messageID, destination);
}
}

@ -1,90 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import org.session.libsession.utilities.TextSecurePreferences;
import java.io.IOException;
import javax.inject.Inject;
public class PushNotificationReceiveJob extends PushReceivedJob implements InjectableType {
public static final String KEY = "PushNotificationReceiveJob";
private static final String TAG = PushNotificationReceiveJob.class.getSimpleName();
@Inject SignalServiceMessageReceiver receiver;
public PushNotificationReceiveJob(Context context) {
this(new Job.Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setQueue("__notification_received")
.setMaxAttempts(3)
.setMaxInstances(1)
.build());
setContext(context);
}
private PushNotificationReceiveJob(@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 {
pullAndProcessMessages(receiver, TAG, System.currentTimeMillis());
}
public void pullAndProcessMessages(SignalServiceMessageReceiver receiver, String tag, long startTime) throws IOException {
synchronized (PushReceivedJob.RECEIVE_LOCK) {
receiver.retrieveMessages(envelope -> {
Log.i(tag, "Retrieved an envelope." + timeSuffix(startTime));
processEnvelope(envelope, false);
Log.i(tag, "Successfully processed an envelope." + timeSuffix(startTime));
});
TextSecurePreferences.setNeedsMessagePull(context, false);
}
}
@Override
public boolean onShouldRetry(@NonNull Exception e) {
Log.w(TAG, e);
return e instanceof PushNetworkException;
}
@Override
public void onCanceled() {
Log.w(TAG, "***** Failed to download pending message!");
// MessageNotifier.notifyMessagesPending(getContext());
}
private static String timeSuffix(long startTime) {
return " (" + (System.currentTimeMillis() - startTime) + " ms elapsed)";
}
public static final class Factory implements Job.Factory<PushNotificationReceiveJob> {
@Override
public @NonNull PushNotificationReceiveJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new PushNotificationReceiveJob(parameters);
}
}
}

@ -1,11 +1,9 @@
package org.thoughtcrime.securesms.jobs;
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import org.session.libsession.messaging.threads.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.recipients.Recipient;
@ -25,7 +23,7 @@ public abstract class PushReceivedJob extends BaseJob {
synchronized (RECEIVE_LOCK) {
try {
if (envelope.hasSource()) {
Address source = Address.Companion.fromExternal(context, envelope.getSource());
Address source = Address.fromExternal(context, envelope.getSource());
Recipient recipient = Recipient.from(context, source, false);
if (!isActiveNumber(recipient)) {
@ -33,10 +31,7 @@ public abstract class PushReceivedJob extends BaseJob {
}
}
if (envelope.isReceipt()) {
handleReceipt(envelope);
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage()
|| envelope.isUnidentifiedSender() || envelope.isFallbackMessage() || envelope.isClosedGroupCiphertext()) {
if (envelope.isUnidentifiedSender() || envelope.isClosedGroupCiphertext()) {
handleMessage(envelope, isPushNotification);
} else {
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
@ -51,13 +46,6 @@ public abstract class PushReceivedJob extends BaseJob {
new PushDecryptJob(context).processMessage(envelope, isPushNotification);
}
@SuppressLint("DefaultLocale")
private void handleReceipt(SignalServiceEnvelope envelope) {
Log.i(TAG, String.format("Received receipt: (XXXXX, %d)", envelope.getTimestamp()));
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(new SyncMessageId(Address.Companion.fromExternal(context, envelope.getSource()),
envelope.getTimestamp()), System.currentTimeMillis());
}
private boolean isActiveNumber(@NonNull Recipient recipient) {
return recipient.resolve().getRegistered() == Recipient.RegisteredState.REGISTERED;
}

@ -12,7 +12,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.utilities.MediaTypes;
import org.session.libsignal.utilities.Base64;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.greenrobot.eventbus.EventBus;
@ -34,13 +33,10 @@ import org.thoughtcrime.securesms.util.BitmapUtil;
import org.session.libsignal.utilities.Hex;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.UnidentifiedAccessPair;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Preview;
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.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress;
@ -55,7 +51,6 @@ import java.util.concurrent.TimeUnit;
public abstract class PushSendJob extends SendJob {
private static final String TAG = PushSendJob.class.getSimpleName();
private static final long CERTIFICATE_EXPIRATION_BUFFER = TimeUnit.DAYS.toMillis(1);
protected PushSendJob(Job.Parameters parameters) {
super(parameters);
@ -72,14 +67,6 @@ public abstract class PushSendJob extends SendJob {
@Override
protected final void onSend() throws Exception {
// if (TextSecurePreferences.getSignedPreKeyFailureCount(context) > 5) {
// ApplicationContext.getInstance(context)
// .getJobManager()
// .add(new RotateSignedPreKeyJob());
//
// throw new TextSecureExpiredException("Too many signed prekey rotation failures");
// }
onPushSend();
}
@ -102,19 +89,6 @@ public abstract class PushSendJob extends SendJob {
return new SignalServiceAddress(address.toString(), Optional.fromNullable(relay));
}
protected List<SignalServiceAttachment> getAttachmentsFor(List<Attachment> parts) {
List<SignalServiceAttachment> attachments = new LinkedList<>();
for (final Attachment attachment : parts) {
SignalServiceAttachment converted = getAttachmentFor(attachment);
if (converted != null) {
attachments.add(converted);
}
}
return attachments;
}
protected SignalServiceAttachment getAttachmentFor(Attachment attachment) {
try {
if (attachment.getDataUri() == null || attachment.getSize() == 0) throw new IOException("Assertion failed, outgoing attachment has no data!");
@ -225,26 +199,6 @@ public abstract class PushSendJob extends SendJob {
return Optional.of(new SignalServiceDataMessage.Quote(quoteId, new SignalServiceAddress(quoteAuthor.serialize()), quoteBody, quoteAttachments));
}
protected Optional<SignalServiceDataMessage.Sticker> getStickerFor(OutgoingMediaMessage message) {
Attachment stickerAttachment = Stream.of(message.getAttachments()).filter(Attachment::isSticker).findFirst().orElse(null);
if (stickerAttachment == null) {
return Optional.absent();
}
try {
byte[] packId = Hex.fromStringCondensed(stickerAttachment.getSticker().getPackId());
byte[] packKey = Hex.fromStringCondensed(stickerAttachment.getSticker().getPackKey());
int stickerId = stickerAttachment.getSticker().getStickerId();
SignalServiceAttachment attachment = getAttachmentPointerFor(stickerAttachment);
return Optional.of(new SignalServiceDataMessage.Sticker(packId, packKey, stickerId, attachment));
} catch (IOException e) {
Log.w(TAG, "Failed to decode sticker id/key", e);
return Optional.absent();
}
}
List<SharedContact> getSharedContactsFor(OutgoingMediaMessage mediaMessage) {
List<SharedContact> sharedContacts = new LinkedList<>();
@ -272,21 +226,5 @@ public abstract class PushSendJob extends SendJob {
}).toList();
}
protected void rotateSenderCertificateIfNecessary() throws IOException {
// Loki - We don't need verification on sender certificates
}
protected SignalServiceSyncMessage buildSelfSendSyncMessage(@NonNull Context context, @NonNull SignalServiceDataMessage message, Optional<UnidentifiedAccessPair> syncAccess) {
String masterPublicKeyOrNull = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
String masterPublicKey = masterPublicKeyOrNull != null ? masterPublicKeyOrNull : TextSecurePreferences.getLocalNumber(context);
SentTranscriptMessage transcript = new SentTranscriptMessage(masterPublicKey,
message.getTimestamp(),
message,
message.getExpiresInSeconds(),
Collections.singletonMap(masterPublicKey, syncAccess.isPresent()));
return SignalServiceSyncMessage.forSentTranscript(transcript);
}
protected abstract void onPushSend() throws Exception;
}

@ -22,19 +22,13 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.crypto.UnidentifiedAccessPair;
import org.session.libsignal.service.api.crypto.UntrustedIdentityException;
import org.session.libsignal.service.api.messages.SendMessageResult;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.multidevice.SignalServiceSyncMessage;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.UnregisteredUserException;
import org.session.libsignal.service.loki.api.SnodeAPI;
import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol;
import java.io.IOException;
@ -126,17 +120,15 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis());
}
if (TextSecurePreferences.isUnidentifiedDeliveryEnabled(context)) {
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {
log(TAG, "Marking recipient as UD-unrestricted following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) {
log(TAG, "Marking recipient as UD-enabled following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
log(TAG, "Marking recipient as UD-disabled following a non-UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
}
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {
log(TAG, "Marking recipient as UD-unrestricted following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.UNRESTRICTED);
} else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) {
log(TAG, "Marking recipient as UD-enabled following a UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.ENABLED);
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
log(TAG, "Marking recipient as UD-disabled following a non-UD send.");
DatabaseFactory.getRecipientDatabase(context).setUnidentifiedAccessMode(recipient, UnidentifiedAccessMode.DISABLED);
}
if (record.getExpiresIn() > 0 && messageId >= 0) {
@ -146,19 +138,6 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
log(TAG, "Sent message: " + templateMessageId + (hasSameDestination ? "" : "to a linked device."));
} catch (InsecureFallbackApprovalException e) {
warn(TAG, "Couldn't send message due to error: ", e);
if (messageId >= 0) {
database.markAsPendingInsecureSmsFallback(record.getId());
ApplicationContext.getInstance(context).messageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
}
} catch (UntrustedIdentityException e) {
warn(TAG, "Couldn't send message due to error: ", e);
if (messageId >= 0) {
database.addMismatchedIdentity(record.getId(), Address.Companion.fromSerialized(e.getE164Number()), e.getIdentityKey());
database.markAsSentFailed(record.getId());
database.markAsPush(record.getId());
}
} catch (SnodeAPI.Error e) {
Log.d("Loki", "Couldn't send message due to error: " + e.getDescription());
if (messageId >= 0) {
@ -189,8 +168,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
}
}
private boolean deliver(SmsMessageRecord message)
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException, SnodeAPI.Error
private boolean deliver(SmsMessageRecord message) throws RetryLaterException, SnodeAPI.Error
{
try {
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
@ -198,21 +176,15 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
SignalServiceAddress address = getPushAddress(recipient.getAddress());
SignalServiceAddress localAddress = new SignalServiceAddress(userPublicKey);
Optional<byte[]> profileKey = getProfileKey(recipient);
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
Optional<UnidentifiedAccess> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
log(TAG, "Have access key to use: " + unidentifiedAccess.isPresent());
// PreKeyBundle preKeyBundle = null;
// if (message.isEndSession()) {
// preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(destination.serialize());
// }
SignalServiceDataMessage textSecureMessage = SignalServiceDataMessage.newBuilder()
.withTimestamp(message.getDateSent())
.withBody(message.getBody())
.withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull())
.asEndSessionMessage(message.isEndSession())
.build();
SignalServiceDataMessage textSecureSelfSendMessage = SignalServiceDataMessage.newBuilder()
@ -221,10 +193,9 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
.withSyncTarget(destination.serialize())
.withExpiration((int)(message.getExpiresIn() / 1000))
.withProfileKey(profileKey.orNull())
.asEndSessionMessage(message.isEndSession())
.build();
if (SessionMetaProtocol.shared.isNoteToSelf(address.getNumber())) {
if (userPublicKey.equals(address.getNumber())) {
// Loki - Device link messages don't go through here
SendMessageResult result = messageSender.sendMessage(messageId, address, unidentifiedAccess, textSecureMessage, true);
if (result.getLokiAPIError() != null) {
@ -241,7 +212,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
try {
// send to ourselves to sync multi-device
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
Optional<UnidentifiedAccess> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
SendMessageResult selfSendResult = messageSender.sendMessage(messageId, localAddress, syncAccess, textSecureSelfSendMessage, true);
if (selfSendResult.getLokiAPIError() != null) {
throw selfSendResult.getLokiAPIError();
@ -252,9 +223,6 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
return isUnidentified;
}
}
} catch (UnregisteredUserException e) {
warn(TAG, "Failure", e);
throw new InsecureFallbackApprovalException(e);
} catch (IOException e) {
warn(TAG, "Failure", e);
throw new RetryLaterException(e);
@ -266,7 +234,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
public @NonNull PushTextSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
long templateMessageID = data.getLong(KEY_TEMPLATE_MESSAGE_ID);
long messageID = data.getLong(KEY_MESSAGE_ID);
Address destination = Address.Companion.fromSerialized(data.getString(KEY_DESTINATION));
Address destination = Address.fromSerialized(data.getString(KEY_DESTINATION));
return new PushTextSendJob(parameters, templateMessageID, messageID, destination);
}
}

@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.crypto.UntrustedIdentityException;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.SignalServiceGroup.Type;
@ -71,7 +70,7 @@ public class RequestGroupInfoJob extends BaseJob implements InjectableType {
}
@Override
public void onRun() throws IOException, UntrustedIdentityException {
public void onRun() throws IOException {
SignalServiceGroup group = SignalServiceGroup.newBuilder(Type.REQUEST_INFO)
.withId(groupId, SignalServiceGroup.GroupType.SIGNAL)
.build();
@ -82,7 +81,7 @@ public class RequestGroupInfoJob extends BaseJob implements InjectableType {
.build();
messageSender.sendMessage(0, new SignalServiceAddress(source),
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.Companion.fromExternal(context, source), false)),
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromExternal(context, source), false)),
message, false);
}

@ -134,7 +134,7 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType
@Override
public @NonNull RetrieveProfileAvatarJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new RetrieveProfileAvatarJob(parameters,
Recipient.from(application, Address.Companion.fromSerialized(data.getString(KEY_ADDRESS)), true),
Recipient.from(application, Address.fromSerialized(data.getString(KEY_ADDRESS)), true),
data.getString(KEY_PROFILE_AVATAR));
}
}

@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.crypto.UntrustedIdentityException;
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
@ -78,7 +77,7 @@ public class SendDeliveryReceiptJob extends BaseJob implements InjectableType {
}
@Override
public void onRun() throws IOException, UntrustedIdentityException {
public void onRun() throws IOException {
Log.d("Loki", "Sending delivery receipt.");
SignalServiceAddress remoteAddress = new SignalServiceAddress(address);
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
@ -86,7 +85,7 @@ public class SendDeliveryReceiptJob extends BaseJob implements InjectableType {
timestamp);
messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.Companion.fromSerialized(address), false)),
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)),
receiptMessage);
}
@ -105,7 +104,7 @@ public class SendDeliveryReceiptJob extends BaseJob implements InjectableType {
@Override
public @NonNull SendDeliveryReceiptJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new SendDeliveryReceiptJob(parameters,
Address.Companion.fromSerialized(data.getString(KEY_ADDRESS)),
Address.fromSerialized(data.getString(KEY_ADDRESS)),
data.getLong(KEY_MESSAGE_ID),
data.getLong(KEY_TIMESTAMP));
}

@ -45,36 +45,6 @@ public abstract class SendJob extends BaseJob {
}
}
protected List<Attachment> scaleAndStripExifFromAttachments(@NonNull MediaConstraints constraints,
@NonNull List<Attachment> attachments)
throws UndeliverableMessageException
{
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
List<Attachment> results = new LinkedList<>();
for (Attachment attachment : attachments) {
try {
if (constraints.isSatisfied(context, attachment)) {
if (MediaUtil.isJpeg(attachment)) {
MediaStream stripped = constraints.getResizedMedia(context, attachment);
results.add(attachmentDatabase.updateAttachmentData(attachment, stripped));
} else {
results.add(attachment);
}
} else if (constraints.canResize(attachment)) {
MediaStream resized = constraints.getResizedMedia(context, attachment);
results.add(attachmentDatabase.updateAttachmentData(attachment, resized));
} else {
throw new UndeliverableMessageException("Size constraints could not be met!");
}
} catch (IOException | MmsException e) {
throw new UndeliverableMessageException(e);
}
}
return results;
}
protected void log(@NonNull String tag, @NonNull String message) {
Log.i(tag, JobLogger.format(this, message));
}

@ -13,7 +13,6 @@ import org.session.libsignal.utilities.logging.Log;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.crypto.UntrustedIdentityException;
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
@ -84,14 +83,14 @@ public class SendReadReceiptJob extends BaseJob implements InjectableType {
}
@Override
public void onRun() throws IOException, UntrustedIdentityException {
public void onRun() throws IOException {
if (!TextSecurePreferences.isReadReceiptsEnabled(context) || messageIds.isEmpty()) return;
SignalServiceAddress remoteAddress = new SignalServiceAddress(address);
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp);
messageSender.sendReceipt(remoteAddress,
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.Companion.fromSerialized(address), false)),
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)),
receiptMessage);
}
@ -109,7 +108,7 @@ public class SendReadReceiptJob extends BaseJob implements InjectableType {
public static final class Factory implements Job.Factory<SendReadReceiptJob> {
@Override
public @NonNull SendReadReceiptJob create(@NonNull Parameters parameters, @NonNull Data data) {
Address address = Address.Companion.fromSerialized(data.getString(KEY_ADDRESS));
Address address = Address.fromSerialized(data.getString(KEY_ADDRESS));
long timestamp = data.getLong(KEY_TIMESTAMP);
long[] ids = data.hasLongArray(KEY_MESSAGE_IDS) ? data.getLongArray(KEY_MESSAGE_IDS) : new long[0];
List<Long> messageIds = new ArrayList<>(ids.length);

@ -1,171 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.telephony.SmsMessage;
import org.session.libsession.messaging.jobs.Data;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.session.libsignal.utilities.Base64;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class SmsReceiveJob extends BaseJob {
public static final String KEY = "SmsReceiveJob";
private static final String TAG = SmsReceiveJob.class.getSimpleName();
private static final String KEY_PDUS = "pdus";
private static final String KEY_SUBSCRIPTION_ID = "subscription_id";
private @Nullable Object[] pdus;
private int subscriptionId;
public SmsReceiveJob(@Nullable Object[] pdus, int subscriptionId) {
this(new Job.Parameters.Builder()
.addConstraint(SqlCipherMigrationConstraint.KEY)
.setMaxAttempts(25)
.build(),
pdus,
subscriptionId);
}
private SmsReceiveJob(@NonNull Job.Parameters parameters, @Nullable Object[] pdus, int subscriptionId) {
super(parameters);
this.pdus = pdus;
this.subscriptionId = subscriptionId;
}
@Override
public @NonNull
Data serialize() {
String[] encoded = new String[pdus.length];
for (int i = 0; i < pdus.length; i++) {
encoded[i] = Base64.encodeBytes((byte[]) pdus[i]);
}
return new Data.Builder().putStringArray(KEY_PDUS, encoded)
.putInt(KEY_SUBSCRIPTION_ID, subscriptionId)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws MigrationPendingException {
Log.i(TAG, "onRun()");
Optional<IncomingTextMessage> message = assembleMessageFragments(pdus, subscriptionId);
if (message.isPresent() && !isBlocked(message.get())) {
Optional<InsertResult> insertResult = storeMessage(message.get());
if (insertResult.isPresent()) {
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else if (message.isPresent()) {
Log.w(TAG, "*** Received blocked SMS, ignoring...");
} else {
Log.w(TAG, "*** Failed to assemble message fragments!");
}
}
@Override
public void onCanceled() {
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
return exception instanceof MigrationPendingException;
}
private boolean isBlocked(IncomingTextMessage message) {
if (message.getSender() != null) {
Recipient recipient = Recipient.from(context, message.getSender(), false);
return recipient.isBlocked();
}
return false;
}
private Optional<InsertResult> storeMessage(IncomingTextMessage message) throws MigrationPendingException {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
database.ensureMigration();
if (TextSecurePreferences.getNeedsSqlCipherMigration(context)) {
throw new MigrationPendingException();
}
if (message.isSecureMessage()) {
IncomingTextMessage placeholder = new IncomingTextMessage(message, "");
Optional<InsertResult> insertResult = database.insertMessageInbox(placeholder);
database.markAsLegacyVersion(insertResult.get().getMessageId());
return insertResult;
} else {
return database.insertMessageInbox(message);
}
}
private Optional<IncomingTextMessage> assembleMessageFragments(@Nullable Object[] pdus, int subscriptionId) {
if (pdus == null) {
return Optional.absent();
}
List<IncomingTextMessage> messages = new LinkedList<>();
for (Object pdu : pdus) {
messages.add(new IncomingTextMessage(context, SmsMessage.createFromPdu((byte[])pdu), subscriptionId));
}
if (messages.isEmpty()) {
return Optional.absent();
}
return Optional.of(new IncomingTextMessage(messages));
}
private class MigrationPendingException extends Exception {
}
public static final class Factory implements Job.Factory<SmsReceiveJob> {
@Override
public @NonNull SmsReceiveJob create(@NonNull Parameters parameters, @NonNull Data data) {
try {
int subscriptionId = data.getInt(KEY_SUBSCRIPTION_ID);
String[] encoded = data.getStringArray(KEY_PDUS);
Object[] pdus = new Object[encoded.length];
for (int i = 0; i < encoded.length; i++) {
pdus[i] = Base64.decode(encoded[i]);
}
return new SmsReceiveJob(parameters, pdus, subscriptionId);
} catch (IOException e) {
throw new AssertionError(e);
}
}
}
}

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

Loading…
Cancel
Save