Merge branch 'dev' into sealed-sender

pull/67/head
Mikunj 5 years ago
commit 8aa46aebec

@ -178,8 +178,6 @@
android:name="org.thoughtcrime.securesms.loki.redesign.activities.LinkedDevicesActivity"
android:screenOrientation="portrait" />
<!-- Session -->
<activity android:name="org.thoughtcrime.securesms.loki.LinkedDevicesActivity"
android:screenOrientation="portrait" />
<activity
android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|fontScale"
@ -549,27 +547,7 @@
android:name="org.thoughtcrime.securesms.ShortcutLauncherActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true"
android:theme="@style/TextSecure.LightNoActionBar" /> <!-- Loki -->
<activity
android:name="org.thoughtcrime.securesms.loki.DisplayNameActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:theme="@style/TextSecure.DarkRegistrationTheme"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name="org.thoughtcrime.securesms.loki.SeedActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:theme="@style/TextSecure.DarkRegistrationTheme"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name="org.thoughtcrime.securesms.loki.NewConversationActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysVisible" />
<activity
android:name="org.thoughtcrime.securesms.loki.AddPublicChatActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysVisible" /> <!-- Loki -->
android:theme="@style/TextSecure.LightNoActionBar" />
<service
android:name="org.thoughtcrime.securesms.service.WebRtcCallService"
android:enabled="true" />
@ -808,12 +786,12 @@
</intent-filter>
</receiver>
<!-- Session -->
<receiver android:name="org.thoughtcrime.securesms.loki.BackgroundPollWorker">
<receiver android:name="org.thoughtcrime.securesms.loki.redesign.messaging.BackgroundPollWorker">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.loki.BackgroundPublicChatPollWorker">
<receiver android:name="org.thoughtcrime.securesms.loki.redesign.messaging.BackgroundPublicChatPollWorker">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>

@ -187,7 +187,6 @@ dependencies {
implementation "org.whispersystems:curve25519-java:0.5.0"
// Remote:
// implementation "com.github.loki-project:loki-messenger-android-service:dev-SNAPSHOT"
// implementation "org.signal:signal-metadata-android:0.0.3"
implementation "com.google.protobuf:protobuf-java:2.5.0"
implementation "com.googlecode.libphonenumber:libphonenumber:8.10.7"
implementation "com.fasterxml.jackson.core:jackson-databind:2.9.8"

@ -66,8 +66,8 @@ import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.loki.BackgroundPollWorker;
import org.thoughtcrime.securesms.loki.BackgroundPublicChatPollWorker;
import org.thoughtcrime.securesms.loki.redesign.messaging.BackgroundPollWorker;
import org.thoughtcrime.securesms.loki.redesign.messaging.BackgroundPublicChatPollWorker;
import org.thoughtcrime.securesms.loki.LokiAPIDatabase;
import org.thoughtcrime.securesms.loki.LokiPublicChatManager;
import org.thoughtcrime.securesms.loki.LokiRSSFeedPoller;

@ -41,8 +41,6 @@ import android.util.Log;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.loki.LinkedDevicesActivity;
import org.thoughtcrime.securesms.loki.QRCodeDialog;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
@ -338,13 +336,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
shareIntent.setType("text/plain");
startActivity(shareIntent);
break;
case PREFERENCE_CATEGORY_QR_CODE:
QRCodeDialog.INSTANCE.show(getContext());
break;
case PREFERENCE_CATEGORY_LINKED_DEVICES:
Intent intent = new Intent(getActivity(), LinkedDevicesActivity.class);
startActivity(intent);
break;
case PREFERENCE_CATEGORY_QR_CODE: break;
case PREFERENCE_CATEGORY_LINKED_DEVICES: break;
case PREFERENCE_CATEGORY_SEED:
Analytics.Companion.getShared().track("Seed Modal Shown");
File languageFileDirectory = new File(getContext().getApplicationInfo().dataDir);

@ -16,7 +16,6 @@
*/
package org.thoughtcrime.securesms;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
@ -35,7 +34,6 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.Toast;
@ -46,20 +44,16 @@ import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.components.RatingManager;
import org.thoughtcrime.securesms.components.SearchToolbar;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
import org.thoughtcrime.securesms.loki.AddPublicChatActivity;
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
import org.thoughtcrime.securesms.loki.RecipientAvatarModifiedEvent;
import org.thoughtcrime.securesms.loki.redesign.activities.JoinPublicChatActivity;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.search.SearchFragment;
import org.thoughtcrime.securesms.service.KeyCachingService;
@ -68,7 +62,6 @@ import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import java.util.List;
@ -333,7 +326,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
}
private void addNewPublicChat() {
startActivity(new Intent(this, AddPublicChatActivity.class));
startActivity(new Intent(this, JoinPublicChatActivity.class));
}
@Subscribe(threadMode = ThreadMode.MAIN)

