Hook up signal device linking view.

This commit is contained in:
Mikunj 2019-11-20 09:50:40 +11:00
parent 549631848d
commit 0f5db5aa33
13 changed files with 919 additions and 863 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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" />
<!--
<TextView android:id="@+id/created"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -29,5 +31,5 @@
android:textColor="?attr/conversation_list_item_subject_color"
android:fontFamily="sans-serif-light"
android:layout_marginBottom="8dp" />
-->
</org.thoughtcrime.securesms.DeviceListItem>

View File

@ -299,6 +299,7 @@
<string name="DeviceListActivity_unlinking_device">Unlinking device...</string>
<string name="DeviceListActivity_unlinking_device_no_ellipsis">Unlinking device</string>
<string name="DeviceListActivity_network_failed">Network failed!</string>
<string name="DeviceListActivity_unlinked_device">Successfully unlinked device</string>
<!-- DeviceListItem -->
<string name="DeviceListItem_unnamed_device">Unnamed device</string>
@ -1576,7 +1577,7 @@
<string name="activity_settings_public_key_copied_message">Copied to clipboard</string>
<string name="activity_settings_share_public_key_button_title">Share Public Key</string>
<string name="activity_settings_show_qr_code_button_title">Show QR Code</string>
<string name="activity_settings_link_device_button_title">Link Device</string>
<string name="activity_settings_linked_devices_button_title">Linked Device</string>
<string name="activity_settings_show_seed_button_title">Show Seed</string>
<string name="activity_settings_seed_dialog_title">Your Seed</string>
<string name="activity_settings_seed_dialog_copy_button_title">Copy</string>

View File

@ -41,8 +41,8 @@
android:title="@string/activity_settings_show_qr_code_button_title"
android:icon="@drawable/icon_qr_code"/>
<Preference android:key="preference_category_link_device"
android:title="Link Device"
<Preference android:key="preference_category_linked_devices"
android:title="@string/activity_settings_linked_devices_button_title"
android:icon="@drawable/icon_link"/>
<Preference android:key="preference_category_seed"

View File

@ -46,6 +46,7 @@ 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;
@ -89,7 +90,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
// private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
private static final String PREFERENCE_CATEGORY_PUBLIC_KEY = "preference_category_public_key";
private static final String PREFERENCE_CATEGORY_QR_CODE = "preference_category_qr_code";
private static final String PREFERENCE_CATEGORY_LINK_DEVICE = "preference_category_link_device";
private static final String PREFERENCE_CATEGORY_LINKED_DEVICES = "preference_category_linked_devices";
private static final String PREFERENCE_CATEGORY_SEED = "preference_category_seed";
private final DynamicTheme dynamicTheme = new DynamicTheme();
@ -192,20 +193,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
this.findPreference(PREFERENCE_CATEGORY_QR_CODE)
.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_QR_CODE));
Preference linkDevicePreference = this.findPreference(PREFERENCE_CATEGORY_LINK_DEVICE);
linkDevicePreference.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_LINK_DEVICE));
// Disable if we hit the cap of 1 linked device
if (isMasterDevice) {
Context context = getContext();
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(context).getPairingAuthorisations(userHexEncodedPublicKey).size() <= 1;
linkDevicePreference.setEnabled(isDeviceLinkingEnabled);
linkDevicePreference.getIcon().setAlpha(isDeviceLinkingEnabled ? 255 : 124);
} else {
// Hide if this is a slave device
linkDevicePreference.setVisible(false);
}
Preference linkDevicesPreference = this.findPreference(PREFERENCE_CATEGORY_LINKED_DEVICES);
linkDevicesPreference.setVisible(isMasterDevice);
linkDevicesPreference.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_LINKED_DEVICES));
Preference seedPreference = this.findPreference(PREFERENCE_CATEGORY_SEED);
// Hide if this is a slave device
@ -299,7 +289,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
// this.findPreference(PREFERENCE_CATEGORY_ADVANCED).setIcon(advanced);
this.findPreference(PREFERENCE_CATEGORY_PUBLIC_KEY).setIcon(publicKey);
this.findPreference(PREFERENCE_CATEGORY_QR_CODE).setIcon(qrCode);
this.findPreference(PREFERENCE_CATEGORY_LINK_DEVICE).setIcon(linkDevice);
this.findPreference(PREFERENCE_CATEGORY_LINKED_DEVICES).setIcon(linkDevice);
this.findPreference(PREFERENCE_CATEGORY_SEED).setIcon(seed);
}
@ -360,7 +350,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
case PREFERENCE_CATEGORY_QR_CODE:
QRCodeDialog.INSTANCE.show(getContext());
break;
case PREFERENCE_CATEGORY_LINK_DEVICE:
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:

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ListFragment;
@ -17,25 +16,21 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import com.melnykov.fab.FloatingActionButton;
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.MnemonicUtilities;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import java.io.IOException;
import java.io.File;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import org.whispersystems.libsignal.util.guava.Function;
import network.loki.messenger.R;
@ -46,14 +41,13 @@ public class DeviceListFragment extends ListFragment
private static final String TAG = DeviceListFragment.class.getSimpleName();
@Inject
SignalServiceAccountManager accountManager;
private File languageFileDirectory;
private Locale locale;
private View empty;
private View progressContainer;
private FloatingActionButton addDeviceButton;
private Button.OnClickListener addDeviceButtonListener;
private Function<String, Void> 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<String, Void> handler) {
this.handleDisconnectDevice = handler;
}
public void setAddDeviceButtonVisible(boolean visible) {
addDeviceButton.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
@Override
public @NonNull Loader<List<Device>> 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<Void, Void, Void>(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);

View File

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

View File

@ -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<List<Device>> {
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<Device> loadInBackground() {
try {
List<Device> devices = Stream.of(accountManager.getDevices())
.filter(d -> d.getId() != SignalServiceAddress.DEFAULT_DEVICE_ID)
.map(this::mapToDevice)
.toList();
String ourPublicKey = TextSecurePreferences.getLocalNumber(getContext());
List<String> secondaryDevicePublicKeys = LokiStorageAPI.shared.getSecondaryDevicePublicKeys(ourPublicKey).get();
List<Device> 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<Device> {
@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());
}
}
}

View File

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

View File

@ -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() }

View File

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

View File

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

View File

@ -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(" ")
}
}