From 0f5db5aa33d9753babd344d02139a28f140443ea Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 20 Nov 2019 09:50:40 +1100 Subject: [PATCH 01/36] Hook up signal device linking view. --- AndroidManifest.xml | 1441 +++++++++-------- res/layout/device_list_item_view.xml | 4 +- res/values/strings.xml | 3 +- res/xml/preferences.xml | 4 +- .../ApplicationPreferencesActivity.java | 26 +- .../securesms/DeviceListFragment.java | 63 +- .../securesms/DeviceListItem.java | 10 +- .../database/loaders/DeviceListLoader.java | 81 +- .../securesms/devicelist/Device.java | 6 +- .../securesms/loki/DeviceLinkingView.kt | 27 +- .../securesms/loki/LinkedDevicesActivity.kt | 76 + .../securesms/loki/LokiAPIDatabase.kt | 5 + .../securesms/loki/MnemonicUtilities.kt | 36 + 13 files changed, 919 insertions(+), 863 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt create mode 100644 src/org/thoughtcrime/securesms/loki/MnemonicUtilities.kt diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3b18e8b60b..3ed51067ca 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,35 +1,50 @@ + xmlns:tools="http://schemas.android.com/tools" + package="network.loki.messenger"> - + - + - - - - - - - + + + + - - - - - - - - + + --> - - - - - - - + - - - - - + + - + - - - - + - - - - - - - - - - - - - + + + + + + - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - + + + - - - - - - - - + + + - + + + - - - - - + + - - - - - - - + + + + - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/device_list_item_view.xml b/res/layout/device_list_item_view.xml index c6919fb7d5..b5a0432d32 100644 --- a/res/layout/device_list_item_view.xml +++ b/res/layout/device_list_item_view.xml @@ -12,9 +12,11 @@ android:singleLine="true" android:ellipsize="marquee" android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 307c000c62..0c78c98786 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -299,6 +299,7 @@ Unlinking device... Unlinking device Network failed! + Successfully unlinked device Unnamed device @@ -1576,7 +1577,7 @@ Copied to clipboard Share Public Key Show QR Code - Link Device + Linked Device Show Seed Your Seed Copy diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 270c9254d3..5cce165b4e 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -41,8 +41,8 @@ android:title="@string/activity_settings_show_qr_code_button_title" android:icon="@drawable/icon_qr_code"/> - handleDisconnectDevice; @Override public void onCreate(Bundle savedInstanceState) { @@ -82,6 +76,7 @@ public class DeviceListFragment extends ListFragment @Override public void onActivityCreated(Bundle bundle) { super.onActivityCreated(bundle); + this.languageFileDirectory = MnemonicUtilities.getLanguageFileDirectory(getContext()); getLoaderManager().initLoader(0, null, this); getListView().setOnItemClickListener(this); } @@ -90,12 +85,20 @@ public class DeviceListFragment extends ListFragment this.addDeviceButtonListener = listener; } + public void setHandleDisconnectDevice(Function handler) { + this.handleDisconnectDevice = handler; + } + + public void setAddDeviceButtonVisible(boolean visible) { + addDeviceButton.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + } + @Override public @NonNull Loader> onCreateLoader(int id, Bundle args) { empty.setVisibility(View.GONE); progressContainer.setVisibility(View.VISIBLE); - return new DeviceListLoader(getActivity(), accountManager); + return new DeviceListLoader(getActivity(), languageFileDirectory); } @Override @@ -125,7 +128,7 @@ public class DeviceListFragment extends ListFragment @Override public void onItemClick(AdapterView parent, View view, int position, long id) { final String deviceName = ((DeviceListItem)view).getDeviceName(); - final long deviceId = ((DeviceListItem)view).getDeviceId(); + final String deviceId = ((DeviceListItem)view).getDeviceId(); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName)); @@ -134,12 +137,16 @@ public class DeviceListFragment extends ListFragment builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - handleDisconnectDevice(deviceId); + if (handleDisconnectDevice != null) { handleDisconnectDevice.apply(deviceId); } } }); builder.show(); } + public void refresh() { + getLoaderManager().restartLoader(0, null, DeviceListFragment.this); + } + private void handleLoaderFailed() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage(R.string.DeviceListActivity_network_connection_failed); @@ -167,34 +174,6 @@ public class DeviceListFragment extends ListFragment builder.show(); } - private void handleDisconnectDevice(final long deviceId) { - new ProgressDialogAsyncTask(getActivity(), - R.string.DeviceListActivity_unlinking_device_no_ellipsis, - R.string.DeviceListActivity_unlinking_device) - { - @Override - protected Void doInBackground(Void... params) { - try { - accountManager.removeDevice(deviceId); - - ApplicationContext.getInstance(getContext()) - .getJobManager() - .add(new RefreshUnidentifiedDeliveryAbilityJob()); - } catch (IOException e) { - Log.w(TAG, e); - Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show(); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - getLoaderManager().restartLoader(0, null, DeviceListFragment.this); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - @Override public void onClick(View v) { if (addDeviceButtonListener != null) addDeviceButtonListener.onClick(v); diff --git a/src/org/thoughtcrime/securesms/DeviceListItem.java b/src/org/thoughtcrime/securesms/DeviceListItem.java index 47331a549d..9f38c89dd0 100644 --- a/src/org/thoughtcrime/securesms/DeviceListItem.java +++ b/src/org/thoughtcrime/securesms/DeviceListItem.java @@ -15,7 +15,7 @@ import network.loki.messenger.R; public class DeviceListItem extends LinearLayout { - private long deviceId; + private String deviceId; private TextView name; private TextView created; private TextView lastActive; @@ -32,14 +32,15 @@ public class DeviceListItem extends LinearLayout { public void onFinishInflate() { super.onFinishInflate(); this.name = (TextView) findViewById(R.id.name); - this.created = (TextView) findViewById(R.id.created); - this.lastActive = (TextView) findViewById(R.id.active); + // this.created = (TextView) findViewById(R.id.created); + // this.lastActive = (TextView) findViewById(R.id.active); } public void set(Device deviceInfo, Locale locale) { if (TextUtils.isEmpty(deviceInfo.getName())) this.name.setText(R.string.DeviceListItem_unnamed_device); else this.name.setText(deviceInfo.getName()); + /* this.created.setText(getContext().getString(R.string.DeviceListItem_linked_s, DateUtils.getDayPrecisionTimeSpanString(getContext(), locale, @@ -49,11 +50,12 @@ public class DeviceListItem extends LinearLayout { DateUtils.getDayPrecisionTimeSpanString(getContext(), locale, deviceInfo.getLastSeen()))); + */ this.deviceId = deviceInfo.getId(); } - public long getDeviceId() { + public String getDeviceId() { return deviceId; } diff --git a/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java index 7d653dff84..1f661b67db 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java @@ -7,10 +7,13 @@ import android.text.TextUtils; import com.annimon.stream.Stream; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; +import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.devicelist.Device; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.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; @@ -19,7 +22,10 @@ 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; @@ -33,93 +39,42 @@ 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> { private static final String TAG = DeviceListLoader.class.getSimpleName(); + private MnemonicCodec mnemonicCodec; - private final SignalServiceAccountManager accountManager; - - public DeviceListLoader(Context context, SignalServiceAccountManager accountManager) { + public DeviceListLoader(Context context, File languageFileDirectory) { super(context); - this.accountManager = accountManager; + this.mnemonicCodec = new MnemonicCodec(languageFileDirectory); } @Override public List loadInBackground() { try { - List devices = Stream.of(accountManager.getDevices()) - .filter(d -> d.getId() != SignalServiceAddress.DEFAULT_DEVICE_ID) - .map(this::mapToDevice) - .toList(); - + String ourPublicKey = TextSecurePreferences.getLocalNumber(getContext()); + List secondaryDevicePublicKeys = LokiStorageAPI.shared.getSecondaryDevicePublicKeys(ourPublicKey).get(); + List devices = Stream.of(secondaryDevicePublicKeys).map(this::mapToDevice).toList(); Collections.sort(devices, new DeviceComparator()); - return devices; - } catch (IOException e) { + } catch (Exception e) { Log.w(TAG, e); return null; } } - private Device mapToDevice(@NonNull DeviceInfo deviceInfo) { - try { - if (TextUtils.isEmpty(deviceInfo.getName()) || deviceInfo.getName().length() < 4) { - throw new IOException("Invalid DeviceInfo name."); - } - - DeviceName deviceName = DeviceName.parseFrom(Base64.decode(deviceInfo.getName())); - - if (!deviceName.hasCiphertext() || !deviceName.hasEphemeralPublic() || !deviceName.hasSyntheticIv()) { - throw new IOException("Got a DeviceName that wasn't properly populated."); - } - - byte[] syntheticIv = deviceName.getSyntheticIv().toByteArray(); - byte[] cipherText = deviceName.getCiphertext().toByteArray(); - ECPrivateKey identityKey = IdentityKeyUtil.getIdentityKeyPair(getContext()).getPrivateKey(); - ECPublicKey ephemeralPublic = Curve.decodePoint(deviceName.getEphemeralPublic().toByteArray(), 0); - byte[] masterSecret = Curve.calculateAgreement(ephemeralPublic, identityKey); - - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(masterSecret, "HmacSHA256")); - byte[] cipherKeyPart1 = mac.doFinal("cipher".getBytes()); - - mac.init(new SecretKeySpec(cipherKeyPart1, "HmacSHA256")); - byte[] cipherKey = mac.doFinal(syntheticIv); - - Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(new byte[16])); - final byte[] plaintext = cipher.doFinal(cipherText); - - mac.init(new SecretKeySpec(masterSecret, "HmacSHA256")); - byte[] verificationPart1 = mac.doFinal("auth".getBytes()); - - mac.init(new SecretKeySpec(verificationPart1, "HmacSHA256")); - byte[] verificationPart2 = mac.doFinal(plaintext); - byte[] ourSyntheticIv = ByteUtil.trim(verificationPart2, 16); - - if (!MessageDigest.isEqual(ourSyntheticIv, syntheticIv)) { - throw new GeneralSecurityException("The computed syntheticIv didn't match the actual syntheticIv."); - } - - return new Device(deviceInfo.getId(), new String(plaintext), deviceInfo.getCreated(), deviceInfo.getLastSeen()); - - } catch (IOException e) { - Log.w(TAG, "Failed while reading the protobuf.", e); - } catch (GeneralSecurityException | InvalidKeyException e) { - Log.w(TAG, "Failed during decryption.", e); - } - - return new Device(deviceInfo.getId(), deviceInfo.getName(), deviceInfo.getCreated(), deviceInfo.getLastSeen()); + private Device mapToDevice(@NonNull String hexEncodedPublicKey) { + long now = System.currentTimeMillis(); + return new Device(hexEncodedPublicKey, MnemonicUtilities.getFirst3Words(mnemonicCodec, hexEncodedPublicKey), now, now); } private static class DeviceComparator implements Comparator { @Override public int compare(Device lhs, Device rhs) { - if (lhs.getCreated() < rhs.getCreated()) return -1; - else if (lhs.getCreated() != rhs.getCreated()) return 1; - else return 0; + return lhs.getName().compareTo(rhs.getName()); } } } diff --git a/src/org/thoughtcrime/securesms/devicelist/Device.java b/src/org/thoughtcrime/securesms/devicelist/Device.java index 1cf302596d..00cdb6df34 100644 --- a/src/org/thoughtcrime/securesms/devicelist/Device.java +++ b/src/org/thoughtcrime/securesms/devicelist/Device.java @@ -2,19 +2,19 @@ package org.thoughtcrime.securesms.devicelist; public class Device { - private final long id; + private final String id; private final String name; private final long created; private final long lastSeen; - public Device(long id, String name, long created, long lastSeen) { + public Device(String id, String name, long created, long lastSeen) { this.id = id; this.name = name; this.created = created; this.lastSeen = lastSeen; } - public long getId() { + public String getId() { return id; } diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt index b7983cc92b..35c5041d7e 100644 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt @@ -32,31 +32,10 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe private constructor(context: Context) : this(context, null) init { - setUpLanguageFileDirectory() + languageFileDirectory = MnemonicUtilities.getLanguageFileDirectory(context) setUpViewHierarchy() } - private fun setUpLanguageFileDirectory() { - val languages = listOf( "english", "japanese", "portuguese", "spanish" ) - val directory = File(context.applicationInfo.dataDir) - for (language in languages) { - val fileName = "$language.txt" - if (directory.list().contains(fileName)) { continue } - val inputStream = context.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 - } - private fun setUpViewHierarchy() { inflate(context, R.layout.view_device_linking, this) spinner.indeterminateDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN) @@ -72,8 +51,8 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe 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).removing05PrefixIfNeeded() - mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ") + val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) + mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), hexEncodedPublicKey) } authorizeButton.visibility = View.GONE authorizeButton.setOnClickListener { authorizePairing() } diff --git a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt new file mode 100644 index 0000000000..7a61384ad8 --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt @@ -0,0 +1,76 @@ +package org.thoughtcrime.securesms.loki + +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 org.thoughtcrime.securesms.database.DatabaseFactory +import org.thoughtcrime.securesms.sms.MessageSender +import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.whispersystems.signalservice.loki.api.LokiStorageAPI + +class LinkedDevicesActivity : PassphraseRequiredActionBarActivity() { + + 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 { + // TODO: Hook up add device + } + 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() + // Send a background message to let the device know that it has been revoked + MessageSender.sendBackgroundMessage(this, devicePublicKey) + // Refresh the list + refresh() + Toast.makeText(this, R.string.DeviceListActivity_unlinked_device, Toast.LENGTH_LONG).show() + return@setHandleDisconnectDevice null + } + initFragment(android.R.id.content, deviceListFragment, dynamicLanguage.currentLocale) + refresh() + } + + private fun refresh() { + val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) + val isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(this).getPairingAuthorisations(userHexEncodedPublicKey).isEmpty() + this.deviceListFragment.setAddDeviceButtonVisible(isDeviceLinkingEnabled) + this.deviceListFragment.refresh() + } + + 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 + } +} diff --git a/src/org/thoughtcrime/securesms/loki/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/LokiAPIDatabase.kt index 9ac5dc0ca0..85d8f38408 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiAPIDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiAPIDatabase.kt @@ -189,6 +189,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( val database = databaseHelper.readableDatabase database.delete(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey )) } + + fun removePairingAuthorisation(primaryDevicePublicKey: String, secondaryDevicePublicKey: String) { + val database = databaseHelper.readableDatabase + database.delete(pairingAuthorisationCache, "${Companion.primaryDevicePublicKey} = ? OR ${Companion.secondaryDevicePublicKey} = ?", arrayOf( primaryDevicePublicKey, secondaryDevicePublicKey )) + } } // region Convenience diff --git a/src/org/thoughtcrime/securesms/loki/MnemonicUtilities.kt b/src/org/thoughtcrime/securesms/loki/MnemonicUtilities.kt new file mode 100644 index 0000000000..a6e6a64538 --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/MnemonicUtilities.kt @@ -0,0 +1,36 @@ +package org.thoughtcrime.securesms.loki + +import android.content.Context +import org.whispersystems.signalservice.loki.crypto.MnemonicCodec +import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded +import java.io.File +import java.io.FileOutputStream + +object MnemonicUtilities { + @JvmStatic + public fun getLanguageFileDirectory(context: Context): File { + val languages = listOf( "english", "japanese", "portuguese", "spanish" ) + val directory = File(context.applicationInfo.dataDir) + for (language in languages) { + val fileName = "$language.txt" + if (directory.list().contains(fileName)) { continue } + val inputStream = context.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() + } + return directory + } + + @JvmStatic + public fun getFirst3Words(codec: MnemonicCodec, hexEncodedPublicKey: String): String { + return codec.encode(hexEncodedPublicKey.removing05PrefixIfNeeded()).split(" ").slice(0 until 3).joinToString(" ") + } +} \ No newline at end of file From ba78fcb9b115cfde9e92dbfcb2eff8dd899f574d Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 20 Nov 2019 11:44:44 +1100 Subject: [PATCH 02/36] Move device linking logic into LinkedDeviceActivity. --- .../ApplicationPreferencesActivity.java | 1 - .../securesms/DeviceListFragment.java | 15 ++++++++++---- .../loki/DeviceLinkingDialogDelegate.kt | 1 - .../securesms/loki/LinkedDevicesActivity.kt | 20 +++++++++---------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index ead8bc1bac..05f45e52ce 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -353,7 +353,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA case PREFERENCE_CATEGORY_LINKED_DEVICES: Intent intent = new Intent(getActivity(), LinkedDevicesActivity.class); startActivity(intent); - DeviceLinkingDialog.Companion.show(getContext(), DeviceLinkingView.Mode.Master, this); break; case PREFERENCE_CATEGORY_SEED: Analytics.Companion.getShared().track("Seed Modal Shown"); diff --git a/src/org/thoughtcrime/securesms/DeviceListFragment.java b/src/org/thoughtcrime/securesms/DeviceListFragment.java index be3758e6c4..08fd4040b3 100644 --- a/src/org/thoughtcrime/securesms/DeviceListFragment.java +++ b/src/org/thoughtcrime/securesms/DeviceListFragment.java @@ -19,6 +19,7 @@ import android.widget.ListView; import com.melnykov.fab.FloatingActionButton; +import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.loaders.DeviceListLoader; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.devicelist.Device; @@ -69,6 +70,7 @@ public class DeviceListFragment extends ListFragment this.progressContainer = view.findViewById(R.id.progress_container); this.addDeviceButton = ViewUtil.findById(view, R.id.add_device); this.addDeviceButton.setOnClickListener(this); + updateAddDeviceButtonVisibility(); return view; } @@ -89,10 +91,6 @@ public class DeviceListFragment extends ListFragment this.handleDisconnectDevice = handler; } - public void setAddDeviceButtonVisible(boolean visible) { - addDeviceButton.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - } - @Override public @NonNull Loader> onCreateLoader(int id, Bundle args) { empty.setVisibility(View.GONE); @@ -144,9 +142,18 @@ public class DeviceListFragment extends ListFragment } public void refresh() { + updateAddDeviceButtonVisibility(); getLoaderManager().restartLoader(0, null, DeviceListFragment.this); } + private void updateAddDeviceButtonVisibility() { + if (addDeviceButton != null) { + String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext()); + boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(getContext()).getPairingAuthorisations(userHexEncodedPublicKey).isEmpty(); + addDeviceButton.setVisibility(isDeviceLinkingEnabled ? View.VISIBLE : View.INVISIBLE); + } + } + private void handleLoaderFailed() { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage(R.string.DeviceListActivity_network_connection_failed); diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt index 48493e25e2..5083b013b3 100644 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.loki import org.whispersystems.signalservice.loki.api.PairingAuthorisation interface DeviceLinkingDialogDelegate { - fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { } fun handleDeviceLinkingDialogDismissed() { } fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { } diff --git a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt index 7a61384ad8..a0fd9ae183 100644 --- a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt @@ -11,8 +11,9 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.signalservice.loki.api.LokiStorageAPI +import org.whispersystems.signalservice.loki.api.PairingAuthorisation -class LinkedDevicesActivity : PassphraseRequiredActionBarActivity() { +class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinkingDialogDelegate { companion object { private val TAG = DeviceActivity::class.java.simpleName @@ -33,7 +34,7 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity() { supportActionBar?.setTitle(R.string.AndroidManifest__linked_devices) this.deviceListFragment = DeviceListFragment() this.deviceListFragment.setAddDeviceButtonListener { - // TODO: Hook up add device + DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Master, this) } this.deviceListFragment.setHandleDisconnectDevice { devicePublicKey -> // Purge the device pairing from our database @@ -45,19 +46,11 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity() { // Send a background message to let the device know that it has been revoked MessageSender.sendBackgroundMessage(this, devicePublicKey) // Refresh the list - refresh() + this.deviceListFragment.refresh() Toast.makeText(this, R.string.DeviceListActivity_unlinked_device, Toast.LENGTH_LONG).show() return@setHandleDisconnectDevice null } initFragment(android.R.id.content, deviceListFragment, dynamicLanguage.currentLocale) - refresh() - } - - private fun refresh() { - val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) - val isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(this).getPairingAuthorisations(userHexEncodedPublicKey).isEmpty() - this.deviceListFragment.setAddDeviceButtonVisible(isDeviceLinkingEnabled) - this.deviceListFragment.refresh() } public override fun onResume() { @@ -73,4 +66,9 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity() { } return false } + + override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { + signAndSendPairingAuthorisationMessage(this, pairingAuthorisation) + this.deviceListFragment.refresh() + } } From 8c2d0751478203da6f3446c2c9263aa7f75c840c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 20 Nov 2019 11:45:54 +1100 Subject: [PATCH 03/36] Revert changes made in ApplicationPreferencesActivity. --- .../ApplicationPreferencesActivity.java | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 05f45e52ce..0d2438d79b 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -173,15 +173,15 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS)); */ this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS) - .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_NOTIFICATIONS)); + .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS)); this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION) - .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_APP_PROTECTION)); + .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APP_PROTECTION)); /* this.findPreference(PREFERENCE_CATEGORY_APPEARANCE) .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE)); */ this.findPreference(PREFERENCE_CATEGORY_CHATS) - .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_CHATS)); + .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS)); /* this.findPreference(PREFERENCE_CATEGORY_DEVICES) .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES)); @@ -189,18 +189,18 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED)); */ this.findPreference(PREFERENCE_CATEGORY_PUBLIC_KEY) - .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_PUBLIC_KEY)); + .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_PUBLIC_KEY)); this.findPreference(PREFERENCE_CATEGORY_QR_CODE) - .setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_QR_CODE)); + .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_QR_CODE)); Preference linkDevicesPreference = this.findPreference(PREFERENCE_CATEGORY_LINKED_DEVICES); linkDevicesPreference.setVisible(isMasterDevice); - linkDevicesPreference.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_LINKED_DEVICES)); + linkDevicesPreference.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_LINKED_DEVICES)); Preference seedPreference = this.findPreference(PREFERENCE_CATEGORY_SEED); // Hide if this is a slave device seedPreference.setVisible(isMasterDevice); - seedPreference.setOnPreferenceClickListener(new CategoryClickListener(getContext(), (PREFERENCE_CATEGORY_SEED))); + seedPreference.setOnPreferenceClickListener(new CategoryClickListener((PREFERENCE_CATEGORY_SEED))); if (VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { tintIcons(getActivity()); @@ -293,12 +293,10 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA this.findPreference(PREFERENCE_CATEGORY_SEED).setIcon(seed); } - private class CategoryClickListener implements Preference.OnPreferenceClickListener, DeviceLinkingDialogDelegate { + private class CategoryClickListener implements Preference.OnPreferenceClickListener { private String category; - private Context context; - CategoryClickListener(Context context,String category) { - this.context = context; + CategoryClickListener(String category) { this.category = category; } @@ -395,12 +393,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA return true; } - - @Override public void sendPairingAuthorizedMessage(@NotNull PairingAuthorisation pairingAuthorisation) { - AsyncTask.execute(() -> MultiDeviceUtilities.signAndSendPairingAuthorisationMessage(context, pairingAuthorisation)); - } - @Override public void handleDeviceLinkAuthorized(@NotNull PairingAuthorisation pairingAuthorisation) {} - @Override public void handleDeviceLinkingDialogDismissed() {} } private class ProfileClickListener implements Preference.OnPreferenceClickListener { From dc40ff0548fe6b2f0eee8071c481f80cd6d0c744 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 20 Nov 2019 11:59:18 +1100 Subject: [PATCH 04/36] Only update display name from received message if the user is not one of our devices. --- .../thoughtcrime/securesms/jobs/PushDecryptJob.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 94a0095349..4c94ae0594 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -311,13 +311,17 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Loki - Store the sender display name if needed Optional rawSenderDisplayName = content.senderDisplayName; if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) { - setDisplayName(envelope.getSource(), rawSenderDisplayName.get()); - - // If we got a name from our primary device then we also set that + // If we got a name from our primary device then we set our profile name to match it String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); if (ourPrimaryDevice != null && envelope.getSource().equals(ourPrimaryDevice)) { TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get()); } + + // If we receive a message from our device then don't set the display name in the database (as we probably have a alias set for them) + MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success(isOneOfOurDevice -> { + if (!isOneOfOurDevice) { setDisplayName(envelope.getSource(), rawSenderDisplayName.get()); } + return Unit.INSTANCE; + }); } // TODO: Deleting the display name From 28a04f13afc08ea3087f906089a365c1120c58cb Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 20 Nov 2019 14:55:42 +1100 Subject: [PATCH 05/36] Refactor. Add text field to authorise dialog. --- res/layout/view_device_linking.xml | 13 ++++++ res/values/strings.xml | 1 + .../ApplicationPreferencesActivity.java | 8 ---- .../securesms/loki/DeviceLinkingDelegate.kt | 33 ++++++++++++++ .../securesms/loki/DeviceLinkingDialog.kt | 18 +++----- .../loki/DeviceLinkingDialogDelegate.kt | 9 ---- .../securesms/loki/DeviceLinkingView.kt | 43 +++++++++++++++---- .../loki/DeviceLinkingViewDelegate.kt | 10 ----- .../securesms/loki/LinkedDevicesActivity.kt | 14 ++++-- .../securesms/loki/SeedActivity.kt | 2 +- 10 files changed, 98 insertions(+), 53 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt delete mode 100644 src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt delete mode 100644 src/org/thoughtcrime/securesms/loki/DeviceLinkingViewDelegate.kt diff --git a/res/layout/view_device_linking.xml b/res/layout/view_device_linking.xml index b16429e9b1..e9ecbc4229 100644 --- a/res/layout/view_device_linking.xml +++ b/res/layout/view_device_linking.xml @@ -1,6 +1,7 @@ + + Your device has been linked successfully Authorize Cancel + Device Name (Optional) Scan QR Code Scan the QR code of the person you\'d like to securely message. They can find their QR code by going into Loki Messenger\'s in-app settings and clicking \"Show QR Code\". diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java index 0d2438d79b..0bed4c6adb 100644 --- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java +++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.os.AsyncTask; import android.os.Build; import android.os.Build.VERSION; import android.os.Bundle; @@ -40,14 +39,8 @@ import android.support.v7.app.AlertDialog; import android.support.v7.preference.Preference; import android.widget.Toast; -import org.jetbrains.annotations.NotNull; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.loki.DeviceLinkingDialog; -import org.thoughtcrime.securesms.loki.DeviceLinkingDialogDelegate; -import org.thoughtcrime.securesms.loki.DeviceLinkingView; import org.thoughtcrime.securesms.loki.LinkedDevicesActivity; -import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.QRCodeDialog; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment; @@ -58,7 +51,6 @@ import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.TextSecurePreferences; -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.SerializationKt; diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt new file mode 100644 index 0000000000..d7509a5c42 --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt @@ -0,0 +1,33 @@ +package org.thoughtcrime.securesms.loki + +import org.whispersystems.signalservice.loki.api.PairingAuthorisation + +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) } + } + + override fun setDeviceDisplayName(hexEncodedPublicKey: String, displayName: String) { + for (delegate in validDelegates) { delegate.setDeviceDisplayName(hexEncodedPublicKey, displayName) } + } + } + } + } + + fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {} + fun handleDeviceLinkingDialogDismissed() {} + fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {} + fun setDeviceDisplayName(hexEncodedPublicKey: String, displayName: String) {} +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialog.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialog.kt index d5faaaca16..0b6bede2eb 100644 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialog.kt +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialog.kt @@ -8,13 +8,12 @@ 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: DeviceLinkingDialogDelegate?) : DeviceLinkingViewDelegate, DeviceLinkingSessionListener { +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: DeviceLinkingDialogDelegate?): DeviceLinkingDialog { + fun show(context: Context, mode: DeviceLinkingView.Mode, delegate: DeviceLinkingDelegate?): DeviceLinkingDialog { val dialog = DeviceLinkingDialog(context, mode, delegate) dialog.show() return dialog @@ -22,8 +21,10 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv } private fun show() { - view = DeviceLinkingView(context, mode, this) + 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) @@ -35,20 +36,11 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv dialog.dismiss() } - override fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { - delegate?.handleDeviceLinkAuthorized(pairingAuthorisation) - } - override fun handleDeviceLinkingDialogDismissed() { if (mode == DeviceLinkingView.Mode.Master && view.pairingAuthorisation != null) { val authorisation = view.pairingAuthorisation!! DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorisation.secondaryDevicePublicKey) } - delegate?.handleDeviceLinkingDialogDismissed() - } - - override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { - delegate?.sendPairingAuthorizedMessage(pairingAuthorisation) } override fun requestUserAuthorization(authorisation: PairingAuthorisation) { diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt deleted file mode 100644 index 5083b013b3..0000000000 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDialogDelegate.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.thoughtcrime.securesms.loki - -import org.whispersystems.signalservice.loki.api.PairingAuthorisation - -interface DeviceLinkingDialogDelegate { - fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { } - fun handleDeviceLinkingDialogDismissed() { } - fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { } -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt index 35c5041d7e..e797a99e06 100644 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt @@ -4,6 +4,8 @@ import android.content.Context import android.graphics.Color import android.graphics.PorterDuff import android.os.Handler +import android.text.Editable +import android.text.TextWatcher import android.util.AttributeSet import android.view.View import android.widget.LinearLayout @@ -12,12 +14,10 @@ import network.loki.messenger.R import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.crypto.MnemonicCodec -import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded import java.io.File -import java.io.FileOutputStream -class DeviceLinkingView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, private val mode: Mode, private var delegate: DeviceLinkingViewDelegate) : LinearLayout(context, attrs, defStyleAttr) { - private lateinit var languageFileDirectory: 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 @@ -27,12 +27,11 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe // endregion // region Lifecycle - constructor(context: Context, mode: Mode, delegate: DeviceLinkingViewDelegate) : this(context, null, 0, mode, delegate) - private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0, Mode.Master, object : DeviceLinkingViewDelegate { }) // Just pass in a dummy mode + 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 { - languageFileDirectory = MnemonicUtilities.getLanguageFileDirectory(context) setUpViewHierarchy() } @@ -57,6 +56,30 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe authorizeButton.visibility = View.GONE authorizeButton.setOnClickListener { authorizePairing() } cancelButton.setOnClickListener { cancel() } + + deviceNameText.visibility = View.GONE + deviceNameText.input.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable?) { + val string = s?.toString() ?: "" + when { + string.trim().length > 30 -> { + deviceNameText.input.error = "Too Long" + enableAuthorizeButton(false) + } + else -> { + deviceNameText.input.error = null + enableAuthorizeButton(true) + } + } + } + }) + } + + private fun enableAuthorizeButton(enabled: Boolean) { + authorizeButton.isEnabled = enabled + authorizeButton.alpha = if (enabled) 1f else 0.5f } // endregion @@ -71,9 +94,10 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe 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 - val hexEncodedPublicKey = pairingAuthorisation.secondaryDevicePublicKey.removing05PrefixIfNeeded() - mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ") + mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), pairingAuthorisation.secondaryDevicePublicKey) authorizeButton.visibility = View.VISIBLE + deviceNameText.visibility = View.VISIBLE + enableAuthorizeButton(true) } fun onDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { @@ -105,6 +129,7 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe if (mode != Mode.Master || pairingAuthorisation == null) { return; } delegate.sendPairingAuthorizedMessage(pairingAuthorisation) delegate.handleDeviceLinkAuthorized(pairingAuthorisation) + delegate.setDeviceDisplayName(pairingAuthorisation.secondaryDevicePublicKey, deviceNameText.text.toString().trim()) dismiss?.invoke() } diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingViewDelegate.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingViewDelegate.kt deleted file mode 100644 index 56c77bd9c7..0000000000 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingViewDelegate.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.thoughtcrime.securesms.loki - -import org.whispersystems.signalservice.loki.api.PairingAuthorisation - -interface DeviceLinkingViewDelegate { - - fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { } - fun handleDeviceLinkingDialogDismissed() { } - fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { } -} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt index a0fd9ae183..4414101900 100644 --- a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.loki +import android.os.AsyncTask import android.os.Bundle import android.view.MenuItem import android.widget.Toast @@ -10,10 +11,11 @@ import network.loki.messenger.R 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(), DeviceLinkingDialogDelegate { +class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinkingDelegate { companion object { private val TAG = DeviceActivity::class.java.simpleName @@ -68,7 +70,13 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinki } override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { - signAndSendPairingAuthorisationMessage(this, pairingAuthorisation) - this.deviceListFragment.refresh() + AsyncTask.execute { + signAndSendPairingAuthorisationMessage(this, pairingAuthorisation) + Util.runOnMain { this.deviceListFragment.refresh() } + } + } + + override fun setDeviceDisplayName(hexEncodedPublicKey: String, displayName: String) { + DatabaseFactory.getLokiUserDatabase(this).setDisplayName(hexEncodedPublicKey, displayName) } } diff --git a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt index 87954908a6..04c788bf5e 100644 --- a/src/org/thoughtcrime/securesms/loki/SeedActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/SeedActivity.kt @@ -32,7 +32,7 @@ import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import java.io.File import java.io.FileOutputStream -class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate { +class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate { private lateinit var languageFileDirectory: File private var mode = Mode.Register set(newValue) { field = newValue; updateUI() } From e72d5502ce28fef86f0cf60ad7bedf8243ed0457 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 20 Nov 2019 15:21:54 +1100 Subject: [PATCH 06/36] Show our device words in preferences if we're a secondary device. --- res/values/strings.xml | 2 +- .../preferences/widgets/ProfilePreference.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e737cc857f..9321c1f6c7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1573,7 +1573,7 @@ Looks like you don\'t have any conversations yet. Get started by messaging a friend. - Secondary device + Linked device (%s) Copied to clipboard Share Public Key Show QR Code diff --git a/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java b/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java index 63b82c4bf7..5cd135a943 100644 --- a/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java +++ b/src/org/thoughtcrime/securesms/preferences/widgets/ProfilePreference.java @@ -20,7 +20,9 @@ import android.widget.Toast; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable; +import org.thoughtcrime.securesms.loki.MnemonicUtilities; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.signalservice.loki.crypto.MnemonicCodec; import network.loki.messenger.R; @@ -31,6 +33,7 @@ public class ProfilePreference extends Preference { private TextView profileNameView; private TextView profileNumberView; private TextView profileTagView; + private String ourDeviceWords; @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -127,6 +130,12 @@ public class ProfilePreference extends Preference { profileNumberView.setText(localAddress.toPhoneString()); profileTagView.setVisibility(primaryDevicePublicKey == null ? View.GONE : View.VISIBLE); - profileTagView.setText(R.string.activity_settings_secondary_device_tag); + if (primaryDevicePublicKey != null && ourDeviceWords == null) { + MnemonicCodec codec = new MnemonicCodec(MnemonicUtilities.getLanguageFileDirectory(context)); + ourDeviceWords = MnemonicUtilities.getFirst3Words(codec, userHexEncodedPublicKey); + } + + String tag = context.getResources().getString(R.string.activity_settings_linked_device_tag); + profileTagView.setText(String.format(tag, ourDeviceWords != null ? ourDeviceWords : "-")); } } From c3bda57ac10b4c1f530b5b130b8aa5ae10cc3b06 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 20 Nov 2019 15:47:28 +1100 Subject: [PATCH 07/36] Show name and short id in linked device screen. --- res/layout/device_list_item_view.xml | 54 ++++++++++--------- .../securesms/DeviceListItem.java | 27 +++------- .../database/loaders/DeviceListLoader.java | 7 ++- .../securesms/devicelist/Device.java | 28 +++------- 4 files changed, 50 insertions(+), 66 deletions(-) diff --git a/res/layout/device_list_item_view.xml b/res/layout/device_list_item_view.xml index b5a0432d32..52bb83aad7 100644 --- a/res/layout/device_list_item_view.xml +++ b/res/layout/device_list_item_view.xml @@ -6,30 +6,36 @@ android:layout_marginStart="16dp" android:layout_marginEnd="16dp"> - + - \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/DeviceListItem.java b/src/org/thoughtcrime/securesms/DeviceListItem.java index 9f38c89dd0..83d2f2a7d9 100644 --- a/src/org/thoughtcrime/securesms/DeviceListItem.java +++ b/src/org/thoughtcrime/securesms/DeviceListItem.java @@ -17,8 +17,7 @@ public class DeviceListItem extends LinearLayout { private String deviceId; private TextView name; - private TextView created; - private TextView lastActive; + private TextView shortId; public DeviceListItem(Context context) { super(context); @@ -31,28 +30,16 @@ public class DeviceListItem extends LinearLayout { @Override public void onFinishInflate() { super.onFinishInflate(); - this.name = (TextView) findViewById(R.id.name); - // this.created = (TextView) findViewById(R.id.created); - // this.lastActive = (TextView) findViewById(R.id.active); + this.name = (TextView) findViewById(R.id.name); + this.shortId = (TextView) findViewById(R.id.shortId); } public void set(Device deviceInfo, Locale locale) { - if (TextUtils.isEmpty(deviceInfo.getName())) this.name.setText(R.string.DeviceListItem_unnamed_device); - else this.name.setText(deviceInfo.getName()); - - /* - this.created.setText(getContext().getString(R.string.DeviceListItem_linked_s, - DateUtils.getDayPrecisionTimeSpanString(getContext(), - locale, - deviceInfo.getCreated()))); - - this.lastActive.setText(getContext().getString(R.string.DeviceListItem_last_active_s, - DateUtils.getDayPrecisionTimeSpanString(getContext(), - locale, - deviceInfo.getLastSeen()))); - */ - this.deviceId = deviceInfo.getId(); + boolean hasName = !TextUtils.isEmpty(deviceInfo.getName()); + this.name.setText(hasName ? deviceInfo.getName() : deviceInfo.getShortId()); + this.shortId.setText(deviceInfo.getShortId()); + this.shortId.setVisibility(hasName ? VISIBLE : GONE); } public String getDeviceId() { diff --git a/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java index 1f661b67db..6961feb8a1 100644 --- a/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java +++ b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java @@ -7,8 +7,10 @@ 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.util.AsyncLoader; @@ -66,8 +68,9 @@ public class DeviceListLoader extends AsyncLoader> { } private Device mapToDevice(@NonNull String hexEncodedPublicKey) { - long now = System.currentTimeMillis(); - return new Device(hexEncodedPublicKey, MnemonicUtilities.getFirst3Words(mnemonicCodec, hexEncodedPublicKey), now, now); + String shortId = MnemonicUtilities.getFirst3Words(mnemonicCodec, hexEncodedPublicKey); + String name = DatabaseFactory.getLokiUserDatabase(getContext()).getDisplayName(hexEncodedPublicKey); + return new Device(hexEncodedPublicKey, shortId, name); } private static class DeviceComparator implements Comparator { diff --git a/src/org/thoughtcrime/securesms/devicelist/Device.java b/src/org/thoughtcrime/securesms/devicelist/Device.java index 00cdb6df34..e6f4f13967 100644 --- a/src/org/thoughtcrime/securesms/devicelist/Device.java +++ b/src/org/thoughtcrime/securesms/devicelist/Device.java @@ -2,31 +2,19 @@ package org.thoughtcrime.securesms.devicelist; public class Device { - private final String id; + private final String id; + private final String shortId; private final String name; - private final long created; - private final long lastSeen; - public Device(String id, String name, long created, long lastSeen) { - this.id = id; - this.name = name; - this.created = created; - this.lastSeen = lastSeen; + public Device(String id, String shortId, String name) { + this.id = id; + this.shortId = shortId; + this.name = name; } public String getId() { return id; } - - public String getName() { - return name; - } - - public long getCreated() { - return created; - } - - public long getLastSeen() { - return lastSeen; - } + public String getShortId() { return shortId; } + public String getName() { return name; } } From d96664cb6a8b23023216c6923a2543b3d311d0fb Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 20 Nov 2019 16:08:03 +1100 Subject: [PATCH 08/36] Remove textbox in linking dialog. --- res/layout/device_list_item_view.xml | 56 ++++++++----------- res/layout/view_device_linking.xml | 12 ---- res/values/strings.xml | 1 - .../securesms/loki/DeviceLinkingDelegate.kt | 6 +- .../securesms/loki/DeviceLinkingView.kt | 27 --------- .../securesms/loki/LinkedDevicesActivity.kt | 4 -- 6 files changed, 24 insertions(+), 82 deletions(-) diff --git a/res/layout/device_list_item_view.xml b/res/layout/device_list_item_view.xml index 52bb83aad7..11406f5f32 100644 --- a/res/layout/device_list_item_view.xml +++ b/res/layout/device_list_item_view.xml @@ -2,40 +2,30 @@ - - + + android:layout_marginTop="8dp" + android:ellipsize="marquee" + android:singleLine="true" + android:text="Name" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?attr/conversation_list_item_contact_color" + android:textSize="18sp" /> - - - - + \ No newline at end of file diff --git a/res/layout/view_device_linking.xml b/res/layout/view_device_linking.xml index e9ecbc4229..1a793dd83f 100644 --- a/res/layout/view_device_linking.xml +++ b/res/layout/view_device_linking.xml @@ -45,18 +45,6 @@ android:textAlignment="center" android:text="word word word" /> - - Your device has been linked successfully Authorize Cancel - Device Name (Optional) Scan QR Code Scan the QR code of the person you\'d like to securely message. They can find their QR code by going into Loki Messenger\'s in-app settings and clicking \"Show QR Code\". diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt index d7509a5c42..3a86bdf245 100644 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt @@ -2,6 +2,7 @@ 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 { @@ -18,10 +19,6 @@ interface DeviceLinkingDelegate { override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { for (delegate in validDelegates) { delegate.sendPairingAuthorizedMessage(pairingAuthorisation) } } - - override fun setDeviceDisplayName(hexEncodedPublicKey: String, displayName: String) { - for (delegate in validDelegates) { delegate.setDeviceDisplayName(hexEncodedPublicKey, displayName) } - } } } } @@ -29,5 +26,4 @@ interface DeviceLinkingDelegate { fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {} fun handleDeviceLinkingDialogDismissed() {} fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {} - fun setDeviceDisplayName(hexEncodedPublicKey: String, displayName: String) {} } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt b/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt index e797a99e06..393ad58398 100644 --- a/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt +++ b/src/org/thoughtcrime/securesms/loki/DeviceLinkingView.kt @@ -56,30 +56,6 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe authorizeButton.visibility = View.GONE authorizeButton.setOnClickListener { authorizePairing() } cancelButton.setOnClickListener { cancel() } - - deviceNameText.visibility = View.GONE - deviceNameText.input.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} - override fun afterTextChanged(s: Editable?) { - val string = s?.toString() ?: "" - when { - string.trim().length > 30 -> { - deviceNameText.input.error = "Too Long" - enableAuthorizeButton(false) - } - else -> { - deviceNameText.input.error = null - enableAuthorizeButton(true) - } - } - } - }) - } - - private fun enableAuthorizeButton(enabled: Boolean) { - authorizeButton.isEnabled = enabled - authorizeButton.alpha = if (enabled) 1f else 0.5f } // endregion @@ -96,8 +72,6 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe mnemonicTextView.visibility = View.VISIBLE mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), pairingAuthorisation.secondaryDevicePublicKey) authorizeButton.visibility = View.VISIBLE - deviceNameText.visibility = View.VISIBLE - enableAuthorizeButton(true) } fun onDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { @@ -129,7 +103,6 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe if (mode != Mode.Master || pairingAuthorisation == null) { return; } delegate.sendPairingAuthorizedMessage(pairingAuthorisation) delegate.handleDeviceLinkAuthorized(pairingAuthorisation) - delegate.setDeviceDisplayName(pairingAuthorisation.secondaryDevicePublicKey, deviceNameText.text.toString().trim()) dismiss?.invoke() } diff --git a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt index 4414101900..fd1568733d 100644 --- a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt @@ -75,8 +75,4 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinki Util.runOnMain { this.deviceListFragment.refresh() } } } - - override fun setDeviceDisplayName(hexEncodedPublicKey: String, displayName: String) { - DatabaseFactory.getLokiUserDatabase(this).setDisplayName(hexEncodedPublicKey, displayName) - } } From b61b4c581dc9240b760b5c57d80974f81d6ad54d Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 21 Nov 2019 10:32:26 +1100 Subject: [PATCH 09/36] Add bottom sheet dialog --- res/drawable/ic_edit_white_24dp.xml | 5 ++++ .../ic_phonelink_erase_white_24dp.xml | 5 ++++ .../fragment_device_list_bottom_sheet.xml | 22 +++++++++++++++++ res/values/dimens.xml | 6 +++++ res/values/strings.xml | 3 +++ res/values/styles.xml | 11 +++++++++ res/values/themes.xml | 2 ++ .../securesms/DeviceListFragment.java | 6 +++++ .../loki/DeviceListBottomSheetFragment.kt | 24 +++++++++++++++++++ 9 files changed, 84 insertions(+) create mode 100644 res/drawable/ic_edit_white_24dp.xml create mode 100644 res/drawable/ic_phonelink_erase_white_24dp.xml create mode 100644 res/layout/fragment_device_list_bottom_sheet.xml create mode 100644 src/org/thoughtcrime/securesms/loki/DeviceListBottomSheetFragment.kt diff --git a/res/drawable/ic_edit_white_24dp.xml b/res/drawable/ic_edit_white_24dp.xml new file mode 100644 index 0000000000..46462b5726 --- /dev/null +++ b/res/drawable/ic_edit_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/res/drawable/ic_phonelink_erase_white_24dp.xml b/res/drawable/ic_phonelink_erase_white_24dp.xml new file mode 100644 index 0000000000..dc5c1b0260 --- /dev/null +++ b/res/drawable/ic_phonelink_erase_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/res/layout/fragment_device_list_bottom_sheet.xml b/res/layout/fragment_device_list_bottom_sheet.xml new file mode 100644 index 0000000000..dca86bdbe8 --- /dev/null +++ b/res/layout/fragment_device_list_bottom_sheet.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 0ddbef44e4..4c4c74c055 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -115,4 +115,10 @@ -150dp + 16dp + 24dp + 16sp + 16dp + 56dp + diff --git a/res/values/strings.xml b/res/values/strings.xml index 74dc96a244..4e6d872cb4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1643,5 +1643,8 @@ Copy public key Add Public Chat + + Edit display name + Unlink device diff --git a/res/values/styles.xml b/res/values/styles.xml index a9aaf9d788..ecf5dc23a5 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -242,4 +242,15 @@ @color/white + + diff --git a/res/values/themes.xml b/res/values/themes.xml index 178c030731..f285cde977 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -133,6 +133,7 @@ @color/loki_darkest_gray @style/AppCompatAlertDialogStyleLight @style/AppCompatDialogStyleLight + @style/Theme.MaterialComponents.Light.BottomSheetDialog @color/white @@ -317,6 +318,7 @@ @color/loki_darkest_gray @style/AppCompatAlertDialogStyleDark @style/AppCompatDialogStyleDark + @style/Theme.MaterialComponents.BottomSheetDialog @color/gray95 @drawable/ic_document_small_dark diff --git a/src/org/thoughtcrime/securesms/DeviceListFragment.java b/src/org/thoughtcrime/securesms/DeviceListFragment.java index 08fd4040b3..8592c3fef2 100644 --- a/src/org/thoughtcrime/securesms/DeviceListFragment.java +++ b/src/org/thoughtcrime/securesms/DeviceListFragment.java @@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; 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.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; @@ -128,6 +129,9 @@ public class DeviceListFragment extends ListFragment final String deviceName = ((DeviceListItem)view).getDeviceName(); final String deviceId = ((DeviceListItem)view).getDeviceId(); + DeviceListBottomSheetFragment fragment = new DeviceListBottomSheetFragment(); + fragment.show(getFragmentManager(), fragment.getTag()); + /* AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName)); builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive); @@ -139,6 +143,8 @@ public class DeviceListFragment extends ListFragment } }); builder.show(); + + */ } public void refresh() { diff --git a/src/org/thoughtcrime/securesms/loki/DeviceListBottomSheetFragment.kt b/src/org/thoughtcrime/securesms/loki/DeviceListBottomSheetFragment.kt new file mode 100644 index 0000000000..013b48a66d --- /dev/null +++ b/src/org/thoughtcrime/securesms/loki/DeviceListBottomSheetFragment.kt @@ -0,0 +1,24 @@ +package org.thoughtcrime.securesms.loki + +import android.os.Bundle +import android.support.design.widget.BottomSheetDialogFragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +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 + + 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() } + } +} \ No newline at end of file From c66786e0f1f3448432f1a9bd17d3c43dd60662cc Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 21 Nov 2019 10:50:33 +1100 Subject: [PATCH 10/36] Allow users to edit device name. --- .../fragment_device_list_bottom_sheet.xml | 2 +- res/values/strings.xml | 3 +- .../securesms/DeviceListFragment.java | 59 ++++++++++++++----- .../securesms/DeviceListItem.java | 4 ++ .../securesms/loki/LinkedDevicesActivity.kt | 5 ++ 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/res/layout/fragment_device_list_bottom_sheet.xml b/res/layout/fragment_device_list_bottom_sheet.xml index dca86bdbe8..5c1092fa65 100644 --- a/res/layout/fragment_device_list_bottom_sheet.xml +++ b/res/layout/fragment_device_list_bottom_sheet.xml @@ -11,7 +11,7 @@ android:id="@+id/editDisplayNameText" style="@style/ActionItem" android:drawableStart="@drawable/ic_edit_white_24dp" - android:text="@string/fragment_device_list_edit_display_name_title"/> + android:text="@string/fragment_device_list_edit_device_name_title"/> Unlinking device Network failed! Successfully unlinked device + Edit device name Unnamed device @@ -1644,7 +1645,7 @@ Add Public Chat - Edit display name + Edit device name Unlink device diff --git a/src/org/thoughtcrime/securesms/DeviceListFragment.java b/src/org/thoughtcrime/securesms/DeviceListFragment.java index 8592c3fef2..2490012f23 100644 --- a/src/org/thoughtcrime/securesms/DeviceListFragment.java +++ b/src/org/thoughtcrime/securesms/DeviceListFragment.java @@ -15,6 +15,7 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.EditText; import android.widget.ListView; import com.melnykov.fab.FloatingActionButton; @@ -34,6 +35,8 @@ import java.util.Locale; import org.whispersystems.libsignal.util.guava.Function; +import kotlin.Pair; +import kotlin.Unit; import network.loki.messenger.R; public class DeviceListFragment extends ListFragment @@ -50,6 +53,7 @@ public class DeviceListFragment extends ListFragment private FloatingActionButton addDeviceButton; private Button.OnClickListener addDeviceButtonListener; private Function handleDisconnectDevice; + private Function, Void> handleDeviceNameChange; @Override public void onCreate(Bundle savedInstanceState) { @@ -92,6 +96,10 @@ public class DeviceListFragment extends ListFragment this.handleDisconnectDevice = handler; } + public void setHandleDeviceNameChange(Function, Void> handler) { + this.handleDeviceNameChange = handler; + } + @Override public @NonNull Loader> onCreateLoader(int id, Bundle args) { empty.setVisibility(View.GONE); @@ -126,25 +134,44 @@ public class DeviceListFragment extends ListFragment @Override public void onItemClick(AdapterView parent, View view, int position, long id) { + final boolean hasDeviceName = ((DeviceListItem)view).hasDeviceName(); // Tells us whether the name is set to shortId or the device name final String deviceName = ((DeviceListItem)view).getDeviceName(); - final String deviceId = ((DeviceListItem)view).getDeviceId(); + final String deviceId = ((DeviceListItem)view).getDeviceId(); - DeviceListBottomSheetFragment fragment = new DeviceListBottomSheetFragment(); - fragment.show(getFragmentManager(), fragment.getTag()); - /* - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName)); - builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive); - builder.setNegativeButton(android.R.string.cancel, null); - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (handleDisconnectDevice != null) { handleDisconnectDevice.apply(deviceId); } - } + DeviceListBottomSheetFragment bottomSheet = new DeviceListBottomSheetFragment(); + bottomSheet.setOnEditTapped(() -> { + bottomSheet.dismiss(); + EditText deviceNameText = new EditText(getContext()); + deviceNameText.setText(hasDeviceName ? deviceName : ""); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.DeviceListActivity_edit_device_name); + builder.setView(deviceNameText); + builder.setNegativeButton(android.R.string.cancel, null); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (handleDeviceNameChange != null) { handleDeviceNameChange.apply(new Pair<>(deviceId, deviceNameText.getText().toString().trim())); } + } + }); + builder.show(); + return Unit.INSTANCE; }); - builder.show(); - - */ + bottomSheet.setOnUnlinkTapped(() -> { + bottomSheet.dismiss(); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName)); + builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive); + builder.setNegativeButton(android.R.string.cancel, null); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (handleDisconnectDevice != null) { handleDisconnectDevice.apply(deviceId); } + } + }); + builder.show(); + return Unit.INSTANCE; + }); + bottomSheet.show(getFragmentManager(), bottomSheet.getTag()); } public void refresh() { diff --git a/src/org/thoughtcrime/securesms/DeviceListItem.java b/src/org/thoughtcrime/securesms/DeviceListItem.java index 83d2f2a7d9..bfc96b4d7a 100644 --- a/src/org/thoughtcrime/securesms/DeviceListItem.java +++ b/src/org/thoughtcrime/securesms/DeviceListItem.java @@ -50,4 +50,8 @@ public class DeviceListItem extends LinearLayout { return name.getText().toString(); } + public boolean hasDeviceName() { + return shortId.getVisibility() == VISIBLE; + } + } diff --git a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt index fd1568733d..28c8556fcd 100644 --- a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt @@ -52,6 +52,11 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinki 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) } From 3a79e1f2159891df3d6816ccb69bf4c23b0d93b3 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 21 Nov 2019 12:43:33 +1100 Subject: [PATCH 11/36] Handle unpair request flag. --- .../securesms/jobs/PushDecryptJob.java | 49 ++++++++------ .../securesms/loki/LinkedDevicesActivity.kt | 8 ++- .../securesms/loki/MultiDeviceUtilities.kt | 18 +++++ .../loki/PushBackgroundMessageSendJob.kt | 67 ++++++++++++------- .../securesms/sms/MessageSender.java | 14 +++- 5 files changed, 108 insertions(+), 48 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 4c94ae0594..8888927b0b 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -331,27 +331,38 @@ public class PushDecryptJob extends BaseJob implements InjectableType { SignalServiceDataMessage message = content.getDataMessage().get(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent(); - 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.isUnpairingRequest()) { + // Make sure we got the request from our primary device + String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); + if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) { + MultiDeviceUtilities.checkForRevocation(context); + } + } else { + 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.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) { - handleUnknownGroupMessage(content, message.getGroupInfo().get()); + if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) { + handleUnknownGroupMessage(content, message.getGroupInfo().get()); + } + + if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { + handleProfileKey(content, message); + } + + // Loki - This doesn't get invoked for group chats + if (content.isNeedsReceipt()) { + handleNeedsDeliveryReceipt(content, message); + } + + // Loki - Handle friend request logic if needed + updateFriendRequestStatusIfNeeded(envelope, content, message); } - - if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { - handleProfileKey(content, message); - } - - // Loki - This doesn't get invoked for group chats - if (content.isNeedsReceipt()) { - handleNeedsDeliveryReceipt(content, message); - } - - // Loki - Handle friend request logic if needed - updateFriendRequestStatusIfNeeded(envelope, content, message); } else if (content.getSyncMessage().isPresent()) { TextSecurePreferences.setMultiDevice(context, true); diff --git a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt index 28c8556fcd..0788095b2e 100644 --- a/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt @@ -8,6 +8,7 @@ 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 @@ -44,9 +45,10 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinki val database = DatabaseFactory.getLokiAPIDatabase(this) database.removePairingAuthorisation(ourPublicKey, devicePublicKey) // Update mapping on the file server - LokiStorageAPI.shared.updateUserDeviceMappings() - // Send a background message to let the device know that it has been revoked - MessageSender.sendBackgroundMessage(this, devicePublicKey) + 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() diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 257336de05..aae9a72a15 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -6,6 +6,7 @@ import nl.komponents.kovenant.Promise import nl.komponents.kovenant.all import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map +import nl.komponents.kovenant.then import nl.komponents.kovenant.toFailVoid import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil @@ -27,6 +28,23 @@ import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import java.util.* import kotlin.concurrent.schedule +fun checkForRevocation(context: Context) { + val primaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return + val ourDevice = TextSecurePreferences.getLocalNumber(context) + + LokiStorageAPI.shared.fetchDeviceMappings(primaryDevice).bind { mappings -> + val ourMapping = mappings.find { it.secondaryDevicePublicKey == ourDevice } + if (ourMapping != null) throw Error("Device has not been revoked") + // remove pairing auths for our device + DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(ourDevice) + LokiStorageAPI.shared.updateUserDeviceMappings() + }.success { + // TODO: Revoke here + }.fail { error -> + Log.d("Loki", "Revocation check failed: $error") + } +} + fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise, Exception> { val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context) return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys -> diff --git a/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt index 0c7e055ff1..d1a81005b8 100644 --- a/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt +++ b/src/org/thoughtcrime/securesms/loki/PushBackgroundMessageSendJob.kt @@ -11,39 +11,58 @@ import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage import org.whispersystems.signalservice.api.push.SignalServiceAddress +import org.whispersystems.signalservice.internal.util.JsonUtil import java.io.IOException import java.util.concurrent.TimeUnit +data class BackgroundMessage private constructor(val recipient: String, val body: String?, val friendRequest: Boolean, val unpairingRequest: Boolean) { + companion object { + @JvmStatic + fun create(recipient: String) = BackgroundMessage(recipient, null, false, false) + @JvmStatic + fun createFriendRequest(recipient: String, messageBody: String) = BackgroundMessage(recipient, messageBody, true, false) + @JvmStatic + fun createUnpairingRequest(recipient: String) = BackgroundMessage(recipient, null, false, true) + + internal fun parse(serialized: String): BackgroundMessage { + val node = JsonUtil.fromJson(serialized) + val recipient = node.get("recipient").asText() + val body = if (node.hasNonNull("body")) node.get("body").asText() else null + val friendRequest = node.get("friendRequest").asBoolean(false) + val unpairingRequest = node.get("unpairingRequest").asBoolean(false) + return BackgroundMessage(recipient, body, friendRequest, unpairingRequest) + } + } + + fun serialize(): String { + val map = mapOf("recipient" to recipient, "body" to body, "friendRequest" to friendRequest, "unpairingRequest" to unpairingRequest) + return JsonUtil.toJson(map) + } +} + class PushBackgroundMessageSendJob private constructor( parameters: Parameters, - private val recipient: String, - private val messageBody: String?, - private val friendRequest: Boolean + private val message: BackgroundMessage ) : BaseJob(parameters) { companion object { const val KEY = "PushBackgroundMessageSendJob" private val TAG = PushBackgroundMessageSendJob::class.java.simpleName - private val KEY_RECIPIENT = "recipient" - private val KEY_MESSAGE_BODY = "message_body" - private val KEY_FRIEND_REQUEST = "asFriendRequest" + private val KEY_MESSAGE = "message" } - constructor(recipient: String): this(recipient, null, false) - constructor(recipient: String, messageBody: String?, friendRequest: Boolean) : this(Parameters.Builder() + constructor(message: BackgroundMessage) : this(Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setQueue(KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(1) .build(), - recipient, messageBody, friendRequest) + message) override fun serialize(): Data { return Data.Builder() - .putString(KEY_RECIPIENT, recipient) - .putString(KEY_MESSAGE_BODY, messageBody) - .putBoolean(KEY_FRIEND_REQUEST, friendRequest) + .putString(KEY_MESSAGE, message.serialize()) .build() } @@ -52,22 +71,24 @@ class PushBackgroundMessageSendJob private constructor( } public override fun onRun() { - val message = SignalServiceDataMessage.newBuilder() + val dataMessage = SignalServiceDataMessage.newBuilder() .withTimestamp(System.currentTimeMillis()) - .withBody(messageBody) + .withBody(message.body) - if (friendRequest) { - val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(recipient) - message.withPreKeyBundle(bundle) + if (message.friendRequest) { + val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(message.recipient) + dataMessage.withPreKeyBundle(bundle) .asFriendRequest(true) + } else if (message.unpairingRequest) { + dataMessage.asUnpairingRequest(true) } val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender() - val address = SignalServiceAddress(recipient) + val address = SignalServiceAddress(message.recipient) try { - messageSender.sendMessage(-1, address, Optional.absent(), message.build()) // The message ID doesn't matter + messageSender.sendMessage(-1, address, Optional.absent(), dataMessage.build()) // The message ID doesn't matter } catch (e: Exception) { - Log.d("Loki", "Failed to send background message to: $recipient.") + Log.d("Loki", "Failed to send background message to: ${message.recipient}.") throw e } } @@ -82,10 +103,8 @@ class PushBackgroundMessageSendJob private constructor( class Factory : Job.Factory { override fun create(parameters: Parameters, data: Data): PushBackgroundMessageSendJob { try { - val recipient = data.getString(KEY_RECIPIENT) - val messageBody = if (data.hasString(KEY_MESSAGE_BODY)) data.getString(KEY_MESSAGE_BODY) else null - val friendRequest = data.getBooleanOrDefault(KEY_FRIEND_REQUEST, false) - return PushBackgroundMessageSendJob(parameters, recipient, messageBody, friendRequest) + val messageJSON = data.getString(KEY_MESSAGE) + return PushBackgroundMessageSendJob(parameters, BackgroundMessage.parse(messageJSON)) } catch (e: IOException) { throw AssertionError(e) } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index de240c81e4..c60bb40231 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -17,6 +17,7 @@ package org.thoughtcrime.securesms.sms; import android.content.Context; +import android.os.AsyncTask; import android.support.annotation.NonNull; import org.thoughtcrime.securesms.ApplicationContext; @@ -44,6 +45,7 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.loki.BackgroundMessage; import org.thoughtcrime.securesms.loki.FriendRequestHandler; import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; @@ -58,7 +60,11 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.push.ContactTokenDetails; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.loki.api.LokiStorageAPI; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.utilities.PromiseUtil; @@ -123,11 +129,15 @@ public class MessageSender { // We don't call the message sender here directly and instead we just opt to create a specific job for the send // This is because calling message sender directly would cause the application to freeze in some cases as it was blocking the thread when waiting for a response from the send public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) { - ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(contactHexEncodedPublicKey)); + ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.create(contactHexEncodedPublicKey))); } public static void sendBackgroundFriendRequest(Context context, String contactHexEncodedPublicKey, String messageBody) { - ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(contactHexEncodedPublicKey, messageBody, true)); + ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createFriendRequest(contactHexEncodedPublicKey, messageBody))); + } + + public static void sendUnpairRequest(Context context, String contactHexEncodedPublicKey) { + ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createUnpairingRequest(contactHexEncodedPublicKey))); } // endregion From 76ce008063dc0eddf93904292dfc4b2193423232 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 21 Nov 2019 15:37:19 +1100 Subject: [PATCH 12/36] forgot to use lokiv4 in previous code --- .../securesms/database/helpers/SQLCipherOpenHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 115cd1090d..6715feb5ee 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -72,7 +72,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV3 = 24; private static final int lokiV4 = 25; - private static final int DATABASE_VERSION = lokiV3; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes + private static final int DATABASE_VERSION = lokiV4; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes private static final String DATABASE_NAME = "signal.db"; private final Context context; From f753dd1ea9be56bceb2d7dd21171cf8ac7f7fe22 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Thu, 21 Nov 2019 16:31:01 +1100 Subject: [PATCH 13/36] Added a way to clear database. --- .../securesms/ApplicationContext.java | 31 ++++++++++++++++++- .../PassphraseRequiredActionBarActivity.java | 1 + .../securesms/crypto/MasterSecretUtil.java | 4 +++ .../securesms/util/TextSecurePreferences.java | 8 +++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index f1c3c872db..bfa719a853 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -20,10 +20,14 @@ import android.annotation.SuppressLint; import android.arch.lifecycle.DefaultLifecycleObserver; import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.ProcessLifecycleOwner; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.database.ContentObserver; import android.os.AsyncTask; import android.os.Build; +import android.os.Handler; +import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.multidex.MultiDexApplication; @@ -38,6 +42,7 @@ import org.signal.aesgcmprovider.AesGcmProvider; import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; +import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.database.DatabaseContentProviders; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule; @@ -154,8 +159,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc @Override public void onCreate() { super.onCreate(); - startKovenant(); Log.i(TAG, "onCreate()"); + checkNeedsDatabaseReset(); + startKovenant(); initializeSecurityProvider(); initializeLogging(); initializeCrashHandling(); @@ -588,4 +594,27 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc if (lokiMessengerUpdatesFeedPoller != null) lokiMessengerUpdatesFeedPoller.startIfNeeded(); } // endregion + + public void checkNeedsDatabaseReset() { + if (TextSecurePreferences.resetDatabase(this)) { + PreferenceManager.getDefaultSharedPreferences(this).edit().clear().commit(); + MasterSecretUtil.clear(this); + if (this.deleteDatabase("signal.db")) { + Log.d("Loki", "Deleted database"); + } + } + } + + public void clearData() { + TextSecurePreferences.setResetDatabase(this, true); + new Handler().postDelayed(this::restartApplication, 200); + } + + public void restartApplication() { + Intent intent = new Intent(this, ConversationListActivity.class); + ComponentName componentName = intent.getComponent(); + Intent mainIntent = Intent.makeRestartActivityTask(componentName); + this.startActivity(mainIntent); + Runtime.getRuntime().exit(0); + } } diff --git a/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java b/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java index f36aac543b..5f24485fac 100644 --- a/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java +++ b/src/org/thoughtcrime/securesms/PassphraseRequiredActionBarActivity.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; diff --git a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java index 0542dbd73f..4d964d0291 100644 --- a/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java +++ b/src/org/thoughtcrime/securesms/crypto/MasterSecretUtil.java @@ -198,6 +198,10 @@ public class MasterSecretUtil { 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() diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index ef82974f7f..c3c77a3b1f 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -1185,5 +1185,13 @@ public class TextSecurePreferences { public static void setMasterHexEncodedPublicKey(Context context, String masterHexEncodedPublicKey) { setStringPreference(context, "master_hex_encoded_public_key", masterHexEncodedPublicKey.toLowerCase()); } + + public static void setResetDatabase(Context context, boolean resetDatabase) { + PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("database_reset", resetDatabase).commit(); + } + + public static boolean resetDatabase(Context context) { + return getBooleanPreference(context, "database_reset", false); + } // endregion } From 733ab06e709747f5de7a0d296be3a654dcac46ea Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 22 Nov 2019 09:35:15 +1100 Subject: [PATCH 14/36] Clear database on unpair. --- .../securesms/ApplicationContext.java | 13 ++++++++++- .../securesms/jobs/PushDecryptJob.java | 2 +- .../securesms/loki/MultiDeviceUtilities.kt | 11 ++++++---- .../securesms/util/TextSecurePreferences.java | 22 +++++++++++++++++++ 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index bfa719a853..716d66a865 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -70,6 +70,7 @@ import org.thoughtcrime.securesms.loki.LokiAPIDatabase; import org.thoughtcrime.securesms.loki.LokiPublicChatManager; import org.thoughtcrime.securesms.loki.LokiRSSFeedPoller; import org.thoughtcrime.securesms.loki.LokiUserDatabase; +import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.providers.BlobProvider; @@ -202,6 +203,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc // Loki - Update device mappings if (setUpStorageAPIIfNeeded()) { LokiStorageAPI.Companion.getShared().updateUserDeviceMappings(); + if (TextSecurePreferences.needsRevocationCheck(this)) { + checkNeedsRevocation(); + } } } @@ -595,13 +599,20 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc } // endregion + public void checkNeedsRevocation() { + MultiDeviceUtilities.checkForRevocation(this); + } + public void checkNeedsDatabaseReset() { if (TextSecurePreferences.resetDatabase(this)) { - PreferenceManager.getDefaultSharedPreferences(this).edit().clear().commit(); + boolean wasUnlinked = TextSecurePreferences.databaseResetFromUnpair(this); + TextSecurePreferences.clearAll(this); MasterSecretUtil.clear(this); if (this.deleteDatabase("signal.db")) { Log.d("Loki", "Deleted database"); } + // Loki - Re-set the preference so we can use it in the starting screen to determine whether device was unlinked or not + TextSecurePreferences.setDatabaseResetFromUnpair(this, wasUnlinked); } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 8888927b0b..419b7b7c97 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -331,7 +331,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { SignalServiceDataMessage message = content.getDataMessage().get(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent(); - if (message.isUnpairingRequest()) { + if (!envelope.isFriendRequest() && message.isUnpairingRequest()) { // Make sure we got the request from our primary device String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) { diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index aae9a72a15..9b35ffe88b 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -8,6 +8,7 @@ import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.then import nl.komponents.kovenant.toFailVoid +import nl.komponents.kovenant.ui.successUi import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.Address @@ -35,13 +36,15 @@ fun checkForRevocation(context: Context) { LokiStorageAPI.shared.fetchDeviceMappings(primaryDevice).bind { mappings -> val ourMapping = mappings.find { it.secondaryDevicePublicKey == ourDevice } if (ourMapping != null) throw Error("Device has not been revoked") - // remove pairing auths for our device + // remove pairing authorisations for our device DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(ourDevice) LokiStorageAPI.shared.updateUserDeviceMappings() - }.success { - // TODO: Revoke here + }.successUi { + TextSecurePreferences.setNeedsRevocationCheck(context, false) + ApplicationContext.getInstance(context).clearData() }.fail { error -> - Log.d("Loki", "Revocation check failed: $error") + TextSecurePreferences.setNeedsRevocationCheck(context, true) + Log.d("Loki", "Revocation check failed: ${error.message ?: error}") } } diff --git a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java index c3c77a3b1f..c6025a3ec7 100644 --- a/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/src/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -1187,11 +1187,33 @@ public class TextSecurePreferences { } public static void setResetDatabase(Context context, boolean resetDatabase) { + // We do it this way so that it gets persisted in storage straight away PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("database_reset", resetDatabase).commit(); } public static boolean resetDatabase(Context context) { return getBooleanPreference(context, "database_reset", false); } + + public static void setDatabaseResetFromUnpair(Context context, boolean value) { + // We do it this way so that it gets persisted in storage straight away + PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("database_reset_unpair", value).commit(); + } + + public static boolean databaseResetFromUnpair(Context context) { + return getBooleanPreference(context, "database_reset_unpair", false); + } + + public static void setNeedsRevocationCheck(Context context, boolean needsCheck) { + setBooleanPreference(context, "needs_revocation", needsCheck); + } + + public static boolean needsRevocationCheck(Context context) { + return getBooleanPreference(context, "needs_revocation", false); + } // endregion + + public static void clearAll(Context context) { + PreferenceManager.getDefaultSharedPreferences(context).edit().clear().commit(); + } } From 27c8b45ae394d8a4878c5710c177521d58b32851 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 22 Nov 2019 10:43:22 +1100 Subject: [PATCH 15/36] Show device unlink message on startup. --- res/values/strings.xml | 3 +++ .../securesms/ApplicationContext.java | 4 ++-- .../securesms/jobs/PushDecryptJob.java | 1 + .../securesms/loki/MultiDeviceUtilities.kt | 2 ++ .../registration/WelcomeActivity.java | 21 +++++++++++++++++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 3b49c035c2..cb70da17b1 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1647,5 +1647,8 @@ Edit device name Unlink device + + Device unlinked + This device has been successfully unlinked. diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java index 716d66a865..b2d9aa9cd8 100644 --- a/src/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/org/thoughtcrime/securesms/ApplicationContext.java @@ -607,12 +607,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc if (TextSecurePreferences.resetDatabase(this)) { boolean wasUnlinked = TextSecurePreferences.databaseResetFromUnpair(this); TextSecurePreferences.clearAll(this); + TextSecurePreferences.setDatabaseResetFromUnpair(this, wasUnlinked); // Loki - Re-set the preference so we can use it in the starting screen to determine whether device was unlinked or not MasterSecretUtil.clear(this); if (this.deleteDatabase("signal.db")) { Log.d("Loki", "Deleted database"); } - // Loki - Re-set the preference so we can use it in the starting screen to determine whether device was unlinked or not - TextSecurePreferences.setDatabaseResetFromUnpair(this, wasUnlinked); + } } diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 419b7b7c97..9e2fad2f15 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -335,6 +335,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // Make sure we got the request from our primary device String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context); if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) { + TextSecurePreferences.setDatabaseResetFromUnpair(context, true); MultiDeviceUtilities.checkForRevocation(context); } } else { diff --git a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt index 9b35ffe88b..e4f7550ad3 100644 --- a/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt +++ b/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt @@ -24,6 +24,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.loki.api.LokiStorageAPI import org.whispersystems.signalservice.loki.api.PairingAuthorisation import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus +import org.whispersystems.signalservice.loki.utilities.Analytics import org.whispersystems.signalservice.loki.utilities.recover import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import java.util.* @@ -40,6 +41,7 @@ fun checkForRevocation(context: Context) { DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(ourDevice) LokiStorageAPI.shared.updateUserDeviceMappings() }.successUi { + Analytics.shared.track("Secondary Device Unlinked") TextSecurePreferences.setNeedsRevocationCheck(context, false) ApplicationContext.getInstance(context).clearData() }.fail { error -> diff --git a/src/org/thoughtcrime/securesms/registration/WelcomeActivity.java b/src/org/thoughtcrime/securesms/registration/WelcomeActivity.java index 5cd036f29c..83247f5371 100644 --- a/src/org/thoughtcrime/securesms/registration/WelcomeActivity.java +++ b/src/org/thoughtcrime/securesms/registration/WelcomeActivity.java @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.registration; import android.Manifest; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; @@ -8,6 +10,7 @@ import android.support.annotation.NonNull; import org.thoughtcrime.securesms.BaseActionBarActivity; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.CommunicationActions; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.loki.utilities.Analytics; import network.loki.messenger.R; @@ -23,6 +26,24 @@ public class WelcomeActivity extends BaseActionBarActivity { Analytics.Companion.getShared().track("Landing Screen Viewed"); } + @Override + protected void onResume() { + super.onResume(); + if (TextSecurePreferences.databaseResetFromUnpair(this)) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.dialog_device_unlink_title); + builder.setMessage(R.string.dialog_device_unlink_message); + builder.setPositiveButton(R.string.ok, null); + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + TextSecurePreferences.setDatabaseResetFromUnpair(getBaseContext(), false); + } + }); + builder.show(); + } + } + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); From b650ee6ebc0dae18aa11ebd27985974760f1fb10 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 22 Nov 2019 12:08:09 +1100 Subject: [PATCH 16/36] Added QR code scanning. --- res/layout/activity_seed.xml | 13 ++++++- res/layout/fragment_scan_qr_code.xml | 3 +- res/layout/view_device_linking.xml | 7 ++++ res/values/strings.xml | 3 +- .../securesms/loki/DeviceLinkingView.kt | 22 ++++++++++-- .../securesms/loki/ScanQRCodeFragment.kt | 25 ++++++++++++-- .../securesms/loki/SeedActivity.kt | 34 ++++++++++++++++++- src/org/thoughtcrime/securesms/qr/QrCode.java | 11 ++++-- 8 files changed, 107 insertions(+), 11 deletions(-) diff --git a/res/layout/activity_seed.xml b/res/layout/activity_seed.xml index 1ac6b7e57a..cb77523182 100644 --- a/res/layout/activity_seed.xml +++ b/res/layout/activity_seed.xml @@ -96,6 +96,17 @@ app:labeledEditText_background="@color/loki_darkest_gray" app:labeledEditText_label="@string/activity_key_pair_public_key_edit_text_label"/> +