@ -74,7 +74,7 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
import org.thoughtcrime.securesms.loki.NewConversationActivity;
import org.thoughtcrime.securesms.loki.redesign.activities.CreatePrivateChatActivity;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
@ -155,7 +155,7 @@ public class ConversationListFragment extends Fragment
super.onActivityCreated(bundle);
setHasOptionsMenu(true);
fab.setOnClickListener(v -> startActivity(new Intent(getActivity(), NewConversationActivity.class)));
fab.setOnClickListener(v -> startActivity(new Intent(getActivity(), CreatePrivateChatActivity.class)));
initializeListAdapter();
initializeTypingObserver();
}

@ -37,7 +37,7 @@ import org.thoughtcrime.securesms.components.FromTextView;
import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.components.TypingIndicatorView;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities;
import org.thoughtcrime.securesms.loki.redesign.utilities.MentionUtilities;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;

@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.loki.DeviceListBottomSheetFragment;
import org.thoughtcrime.securesms.loki.MnemonicUtilities;
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.util.guava.Function;

@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.redesign.activities.LandingActivity;
import org.thoughtcrime.securesms.loki.SeedActivity;
import org.thoughtcrime.securesms.loki.redesign.activities.SeedActivity;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;

@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.NewConversationActivity;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Rfc5724Uri;

@ -157,7 +157,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities;
import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;

@ -2,47 +2,23 @@ package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.database.Database;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.MnemonicUtilities;
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities;
import org.thoughtcrime.securesms.util.AsyncLoader;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPrivateKey;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.util.ByteUtil;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.*;
import static org.whispersystems.signalservice.loki.utilities.TrimmingKt.removing05PrefixIfNeeded;
public class DeviceListLoader extends AsyncLoader<List<Device>> {
private static final String TAG = DeviceListLoader.class.getSimpleName();

@ -68,7 +68,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.FriendRequestHandler;
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities;
import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
import org.thoughtcrime.securesms.loki.LokiPreKeyBundleDatabase;
import org.thoughtcrime.securesms.loki.LokiPreKeyRecordDatabase;

@ -1,78 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.os.Bundle
import android.util.Patterns
import android.view.MenuItem
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_add_public_chat.*
import network.loki.messenger.R
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.util.DynamicTheme
import org.thoughtcrime.securesms.util.TextSecurePreferences
class AddPublicChatActivity : PassphraseRequiredActionBarActivity() {
private val dynamicTheme = DynamicTheme()
override fun onPreCreate() {
dynamicTheme.onCreate(this)
}
override fun onCreate(bundle: Bundle?, isReady: Boolean) {
supportActionBar!!.setTitle(R.string.fragment_add_public_chat_title)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
setContentView(R.layout.activity_add_public_chat)
updateUI(false)
addButton.setOnClickListener { addPublicChatIfPossible() }
}
public override fun onResume() {
super.onResume()
dynamicTheme.onResume(this)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun addPublicChatIfPossible() {
val inputMethodManager = getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(urlEditText.windowToken, 0)
val url = urlEditText.text.toString().toLowerCase().replace("http://", "https://")
if (!Patterns.WEB_URL.matcher(url).matches() || !url.startsWith("https://")) {
return Toast.makeText(this, R.string.fragment_add_public_chat_invalid_url_message, Toast.LENGTH_SHORT).show()
}
updateUI(true)
val application = ApplicationContext.getInstance(this)
val channel: Long = 1
val displayName = TextSecurePreferences.getProfileName(this)
val lokiPublicChatAPI = application.lokiPublicChatAPI!!
application.lokiPublicChatManager.addChat(url, channel).successUi {
lokiPublicChatAPI.getMessages(channel, url)
lokiPublicChatAPI.setDisplayName(displayName, url)
val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(this)
val profileUrl: String? = TextSecurePreferences.getProfileAvatarUrl(this)
lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
finish()
}.failUi {
updateUI(false)
Toast.makeText(this, R.string.fragment_add_public_chat_connection_failed_message, Toast.LENGTH_SHORT).show()
}
}
private fun updateUI(isConnecting: Boolean) {
addButton.isEnabled = !isConnecting
val text = if (isConnecting) R.string.fragment_add_public_chat_add_button_title_2 else R.string.fragment_add_public_chat_add_button_title_1
addButton.setText(text)
urlEditText.isEnabled = !isConnecting
}
}

@ -1,29 +0,0 @@
package org.thoughtcrime.securesms.loki
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
// Loki - TODO: Remove this yucky delegate pattern for device linking dialog once we have the redesign
interface DeviceLinkingDelegate {
companion object {
fun combine(vararg delegates: DeviceLinkingDelegate?): DeviceLinkingDelegate {
val validDelegates = delegates.filterNotNull()
return object : DeviceLinkingDelegate {
override fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {
for (delegate in validDelegates) { delegate.handleDeviceLinkAuthorized(pairingAuthorisation) }
}
override fun handleDeviceLinkingDialogDismissed() {
for (delegate in validDelegates) { delegate.handleDeviceLinkingDialogDismissed() }
}
override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {
for (delegate in validDelegates) { delegate.sendPairingAuthorizedMessage(pairingAuthorisation) }
}
}
}
}
fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {}
fun handleDeviceLinkingDialogDismissed() {}
fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {}
}

@ -1,59 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.content.Context
import android.support.v7.app.AlertDialog
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession
import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
class DeviceLinkingDialog private constructor(private val context: Context, private val mode: DeviceLinkingView.Mode, private val delegate: DeviceLinkingDelegate?) : DeviceLinkingDelegate, DeviceLinkingSessionListener {
private lateinit var view: DeviceLinkingView
private lateinit var dialog: AlertDialog
companion object {
fun show(context: Context, mode: DeviceLinkingView.Mode, delegate: DeviceLinkingDelegate?): DeviceLinkingDialog {
val dialog = DeviceLinkingDialog(context, mode, delegate)
dialog.show()
return dialog
}
}
private fun show() {
val delegate = DeviceLinkingDelegate.combine(this, this.delegate)
view = DeviceLinkingView(context, mode, delegate)
dialog = AlertDialog.Builder(context).setView(view).show()
dialog.setCanceledOnTouchOutside(false)
view.dismiss = { dismiss() }
DeviceLinkingSession.shared.startListeningForLinkingRequests()
DeviceLinkingSession.shared.addListener(this)
}
private fun dismiss() {
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this)
dialog.dismiss()
}
override fun handleDeviceLinkingDialogDismissed() {
if (mode == DeviceLinkingView.Mode.Master && view.pairingAuthorisation != null) {
val authorisation = view.pairingAuthorisation!!
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorisation.secondaryDevicePublicKey)
}
}
override fun requestUserAuthorization(authorisation: PairingAuthorisation) {
Util.runOnMain {
view.requestUserAuthorization(authorisation)
}
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
}
override fun onDeviceLinkRequestAuthorized(authorisation: PairingAuthorisation) {
Util.runOnMain {
view.onDeviceLinkAuthorized(authorisation)
}
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
}
}

