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