@ -1,132 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.content.Context
import android.graphics.Color
import android.graphics.PorterDuff
import android.os.Handler
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.View
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_device_linking.view.*
import kotlinx.android.synthetic.main.view_device_linking.view.cancelButton
import kotlinx.android.synthetic.main.view_device_linking.view.explanationTextView
import kotlinx.android.synthetic.main.view_device_linking.view.titleTextView
import network.loki.messenger.R
import org.thoughtcrime.securesms.qr.QrCode
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
import java.io.File
class DeviceLinkingView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, private val mode: Mode, private var delegate: DeviceLinkingDelegate) : LinearLayout(context, attrs, defStyleAttr) {
private val languageFileDirectory: File = MnemonicUtilities.getLanguageFileDirectory(context)
var dismiss: (() -> Unit)? = null
var pairingAuthorisation: PairingAuthorisation? = null
private set
// region Types
enum class Mode { Master, Slave }
// endregion
// region Lifecycle
constructor(context: Context, mode: Mode, delegate: DeviceLinkingDelegate) : this(context, null, 0, mode, delegate)
private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0, Mode.Master, object : DeviceLinkingDelegate { }) // Just pass in a dummy mode
private constructor(context: Context) : this(context, null)
init {
setUpViewHierarchy()
}
private fun setUpViewHierarchy() {
inflate(context, R.layout.view_device_linking, this)
spinner.indeterminateDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
val titleID = when (mode) {
Mode.Master -> R.string.view_device_linking_title_1
Mode.Slave -> R.string.view_device_linking_title_2
}
titleTextView.text = resources.getString(titleID)
val explanationID = when (mode) {
Mode.Master -> R.string.view_device_linking_explanation_1
Mode.Slave -> R.string.view_device_linking_explanation_2
}
explanationTextView.text = resources.getString(explanationID)
mnemonicTextView.visibility = if (mode == Mode.Master) View.GONE else View.VISIBLE
if (mode == Mode.Slave) {
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), hexEncodedPublicKey)
}
authorizeButton.visibility = View.GONE
authorizeButton.setOnClickListener { authorizePairing() }
// QR Code
spinner.visibility = if (mode == Mode.Master) View.GONE else View.VISIBLE
qrCodeImageView.visibility = if (mode == Mode.Master) View.VISIBLE else View.GONE
if (mode == Mode.Master) {
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val displayMetrics = DisplayMetrics()
ServiceUtil.getWindowManager(context).defaultDisplay.getMetrics(displayMetrics)
val size = displayMetrics.widthPixels - 2 * toPx(96, resources)
val qrCode = QrCode.create(hexEncodedPublicKey, size)
qrCodeImageView.setImageBitmap(qrCode)
}
cancelButton.setOnClickListener { cancel() }
}
// endregion
// region Device Linking
fun requestUserAuthorization(pairingAuthorisation: PairingAuthorisation) {
if (mode != Mode.Master || pairingAuthorisation.type != PairingAuthorisation.Type.REQUEST || this.pairingAuthorisation != null) { return }
this.pairingAuthorisation = pairingAuthorisation
spinner.visibility = View.GONE
qrCodeImageView.visibility = View.GONE
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
titleTextViewLayoutParams.topMargin = toPx(16, resources)
titleTextView.layoutParams = titleTextViewLayoutParams
titleTextView.text = resources.getString(R.string.view_device_linking_title_3)
explanationTextView.text = resources.getString(R.string.view_device_linking_explanation_2)
mnemonicTextView.visibility = View.VISIBLE
mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), pairingAuthorisation.secondaryDevicePublicKey)
authorizeButton.visibility = View.VISIBLE
}
fun onDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {
if (mode != Mode.Slave || pairingAuthorisation.type != PairingAuthorisation.Type.GRANT || this.pairingAuthorisation != null) { return }
this.pairingAuthorisation = pairingAuthorisation
spinner.visibility = View.GONE
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
titleTextViewLayoutParams.topMargin = toPx(8, resources)
titleTextView.layoutParams = titleTextViewLayoutParams
titleTextView.text = resources.getString(R.string.view_device_linking_title_4)
val explanationTextViewLayoutParams = explanationTextView.layoutParams as LayoutParams
explanationTextViewLayoutParams.bottomMargin = toPx(12, resources)
explanationTextView.layoutParams = explanationTextViewLayoutParams
explanationTextView.text = resources.getString(R.string.view_device_linking_explanation_3)
titleTextView.text = resources.getString(R.string.view_device_linking_title_4)
mnemonicTextView.visibility = View.GONE
buttonContainer.visibility = View.GONE
cancelButton.visibility = View.GONE
Handler().postDelayed({
delegate.handleDeviceLinkAuthorized(pairingAuthorisation)
dismiss?.invoke()
}, 4000)
}
// endregion
// region Interaction
private fun authorizePairing() {
val pairingAuthorisation = this.pairingAuthorisation
if (mode != Mode.Master || pairingAuthorisation == null) { return; }
delegate.sendPairingAuthorizedMessage(pairingAuthorisation)
delegate.handleDeviceLinkAuthorized(pairingAuthorisation)
dismiss?.invoke()
}
private fun cancel() {
delegate.handleDeviceLinkingDialogDismissed()
dismiss?.invoke()
}
// endregion
}

@ -9,16 +9,16 @@ import kotlinx.android.synthetic.main.fragment_device_list_bottom_sheet.*
import network.loki.messenger.R
public class DeviceListBottomSheetFragment : BottomSheetDialogFragment() {
var onEditTapped: (() -> Unit)? = null
var onUnlinkTapped: (() -> Unit)? = null
var onEditTapped: (() -> Unit)? = null
var onUnlinkTapped: (() -> Unit)? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_device_list_bottom_sheet, container, false)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_device_list_bottom_sheet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
editDisplayNameText.setOnClickListener { onEditTapped?.invoke() }
unlinkDeviceText.setOnClickListener { onUnlinkTapped?.invoke() }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
editDisplayNameText.setOnClickListener { onEditTapped?.invoke() }
unlinkDeviceText.setOnClickListener { onUnlinkTapped?.invoke() }
}
}

@ -1,61 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.content.Intent
import android.os.Bundle
import android.view.inputmethod.InputMethodManager
import kotlinx.android.synthetic.main.activity_display_name.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.ConversationListActivity
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.redesign.utilities.show
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.crypto.ProfileCipher
import org.whispersystems.signalservice.loki.utilities.Analytics
class DisplayNameActivity : BaseActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_display_name)
nextButton.setOnClickListener { continueIfPossible() }
Analytics.shared.track("Display Name Screen Viewed")
}
private fun continueIfPossible() {
val name = nameEditText.text.toString()
if (name.isEmpty()) {
return nameEditText.input.setError("Invalid")
}
if (!name.matches(Regex("[a-zA-Z0-9_]+"))) {
return nameEditText.input.setError("Invalid (a-z, A-Z, 0-9 and _ only)")
}
if (name.toByteArray().size > ProfileCipher.NAME_PADDED_LENGTH) {
return nameEditText.input.setError("Too Long")
} else {
Analytics.shared.track("Display Name Updated")
TextSecurePreferences.setProfileName(this, name)
}
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(nameEditText.windowToken, 0)
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setPromptedPushRegistration(this, true)
val application = ApplicationContext.getInstance(this)
application.setUpP2PAPI()
application.startLongPollingIfNeeded()
application.setUpStorageAPIIfNeeded()
show(Intent(this, ConversationListActivity::class.java))
finish()
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
if (publicChatAPI != null) {
application.createDefaultPublicChatsIfNeeded()
application.createRSSFeedsIfNeeded()
application.lokiPublicChatManager.startPollersIfNeeded()
application.startRSSFeedPollersIfNeeded()
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
servers.forEach { publicChatAPI.setDisplayName(name, it) }
application.updatePublicChatProfileAvatarIfNeeded()
}
}
}

@ -1,85 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.os.AsyncTask
import android.os.Bundle
import android.view.MenuItem
import android.widget.Toast
import org.thoughtcrime.securesms.*
import org.thoughtcrime.securesms.util.DynamicTheme
import org.thoughtcrime.securesms.util.DynamicLanguage
import network.loki.messenger.R
import nl.komponents.kovenant.then
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinkingDelegate {
companion object {
private val TAG = DeviceActivity::class.java.simpleName
}
private val dynamicTheme = DynamicTheme()
private val dynamicLanguage = DynamicLanguage()
private lateinit var deviceListFragment: DeviceListFragment
public override fun onPreCreate() {
dynamicTheme.onCreate(this)
dynamicLanguage.onCreate(this)
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setTitle(R.string.AndroidManifest__linked_devices)
this.deviceListFragment = DeviceListFragment()
this.deviceListFragment.setAddDeviceButtonListener {
DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Master, this)
}
this.deviceListFragment.setHandleDisconnectDevice { devicePublicKey ->
// Purge the device pairing from our database
val ourPublicKey = TextSecurePreferences.getLocalNumber(this)
val database = DatabaseFactory.getLokiAPIDatabase(this)
database.removePairingAuthorisation(ourPublicKey, devicePublicKey)
// Update mapping on the file server
LokiStorageAPI.shared.updateUserDeviceMappings().success {
// Send an unpair request to let the device know that it has been revoked
MessageSender.sendUnpairRequest(this, devicePublicKey)
}
// Refresh the list
this.deviceListFragment.refresh()
Toast.makeText(this, R.string.DeviceListActivity_unlinked_device, Toast.LENGTH_LONG).show()
return@setHandleDisconnectDevice null
}
this.deviceListFragment.setHandleDeviceNameChange { pair ->
DatabaseFactory.getLokiUserDatabase(this).setDisplayName(pair.first, pair.second)
this.deviceListFragment.refresh()
return@setHandleDeviceNameChange null
}
initFragment(android.R.id.content, deviceListFragment, dynamicLanguage.currentLocale)
}
public override fun onResume() {
super.onResume()
dynamicTheme.onResume(this)
dynamicLanguage.onResume(this)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
return false
}
override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {
AsyncTask.execute {
signAndSendPairingAuthorisationMessage(this, pairingAuthorisation)
Util.runOnMain { this.deviceListFragment.refresh() }
}
}
}

@ -4,6 +4,7 @@ import android.content.ContentValues
import android.content.Context
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.*
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol

@ -6,6 +6,9 @@ import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.get
import org.thoughtcrime.securesms.loki.redesign.utilities.getInt
import org.thoughtcrime.securesms.loki.redesign.utilities.insertOrUpdate
import org.whispersystems.signalservice.loki.messaging.LokiMessageDatabaseProtocol
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus

@ -9,6 +9,10 @@ import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.loki.redesign.utilities.get
import org.thoughtcrime.securesms.loki.redesign.utilities.getBase64EncodedData
import org.thoughtcrime.securesms.loki.redesign.utilities.getInt
import org.thoughtcrime.securesms.loki.redesign.utilities.insertOrUpdate
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.IdentityKey

@ -5,6 +5,9 @@ import android.content.Context
import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.get
import org.thoughtcrime.securesms.loki.redesign.utilities.getInt
import org.thoughtcrime.securesms.loki.redesign.utilities.insertOrUpdate
import org.whispersystems.libsignal.state.PreKeyRecord
import org.whispersystems.signalservice.loki.messaging.LokiPreKeyRecordDatabaseProtocol

@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPublicChatPoller
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.LokiPublicChat

@ -7,6 +7,11 @@ import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.get
import org.thoughtcrime.securesms.loki.redesign.utilities.getInt
import org.thoughtcrime.securesms.loki.redesign.utilities.getLong
import org.thoughtcrime.securesms.loki.redesign.utilities.getString
import org.thoughtcrime.securesms.loki.redesign.utilities.insertOrUpdate
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.internal.util.JsonUtil

@ -7,6 +7,8 @@ import android.util.Log
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.get
import org.thoughtcrime.securesms.loki.redesign.utilities.insertOrUpdate
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.messaging.LokiUserDatabaseProtocol

@ -1,85 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.Manifest
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import android.widget.Toast
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.qr.ScanListener
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.DynamicTheme
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.utilities.Analytics
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
class NewConversationActivity : PassphraseRequiredActionBarActivity(), ScanListener {
private val dynamicTheme = DynamicTheme()
override fun onPreCreate() {
dynamicTheme.onCreate(this)
}
override fun onCreate(bundle: Bundle?, isReady: Boolean) {
supportActionBar!!.setTitle(R.string.fragment_new_conversation_title)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
val fragment = NewConversationFragment()
initFragment(android.R.id.content, fragment, null)
}
public override fun onResume() {
super.onResume()
dynamicTheme.onResume(this)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
fun scanQRCode() {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.fragment_scan_qr_code_camera_permission_dialog_message))
.onAllGranted {
val fragment = ScanQRCodeFragment()
fragment.scanListener = this
supportFragmentManager.beginTransaction().replace(android.R.id.content, fragment).addToBackStack(null).commitAllowingStateLoss()
}
.onAnyDenied { Toast.makeText(this, R.string.fragment_scan_qr_code_camera_permission_dialog_message, Toast.LENGTH_SHORT).show() }
.execute()
}
override fun onQrDataFound(hexEncodedPublicKey: String) {
Analytics.shared.track("QR Code Scanned")
startNewConversationIfPossible(hexEncodedPublicKey)
}
fun startNewConversationIfPossible(hexEncodedPublicKey: String) {
if (!PublicKeyValidation.isValid(hexEncodedPublicKey)) { return Toast.makeText(this, R.string.fragment_new_conversation_invalid_public_key_message, Toast.LENGTH_SHORT).show() }
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
// If we try to contact our master device then redirect to note to self
val contactPublicKey = if (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == hexEncodedPublicKey) userHexEncodedPublicKey else hexEncodedPublicKey
val contact = Recipient.from(this, Address.fromSerialized(contactPublicKey), true)
val intent = Intent(this, ConversationActivity::class.java)
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, contact.address)
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA))
intent.setDataAndType(getIntent().data, getIntent().type)
val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(contact)
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread)
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT)
Analytics.shared.track("New Conversation Started")
startActivity(intent)
finish()
}
}

@ -1,35 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_new_conversation.*
import network.loki.messenger.R
class NewConversationFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_new_conversation, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
qrCodeButton.setOnClickListener {
val activity = activity as NewConversationActivity
activity.scanQRCode()
}
nextButton.setOnClickListener {
val activity = activity as NewConversationActivity
val hexEncodedPublicKey = publicKeyEditText.text.toString().trim()
activity.startNewConversationIfPossible(hexEncodedPublicKey)
}
}
override fun onResume() {
super.onResume()
val activity = activity as NewConversationActivity
activity.supportActionBar!!.setTitle(R.string.fragment_new_conversation_title)
}
}

@ -1,39 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.content.Context
import android.support.v7.app.AlertDialog
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_qr_code.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.qr.QrCode
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
object QRCodeDialog {
fun show(context: Context) {
val view = QRCodeView(context)
val dialog = AlertDialog.Builder(context).setView(view).show()
view.onCancel = { dialog.dismiss() }
}
}
class QRCodeView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
var onCancel: (() -> Unit)? = null
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context) : this(context, null)
init {
inflate(context, R.layout.view_qr_code, this)
val hexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(getContext()) ?: TextSecurePreferences.getLocalNumber(context)
val displayMetrics = DisplayMetrics()
ServiceUtil.getWindowManager(context).defaultDisplay.getMetrics(displayMetrics)
val size = displayMetrics.widthPixels - 2 * toPx(96, resources)
val qrCode = QrCode.create(hexEncodedPublicKey, size)
qrCodeImageView.setImageBitmap(qrCode)
cancelButton.setOnClickListener { onCancel?.invoke() }
}
}

@ -1,79 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.content.res.Configuration
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.fragment_scan_qr_code.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.qr.ScanListener
import org.thoughtcrime.securesms.qr.ScanningThread
class ScanQRCodeFragment : Fragment() {
private val scanningThread = ScanningThread()
private var viewCreated = false
var scanListener: ScanListener? = null
set(value) { field = value; scanningThread.setScanListener(scanListener) }
var mode: Mode = Mode.NewConversation
set(value) { field = value; updateDescription(); }
// region Types
enum class Mode { NewConversation, LinkDevice }
// endregion
override fun onCreateView(layoutInflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? {
return layoutInflater.inflate(R.layout.fragment_scan_qr_code, viewGroup, false)
}
override fun onViewCreated(view: View, bundle: Bundle?) {
super.onViewCreated(view, bundle)
viewCreated = true
when (resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> overlayView.orientation = LinearLayout.HORIZONTAL
else -> overlayView.orientation = LinearLayout.VERTICAL
}
updateDescription()
}
override fun onResume() {
super.onResume()
this.scanningThread.setScanListener(scanListener)
this.cameraView.onResume()
this.cameraView.setPreviewCallback(scanningThread)
this.scanningThread.start()
if (activity is AppCompatActivity) {
val activity = activity as AppCompatActivity
activity.supportActionBar?.setTitle(R.string.fragment_scan_qr_code_title)
}
}
override fun onPause() {
super.onPause()
this.cameraView.onPause()
this.scanningThread.stopScanning()
}
override fun onConfigurationChanged(newConfiguration: Configuration) {
super.onConfigurationChanged(newConfiguration)
this.cameraView.onPause()
when (newConfiguration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> overlayView.orientation = LinearLayout.HORIZONTAL
else -> overlayView.orientation = LinearLayout.VERTICAL
}
cameraView.onResume()
cameraView.setPreviewCallback(scanningThread)
}
fun updateDescription() {
if (!viewCreated) { return }
val text = when (mode) {
Mode.NewConversation -> R.string.fragment_scan_qr_code_explanation_new_conversation
Mode.LinkDevice -> R.string.fragment_scan_qr_code_explanation_link_device
}
descriptionTextView.setText(text)
}
}

@ -1,273 +0,0 @@
package org.thoughtcrime.securesms.loki
import android.Manifest
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle
import android.support.v4.app.FragmentManager
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_seed.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.ConversationListActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.qr.ScanListener
import org.thoughtcrime.securesms.util.Hex
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.curve25519.Curve25519
import org.whispersystems.libsignal.util.KeyHelper
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
import org.whispersystems.signalservice.loki.utilities.Analytics
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation
import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
import java.io.File
import java.io.FileOutputStream
class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate, ScanListener {
private lateinit var languageFileDirectory: File
private var mode = Mode.Register
set(newValue) { field = newValue; updateUI() }
private var seed: ByteArray? = null
set(newValue) { field = newValue; updateMnemonic() }
private var mnemonic: String? = null
set(newValue) { field = newValue; updateMnemonicTextView() }
// region Types
enum class Mode { Register, Restore, Link }
// endregion
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_seed)
setUpLanguageFileDirectory()
mnemonicEditText.input.imeOptions = mnemonicEditText.input.imeOptions or 16777216 // Always use incognito keyboard for this
updateSeed()
copyButton.setOnClickListener { copy() }
toggleRegisterModeButton.setOnClickListener { mode = Mode.Register }
toggleRestoreModeButton.setOnClickListener { mode = Mode.Restore }
toggleLinkModeButton.setOnClickListener { mode = Mode.Link }
mainButton.setOnClickListener { handleMainButtonTapped() }
scanQRButton.setOnClickListener {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.fragment_scan_qr_code_camera_permission_dialog_message))
.onAllGranted {
val fragment = ScanQRCodeFragment()
fragment.mode = ScanQRCodeFragment.Mode.LinkDevice
fragment.scanListener = this
supportFragmentManager.beginTransaction().replace(android.R.id.content, fragment).addToBackStack("QR").commitAllowingStateLoss()
publicKeyEditText.clearFocus()
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(publicKeyEditText.windowToken, 0)
}
.onAnyDenied { Toast.makeText(this, R.string.fragment_scan_qr_code_camera_permission_dialog_message, Toast.LENGTH_SHORT).show() }
.execute()
}
Analytics.shared.track("Seed Screen Viewed")
}
// endregion
// region General
private fun setUpLanguageFileDirectory() {
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
val directory = File(applicationInfo.dataDir)
for (language in languages) {
val fileName = "$language.txt"
if (directory.list().contains(fileName)) { continue }
val inputStream = assets.open("mnemonic/$fileName")
val file = File(directory, fileName)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(1024)
while (true) {
val count = inputStream.read(buffer)
if (count < 0) { break }
outputStream.write(buffer, 0, count)
}
inputStream.close()
outputStream.close()
}
languageFileDirectory = directory
}
// endregion
// region Updating
private fun updateSeed() {
val seed = Curve25519.getInstance(Curve25519.BEST).generateSeed(16)
try {
IdentityKeyUtil.generateIdentityKeyPair(this, seed + seed)
} catch (exception: Exception) {
return updateSeed()
}
this.seed = seed
}
private fun updateUI() {
val registerModeVisibility = if (mode == Mode.Register) View.VISIBLE else View.GONE
val restoreModeVisibility = if (mode == Mode.Restore) View.VISIBLE else View.GONE
val linkModeVisibility = if (mode == Mode.Link) View.VISIBLE else View.GONE
seedExplanationTextView1.visibility = registerModeVisibility
mnemonicTextView.visibility = registerModeVisibility
copyButton.visibility = registerModeVisibility
seedExplanationTextView2.visibility = restoreModeVisibility
mnemonicEditText.visibility = restoreModeVisibility
linkExplanationTextView.visibility = linkModeVisibility
publicKeyEditText.visibility = linkModeVisibility
scanQRButton.visibility = linkModeVisibility
toggleRegisterModeButton.visibility = if (mode != Mode.Register) View.VISIBLE else View.GONE
toggleRestoreModeButton.visibility = if (mode != Mode.Restore) View.VISIBLE else View.GONE
toggleLinkModeButton.visibility = if (mode != Mode.Link) View.VISIBLE else View.GONE
val mainButtonTitleID = when (mode) {
Mode.Register -> R.string.activity_key_pair_main_button_title_1
Mode.Restore -> R.string.activity_key_pair_main_button_title_2
Mode.Link -> R.string.activity_key_pair_main_button_title_3
}
mainButton.setText(mainButtonTitleID)
if (mode == Mode.Restore) {
mnemonicEditText.requestFocus()
} else {
mnemonicEditText.clearFocus()
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(mnemonicEditText.windowToken, 0)
}
if (mode == Mode.Link) {
publicKeyEditText.requestFocus()
} else {
publicKeyEditText.clearFocus()
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(publicKeyEditText.windowToken, 0)
}
}
private fun updateMnemonic() {
val hexEncodedSeed = Hex.toStringCondensed(seed)
mnemonic = MnemonicCodec(languageFileDirectory).encode(hexEncodedSeed)
}
private fun updateMnemonicTextView() {
mnemonicTextView.text = mnemonic!!
}
// endregion
// region Interaction
private fun copy() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Mnemonic", mnemonic)
clipboard.primaryClip = clip
Toast.makeText(this, R.string.activity_key_pair_mnemonic_copied_message, Toast.LENGTH_SHORT).show()
}
private fun handleMainButtonTapped() {
var seed: ByteArray
when (mode) {
Mode.Register -> seed = this.seed!!
Mode.Restore -> {
val mnemonic = mnemonicEditText.text.toString()
try {
val hexEncodedSeed = MnemonicCodec(languageFileDirectory).decode(mnemonic)
seed = Hex.fromStringCondensed(hexEncodedSeed)
} catch (e: Exception) {
val message = if (e is MnemonicCodec.DecodingError) e.description else MnemonicCodec.DecodingError.Generic.description
return Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
Mode.Link -> {
val hexEncodedPublicKey = publicKeyEditText.text.trim().toString()
if (!PublicKeyValidation.isValid(hexEncodedPublicKey)) {
return Toast.makeText(this, "Invalid public key", Toast.LENGTH_SHORT).show()
}
seed = this.seed!!
}
}
val hexEncodedSeed = Hex.toStringCondensed(seed)
IdentityKeyUtil.save(this, IdentityKeyUtil.lokiSeedKey, hexEncodedSeed)
if (seed.count() == 16) seed += seed
if (mode == Mode.Restore) {
IdentityKeyUtil.generateIdentityKeyPair(this, seed)
}
val keyPair = IdentityKeyUtil.getIdentityKeyPair(this)
val userHexEncodedPublicKey = keyPair.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(this, registrationID)
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey), keyPair.publicKey,
IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true)
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
if (mode == Mode.Restore) {
TextSecurePreferences.setRestorationTime(this, System.currentTimeMillis())
}
when (mode) {
Mode.Register -> Analytics.shared.track("Seed Created")
Mode.Restore -> Analytics.shared.track("Seed Restored")
Mode.Link -> Analytics.shared.track("Device Linking Attempted")
}
if (mode == Mode.Link) {
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setPromptedPushRegistration(this, true)
val masterHexEncodedPublicKey = publicKeyEditText.text.trim().toString()
val authorisation = PairingAuthorisation(masterHexEncodedPublicKey, userHexEncodedPublicKey).sign(PairingAuthorisation.Type.REQUEST, keyPair.privateKey.serialize())
if (authorisation == null) {
Log.d("Loki", "Failed to sign pairing request.")
resetForRegistration()
return Toast.makeText(application, "Couldn't start device linking process.", Toast.LENGTH_SHORT).show()
}
val application = ApplicationContext.getInstance(this)
application.startLongPollingIfNeeded()
application.setUpP2PAPI()
application.setUpStorageAPIIfNeeded()
DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Slave, this)
AsyncTask.execute {
retryIfNeeded(8) {
sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation)
}
}
} else {
startActivity(Intent(this, DisplayNameActivity::class.java))
finish()
}
}
override fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {
Analytics.shared.track("Device Linked Successfully")
if (pairingAuthorisation.secondaryDevicePublicKey == TextSecurePreferences.getLocalNumber(this)) {
TextSecurePreferences.setMasterHexEncodedPublicKey(this, pairingAuthorisation.primaryDevicePublicKey)
}
startActivity(Intent(this, ConversationListActivity::class.java))
finish()
}
override fun handleDeviceLinkingDialogDismissed() {
resetForRegistration()
}
private fun resetForRegistration() {
IdentityKeyUtil.delete(this, IdentityKeyUtil.lokiSeedKey)
TextSecurePreferences.removeLocalNumber(this)
TextSecurePreferences.setHasSeenWelcomeScreen(this, false)
TextSecurePreferences.setPromptedPushRegistration(this, false)
}
override fun onQrDataFound(data: String?) {
runOnUiThread {
if (data != null && PublicKeyValidation.isValid(data.trim())) {
publicKeyEditText.setText(data.trim())
supportFragmentManager.popBackStackImmediate("QR", FragmentManager.POP_BACK_STACK_INCLUSIVE)
handleMainButtonTapped()
}
}
}
// endregion
}

@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.loki.redesign.activities
import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.devicelist.Device
import org.thoughtcrime.securesms.loki.MnemonicUtilities
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.AsyncLoader
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.LokiStorageAPI

@ -12,7 +12,7 @@ import android.widget.LinearLayout
import kotlinx.android.synthetic.main.dialog_link_device_master_mode.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.MnemonicUtilities
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.loki.redesign.utilities.QRCodeUtilities
import org.thoughtcrime.securesms.loki.toPx
import org.thoughtcrime.securesms.util.TextSecurePreferences

@ -12,7 +12,7 @@ import android.view.View
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.dialog_link_device_slave_mode.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.MnemonicUtilities
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.loki
package org.thoughtcrime.securesms.loki.redesign.messaging
import android.content.Context
import android.content.Intent

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.loki
package org.thoughtcrime.securesms.loki.redesign.messaging
import android.content.Context
import android.content.Intent

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.loki
package org.thoughtcrime.securesms.loki.redesign.messaging
import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.loki
package org.thoughtcrime.securesms.loki.redesign.messaging
import android.content.Context
import android.os.Handler

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.loki
package org.thoughtcrime.securesms.loki.redesign.utilities
import android.content.ContentValues
import net.sqlcipher.Cursor

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.loki
package org.thoughtcrime.securesms.loki.redesign.utilities
import android.content.Context
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec

@ -9,7 +9,7 @@ import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_conversation.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.loki.LokiAPIUtilities.populateUserHexEncodedPublicKeyCacheIfNeeded
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIUtilities.populateUserHexEncodedPublicKeyCacheIfNeeded
import org.thoughtcrime.securesms.loki.redesign.utilities.MentionUtilities.highlightMentions
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.DateUtils

@ -20,10 +20,8 @@ import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.loki.MnemonicUtilities;
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.TextSecurePreferences;

Loading…
Cancel
Save