mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-13 04:03:39 +00:00
Merge branch 'dev' of https://github.com/loki-project/session-android into light-theme
This commit is contained in:
commit
ec24e3ea4a
@ -185,8 +185,8 @@ dependencies {
|
|||||||
implementation "com.opencsv:opencsv:4.6"
|
implementation "com.opencsv:opencsv:4.6"
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 65
|
def canonicalVersionCode = 69
|
||||||
def canonicalVersionName = "1.4.1"
|
def canonicalVersionName = "1.4.2"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
def abiPostFix = ['armeabi-v7a' : 1,
|
def abiPostFix = ['armeabi-v7a' : 1,
|
||||||
|
@ -65,4 +65,14 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:text="@string/dialog_seed_disclaimer"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/very_small_font_size"
|
||||||
|
android:textAlignment="center" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -1409,7 +1409,7 @@
|
|||||||
<string name="text_secure_normal__menu_settings">Settings</string>
|
<string name="text_secure_normal__menu_settings">Settings</string>
|
||||||
<string name="text_secure_normal__menu_clear_passphrase">Lock</string>
|
<string name="text_secure_normal__menu_clear_passphrase">Lock</string>
|
||||||
<string name="text_secure_normal__mark_all_as_read">Mark all read</string>
|
<string name="text_secure_normal__mark_all_as_read">Mark all read</string>
|
||||||
<string name="text_secure_normal__invite_friends">Invite friends</string>
|
<string name="text_secure_normal__invite_friends">Invite Contacts</string>
|
||||||
<string name="text_secure_normal__help">Help</string>
|
<string name="text_secure_normal__help">Help</string>
|
||||||
|
|
||||||
<!-- verify_display_fragment -->
|
<!-- verify_display_fragment -->
|
||||||
@ -1714,7 +1714,7 @@
|
|||||||
|
|
||||||
<string name="activity_seed_title">Your Recovery Phrase</string>
|
<string name="activity_seed_title">Your Recovery Phrase</string>
|
||||||
<string name="activity_seed_title_2">Meet your recovery phrase</string>
|
<string name="activity_seed_title_2">Meet your recovery phrase</string>
|
||||||
<string name="activity_seed_explanation">Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\’t give it to anyone.</string>
|
<string name="activity_seed_explanation">Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\'t give it to anyone.</string>
|
||||||
<string name="activity_seed_reveal_button_title">Hold to reveal</string>
|
<string name="activity_seed_reveal_button_title">Hold to reveal</string>
|
||||||
|
|
||||||
<string name="view_seed_reminder_subtitle_1">Secure your account by saving your recovery phrase</string>
|
<string name="view_seed_reminder_subtitle_1">Secure your account by saving your recovery phrase</string>
|
||||||
@ -1732,7 +1732,7 @@
|
|||||||
<string name="activity_create_private_chat_title">New Session</string>
|
<string name="activity_create_private_chat_title">New Session</string>
|
||||||
<string name="activity_create_private_chat_enter_session_id_tab_title">Enter Session ID</string>
|
<string name="activity_create_private_chat_enter_session_id_tab_title">Enter Session ID</string>
|
||||||
<string name="activity_create_private_chat_scan_qr_code_tab_title">Scan QR Code</string>
|
<string name="activity_create_private_chat_scan_qr_code_tab_title">Scan QR Code</string>
|
||||||
<string name="activity_create_private_chat_scan_qr_code_explanation">Scan a user\’s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings.</string>
|
<string name="activity_create_private_chat_scan_qr_code_explanation">Scan a user\'s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings.</string>
|
||||||
|
|
||||||
<string name="fragment_enter_public_key_edit_text_hint">Enter Session ID of recipient</string>
|
<string name="fragment_enter_public_key_edit_text_hint">Enter Session ID of recipient</string>
|
||||||
<string name="fragment_enter_public_key_explanation">Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code.</string>
|
<string name="fragment_enter_public_key_explanation">Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code.</string>
|
||||||
@ -1851,4 +1851,6 @@
|
|||||||
|
|
||||||
<string name="activity_select_contacts_title">Select Contacts</string>
|
<string name="activity_select_contacts_title">Select Contacts</string>
|
||||||
|
|
||||||
|
<string name="dialog_seed_disclaimer">*Please note that it is not possible to use the same Session ID on multiple devices simultaneously</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -29,16 +29,17 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Build.VERSION;
|
import android.os.Build.VERSION;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.graphics.drawable.DrawableCompat;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||||
@ -51,11 +52,8 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
|||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
|
|
||||||
import org.whispersystems.signalservice.loki.utilities.HexEncodingKt;
|
import org.whispersystems.signalservice.loki.utilities.HexEncodingKt;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -337,13 +335,12 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
case PREFERENCE_CATEGORY_QR_CODE: break;
|
case PREFERENCE_CATEGORY_QR_CODE: break;
|
||||||
case PREFERENCE_CATEGORY_LINKED_DEVICES: break;
|
case PREFERENCE_CATEGORY_LINKED_DEVICES: break;
|
||||||
case PREFERENCE_CATEGORY_SEED:
|
case PREFERENCE_CATEGORY_SEED:
|
||||||
File languageFileDirectory = new File(getContext().getApplicationInfo().dataDir);
|
|
||||||
try {
|
try {
|
||||||
String hexEncodedSeed = IdentityKeyUtil.retrieve(getContext(), IdentityKeyUtil.lokiSeedKey);
|
String hexEncodedSeed = IdentityKeyUtil.retrieve(getContext(), IdentityKeyUtil.lokiSeedKey);
|
||||||
if (hexEncodedSeed == null) {
|
if (hexEncodedSeed == null) {
|
||||||
hexEncodedSeed = HexEncodingKt.getHexEncodedPrivateKey(IdentityKeyUtil.getIdentityKeyPair(getContext())); // Legacy account
|
hexEncodedSeed = HexEncodingKt.getHexEncodedPrivateKey(IdentityKeyUtil.getIdentityKeyPair(getContext())); // Legacy account
|
||||||
}
|
}
|
||||||
String seed = new MnemonicCodec(languageFileDirectory).encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.Companion.getEnglish());
|
String seed = "";
|
||||||
new AlertDialog.Builder(getContext())
|
new AlertDialog.Builder(getContext())
|
||||||
.setTitle(R.string.activity_settings_seed_dialog_title)
|
.setTitle(R.string.activity_settings_seed_dialog_title)
|
||||||
.setMessage(seed)
|
.setMessage(seed)
|
||||||
|
@ -4,11 +4,6 @@ import android.app.Activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.fragment.app.ListFragment;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -19,6 +14,12 @@ import android.widget.EditText;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.ListFragment;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
import com.melnykov.fab.FloatingActionButton;
|
import com.melnykov.fab.FloatingActionButton;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
@ -26,12 +27,10 @@ import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
|||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.devicelist.Device;
|
import org.thoughtcrime.securesms.devicelist.Device;
|
||||||
import org.thoughtcrime.securesms.loki.dialogs.DeviceEditingOptionsBottomSheet;
|
import org.thoughtcrime.securesms.loki.dialogs.DeviceEditingOptionsBottomSheet;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.whispersystems.libsignal.util.guava.Function;
|
import org.whispersystems.libsignal.util.guava.Function;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@ -48,7 +47,6 @@ public class DeviceListFragment extends ListFragment
|
|||||||
|
|
||||||
private static final String TAG = DeviceListFragment.class.getSimpleName();
|
private static final String TAG = DeviceListFragment.class.getSimpleName();
|
||||||
|
|
||||||
private File languageFileDirectory;
|
|
||||||
private Locale locale;
|
private Locale locale;
|
||||||
private View empty;
|
private View empty;
|
||||||
private View progressContainer;
|
private View progressContainer;
|
||||||
@ -85,7 +83,6 @@ public class DeviceListFragment extends ListFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle bundle) {
|
public void onActivityCreated(Bundle bundle) {
|
||||||
super.onActivityCreated(bundle);
|
super.onActivityCreated(bundle);
|
||||||
this.languageFileDirectory = MnemonicUtilities.getLanguageFileDirectory(getContext());
|
|
||||||
getLoaderManager().initLoader(0, null, this);
|
getLoaderManager().initLoader(0, null, this);
|
||||||
getListView().setOnItemClickListener(this);
|
getListView().setOnItemClickListener(this);
|
||||||
}
|
}
|
||||||
@ -107,7 +104,7 @@ public class DeviceListFragment extends ListFragment
|
|||||||
empty.setVisibility(View.GONE);
|
empty.setVisibility(View.GONE);
|
||||||
progressContainer.setVisibility(View.VISIBLE);
|
progressContainer.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
return new DeviceListLoader(getActivity(), languageFileDirectory);
|
return new DeviceListLoader(getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1530,9 +1530,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onSecurityUpdated() {
|
private void onSecurityUpdated() {
|
||||||
Log.i(TAG, "onSecurityUpdated()");
|
if (recipient != null) {
|
||||||
updateReminders(recipient.hasSeenInviteReminder());
|
updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId());
|
||||||
updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId());
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateReminders(boolean seenInvite) {
|
protected void updateReminders(boolean seenInvite) {
|
||||||
@ -1853,9 +1853,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
ApplicationContext.getInstance(this)
|
ApplicationContext.getInstance(this)
|
||||||
.getJobManager()
|
.getJobManager()
|
||||||
.add(new RetrieveProfileJob(recipient));
|
.add(new RetrieveProfileJob(recipient));
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -25,10 +25,6 @@ import android.graphics.Color;
|
|||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.DimenRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
@ -50,6 +46,11 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.DimenRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
@ -975,7 +976,11 @@ public class ConversationItem extends LinearLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setAuthor(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
private void setAuthor(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
|
||||||
String threadName = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(current.getThreadId()).getName();
|
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(current.getThreadId());
|
||||||
|
String threadName = null;
|
||||||
|
if (recipient != null) {
|
||||||
|
threadName = recipient.getName();
|
||||||
|
}
|
||||||
boolean isRSSFeed = threadName != null && (threadName.equals("Loki News") || threadName.equals("Session Updates"));
|
boolean isRSSFeed = threadName != null && (threadName.equals("Loki News") || threadName.equals("Session Updates"));
|
||||||
if (isGroupThread && !isRSSFeed && !current.isOutgoing()) {
|
if (isGroupThread && !isRSSFeed && !current.isOutgoing()) {
|
||||||
contactPhotoHolder.setVisibility(VISIBLE);
|
contactPhotoHolder.setVisibility(VISIBLE);
|
||||||
|
@ -4,9 +4,10 @@ package org.thoughtcrime.securesms.crypto;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.security.keystore.KeyGenParameterSpec;
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
import android.security.keystore.KeyProperties;
|
import android.security.keystore.KeyProperties;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import android.util.Base64;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
@ -28,6 +29,7 @@ import java.security.KeyStoreException;
|
|||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.NoSuchProviderException;
|
import java.security.NoSuchProviderException;
|
||||||
import java.security.UnrecoverableEntryException;
|
import java.security.UnrecoverableEntryException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
@ -38,7 +40,7 @@ import javax.crypto.NoSuchPaddingException;
|
|||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.GCMParameterSpec;
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
|
|
||||||
public class KeyStoreHelper {
|
public final class KeyStoreHelper {
|
||||||
|
|
||||||
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
|
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
|
||||||
private static final String KEY_ALIAS = "SignalSecret";
|
private static final String KEY_ALIAS = "SignalSecret";
|
||||||
@ -99,12 +101,38 @@ public class KeyStoreHelper {
|
|||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
private static SecretKey getKeyStoreEntry() {
|
private static SecretKey getKeyStoreEntry() {
|
||||||
|
KeyStore keyStore = getKeyStore();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt 1
|
||||||
|
return getSecretKey(keyStore);
|
||||||
|
} catch (UnrecoverableKeyException e1) {
|
||||||
|
try {
|
||||||
|
// Attempt 2
|
||||||
|
return getSecretKey(keyStore);
|
||||||
|
} catch (UnrecoverableKeyException e2) {
|
||||||
|
throw new AssertionError(e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SecretKey getSecretKey(KeyStore keyStore) throws UnrecoverableKeyException {
|
||||||
|
try {
|
||||||
|
KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
|
||||||
|
return entry.getSecretKey();
|
||||||
|
} catch (UnrecoverableKeyException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyStore getKeyStore() {
|
||||||
try {
|
try {
|
||||||
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
|
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
|
||||||
keyStore.load(null);
|
keyStore.load(null);
|
||||||
|
return keyStore;
|
||||||
return ((KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null)).getSecretKey();
|
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
|
||||||
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableEntryException e) {
|
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,10 @@ import android.content.Context;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import net.sqlcipher.database.SQLiteDatabase;
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
import net.sqlcipher.database.SQLiteDatabaseHook;
|
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||||
@ -87,8 +88,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
private static final int lokiV10 = 31;
|
private static final int lokiV10 = 31;
|
||||||
private static final int lokiV11 = 32;
|
private static final int lokiV11 = 32;
|
||||||
private static final int lokiV12 = 33;
|
private static final int lokiV12 = 33;
|
||||||
|
private static final int lokiV13 = 34;
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = lokiV12; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
private static final int DATABASE_VERSION = lokiV13; // 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 static final String DATABASE_NAME = "signal.db";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@ -140,7 +142,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateOnionRequestPathTableCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
|
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateReceivedMessageHashValuesTable2Command());
|
db.execSQL(LokiAPIDatabase.getCreateReceivedMessageHashValuesTable3Command());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateOpenGroupAuthTokenTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateOpenGroupAuthTokenTableCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand());
|
db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand());
|
||||||
@ -609,11 +611,14 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
if (oldVersion < lokiV12) {
|
if (oldVersion < lokiV12) {
|
||||||
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
|
db.execSQL(LokiAPIDatabase.getCreateLastMessageHashValueTable2Command());
|
||||||
db.execSQL(LokiAPIDatabase.getCreateReceivedMessageHashValuesTable2Command());
|
|
||||||
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupRatchetTableCommand());
|
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupRatchetTableCommand());
|
||||||
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand());
|
db.execSQL(SharedSenderKeysDatabase.getCreateClosedGroupPrivateKeyTableCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < lokiV13) {
|
||||||
|
db.execSQL(LokiAPIDatabase.getCreateReceivedMessageHashValuesTable3Command());
|
||||||
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.database.loaders;
|
package org.thoughtcrime.securesms.database.loaders;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
@ -8,13 +9,10 @@ import com.annimon.stream.Stream;
|
|||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.devicelist.Device;
|
import org.thoughtcrime.securesms.devicelist.Device;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities;
|
|
||||||
import org.thoughtcrime.securesms.util.AsyncLoader;
|
import org.thoughtcrime.securesms.util.AsyncLoader;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
|
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -23,11 +21,9 @@ import java.util.Set;
|
|||||||
public class DeviceListLoader extends AsyncLoader<List<Device>> {
|
public class DeviceListLoader extends AsyncLoader<List<Device>> {
|
||||||
|
|
||||||
private static final String TAG = DeviceListLoader.class.getSimpleName();
|
private static final String TAG = DeviceListLoader.class.getSimpleName();
|
||||||
private MnemonicCodec mnemonicCodec;
|
|
||||||
|
|
||||||
public DeviceListLoader(Context context, File languageFileDirectory) {
|
public DeviceListLoader(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
this.mnemonicCodec = new MnemonicCodec(languageFileDirectory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -45,7 +41,7 @@ public class DeviceListLoader extends AsyncLoader<List<Device>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Device mapToDevice(@NonNull String hexEncodedPublicKey) {
|
private Device mapToDevice(@NonNull String hexEncodedPublicKey) {
|
||||||
String shortId = MnemonicUtilities.getFirst3Words(mnemonicCodec, hexEncodedPublicKey);
|
String shortId = "";
|
||||||
String name = DatabaseFactory.getLokiUserDatabase(getContext()).getDisplayName(hexEncodedPublicKey);
|
String name = DatabaseFactory.getLokiUserDatabase(getContext()).getDisplayName(hexEncodedPublicKey);
|
||||||
return new Device(hexEncodedPublicKey, shortId, name);
|
return new Device(hexEncodedPublicKey, shortId, name);
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,13 @@ import android.app.PendingIntent;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import com.annimon.stream.Collectors;
|
import com.annimon.stream.Collectors;
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
@ -66,10 +67,10 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceProtocol;
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation;
|
||||||
|
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.shelved.SyncMessagesProtocol;
|
import org.thoughtcrime.securesms.loki.protocol.shelved.SyncMessagesProtocol;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.PromiseUtilities;
|
import org.thoughtcrime.securesms.loki.utilities.PromiseUtilities;
|
||||||
@ -303,7 +304,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
SessionMetaProtocol.handleProfileKeyUpdate(context, content);
|
SessionMetaProtocol.handleProfileKeyUpdate(context, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.isNeedsReceipt() && SessionMetaProtocol.shouldSendDeliveryReceipt(Address.fromSerialized(content.getSender()))) {
|
if (SessionMetaProtocol.shouldSendDeliveryReceipt(message, Address.fromSerialized(content.getSender()))) {
|
||||||
handleNeedsDeliveryReceipt(content, message);
|
handleNeedsDeliveryReceipt(content, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.jobmanager.Data;
|
|||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||||
@ -79,13 +78,12 @@ public class SendDeliveryReceiptJob extends BaseJob implements InjectableType {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRun() throws IOException, UntrustedIdentityException {
|
public void onRun() throws IOException, UntrustedIdentityException {
|
||||||
|
Log.d("Loki", "Sending delivery receipt.");
|
||||||
SignalServiceAddress remoteAddress = new SignalServiceAddress(address);
|
SignalServiceAddress remoteAddress = new SignalServiceAddress(address);
|
||||||
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
|
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
|
||||||
Collections.singletonList(messageId),
|
Collections.singletonList(messageId),
|
||||||
timestamp);
|
timestamp);
|
||||||
|
|
||||||
if (!SessionMetaProtocol.shouldSendDeliveryReceipt(Address.fromSerialized(address))) { return; }
|
|
||||||
|
|
||||||
messageSender.sendReceipt(remoteAddress,
|
messageSender.sendReceipt(remoteAddress,
|
||||||
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)),
|
UnidentifiedAccessUtil.getAccessFor(context, Recipient.from(context, Address.fromSerialized(address), false)),
|
||||||
receiptMessage);
|
receiptMessage);
|
||||||
|
@ -13,8 +13,10 @@ import java.io.File
|
|||||||
class LinkedDevicesLoader(context: Context) : AsyncLoader<List<Device>>(context) {
|
class LinkedDevicesLoader(context: Context) : AsyncLoader<List<Device>>(context) {
|
||||||
|
|
||||||
private val mnemonicCodec by lazy {
|
private val mnemonicCodec by lazy {
|
||||||
val languageFileDirectory = File(context.applicationInfo.dataDir)
|
val loadFileContents: (String) -> String = { fileName ->
|
||||||
MnemonicCodec(languageFileDirectory)
|
MnemonicUtilities.loadFileContents(context, fileName)
|
||||||
|
}
|
||||||
|
MnemonicCodec(loadFileContents)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadInBackground(): List<Device>? {
|
override fun loadInBackground(): List<Device>? {
|
||||||
|
@ -142,7 +142,11 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
private fun getPathRow(snode: Snode, location: LineView.Location, dotAnimationStartDelay: Long, dotAnimationRepeatInterval: Long, isGuardSnode: Boolean): LinearLayout {
|
private fun getPathRow(snode: Snode, location: LineView.Location, dotAnimationStartDelay: Long, dotAnimationRepeatInterval: Long, isGuardSnode: Boolean): LinearLayout {
|
||||||
val title = if (isGuardSnode) resources.getString(R.string.activity_path_guard_node_row_title) else resources.getString(R.string.activity_path_service_node_row_title)
|
val title = if (isGuardSnode) resources.getString(R.string.activity_path_guard_node_row_title) else resources.getString(R.string.activity_path_service_node_row_title)
|
||||||
val subtitle = IP2Country.shared.countryNamesCache[snode.ip] ?: "Resolving..."
|
val subtitle = if (IP2Country.isInitialized) {
|
||||||
|
IP2Country.shared.countryNamesCache[snode.ip] ?: "Resolving..."
|
||||||
|
} else {
|
||||||
|
"Resolving..."
|
||||||
|
}
|
||||||
return getPathRow(title, subtitle, location, dotAnimationStartDelay, dotAnimationRepeatInterval)
|
return getPathRow(title, subtitle, location, dotAnimationStartDelay, dotAnimationRepeatInterval)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -44,7 +44,6 @@ class RegisterActivity : BaseActionBarActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_register)
|
setContentView(R.layout.activity_register)
|
||||||
setUpLanguageFileDirectory()
|
|
||||||
setUpActionBarSessionLogo()
|
setUpActionBarSessionLogo()
|
||||||
registerButton.setOnClickListener { register() }
|
registerButton.setOnClickListener { register() }
|
||||||
copyButton.setOnClickListener { copyPublicKey() }
|
copyButton.setOnClickListener { copyPublicKey() }
|
||||||
@ -69,28 +68,6 @@ class RegisterActivity : BaseActionBarActivity() {
|
|||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region General
|
|
||||||
private fun setUpLanguageFileDirectory() {
|
|
||||||
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
|
|
||||||
val directory = File(applicationInfo.dataDir)
|
|
||||||
for (language in languages) {
|
|
||||||
val fileName = "$language.txt"
|
|
||||||
if (directory.list().contains(fileName)) { continue }
|
|
||||||
val inputStream = assets.open("mnemonic/$fileName")
|
|
||||||
val file = File(directory, fileName)
|
|
||||||
val outputStream = FileOutputStream(file)
|
|
||||||
val buffer = ByteArray(1024)
|
|
||||||
while (true) {
|
|
||||||
val count = inputStream.read(buffer)
|
|
||||||
if (count < 0) { break }
|
|
||||||
outputStream.write(buffer, 0, count)
|
|
||||||
}
|
|
||||||
inputStream.close()
|
|
||||||
outputStream.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Updating
|
// region Updating
|
||||||
private fun updateKeyPair() {
|
private fun updateKeyPair() {
|
||||||
val seedCandidate = Curve25519.getInstance(Curve25519.BEST).generateSeed(16)
|
val seedCandidate = Curve25519.getInstance(Curve25519.BEST).generateSeed(16)
|
||||||
|
@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
|||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
|
||||||
import org.thoughtcrime.securesms.loki.utilities.push
|
import org.thoughtcrime.securesms.loki.utilities.push
|
||||||
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
|
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
|
||||||
import org.thoughtcrime.securesms.util.Base64
|
import org.thoughtcrime.securesms.util.Base64
|
||||||
@ -31,12 +32,10 @@ import java.io.File
|
|||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
class RestoreActivity : BaseActionBarActivity() {
|
class RestoreActivity : BaseActionBarActivity() {
|
||||||
private lateinit var languageFileDirectory: File
|
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setUpLanguageFileDirectory()
|
|
||||||
setUpActionBarSessionLogo()
|
setUpActionBarSessionLogo()
|
||||||
setContentView(R.layout.activity_restore)
|
setContentView(R.layout.activity_restore)
|
||||||
mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard
|
mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard
|
||||||
@ -61,34 +60,14 @@ class RestoreActivity : BaseActionBarActivity() {
|
|||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region General
|
|
||||||
private fun setUpLanguageFileDirectory() {
|
|
||||||
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
|
|
||||||
val directory = File(applicationInfo.dataDir)
|
|
||||||
for (language in languages) {
|
|
||||||
val fileName = "$language.txt"
|
|
||||||
if (directory.list().contains(fileName)) { continue }
|
|
||||||
val inputStream = assets.open("mnemonic/$fileName")
|
|
||||||
val file = File(directory, fileName)
|
|
||||||
val outputStream = FileOutputStream(file)
|
|
||||||
val buffer = ByteArray(1024)
|
|
||||||
while (true) {
|
|
||||||
val count = inputStream.read(buffer)
|
|
||||||
if (count < 0) { break }
|
|
||||||
outputStream.write(buffer, 0, count)
|
|
||||||
}
|
|
||||||
inputStream.close()
|
|
||||||
outputStream.close()
|
|
||||||
}
|
|
||||||
languageFileDirectory = directory
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Interaction
|
// region Interaction
|
||||||
private fun restore() {
|
private fun restore() {
|
||||||
val mnemonic = mnemonicEditText.text.toString()
|
val mnemonic = mnemonicEditText.text.toString()
|
||||||
try {
|
try {
|
||||||
val hexEncodedSeed = MnemonicCodec(languageFileDirectory).decode(mnemonic)
|
val loadFileContents: (String) -> String = { fileName ->
|
||||||
|
MnemonicUtilities.loadFileContents(this, fileName)
|
||||||
|
}
|
||||||
|
val hexEncodedSeed = MnemonicCodec(loadFileContents).decode(mnemonic)
|
||||||
var seed = Hex.fromStringCondensed(hexEncodedSeed)
|
var seed = Hex.fromStringCondensed(hexEncodedSeed)
|
||||||
IdentityKeyUtil.save(this, IdentityKeyUtil.lokiSeedKey, Hex.toStringCondensed(seed))
|
IdentityKeyUtil.save(this, IdentityKeyUtil.lokiSeedKey, Hex.toStringCondensed(seed))
|
||||||
if (seed.size == 16) { seed = seed + seed }
|
if (seed.size == 16) { seed = seed + seed }
|
||||||
|
@ -7,12 +7,14 @@ import android.os.Bundle
|
|||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
|
import android.util.Log
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import kotlinx.android.synthetic.main.activity_seed.*
|
import kotlinx.android.synthetic.main.activity_seed.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
|
||||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||||
@ -22,12 +24,14 @@ import java.io.File
|
|||||||
class SeedActivity : BaseActionBarActivity() {
|
class SeedActivity : BaseActionBarActivity() {
|
||||||
|
|
||||||
private val seed by lazy {
|
private val seed by lazy {
|
||||||
val languageFileDirectory = File(applicationInfo.dataDir)
|
|
||||||
var hexEncodedSeed = IdentityKeyUtil.retrieve(this, IdentityKeyUtil.lokiSeedKey)
|
var hexEncodedSeed = IdentityKeyUtil.retrieve(this, IdentityKeyUtil.lokiSeedKey)
|
||||||
if (hexEncodedSeed == null) {
|
if (hexEncodedSeed == null) {
|
||||||
hexEncodedSeed = IdentityKeyUtil.getIdentityKeyPair(this).hexEncodedPrivateKey // Legacy account
|
hexEncodedSeed = IdentityKeyUtil.getIdentityKeyPair(this).hexEncodedPrivateKey // Legacy account
|
||||||
}
|
}
|
||||||
MnemonicCodec(languageFileDirectory).encode(hexEncodedSeed!!, MnemonicCodec.Language.Configuration.english)
|
val loadFileContents: (String) -> String = { fileName ->
|
||||||
|
MnemonicUtilities.loadFileContents(this, fileName)
|
||||||
|
}
|
||||||
|
MnemonicCodec(loadFileContents).encode(hexEncodedSeed!!, MnemonicCodec.Language.Configuration.english)
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
|
@ -38,10 +38,10 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
@JvmStatic val createLastMessageHashValueTable2Command
|
@JvmStatic val createLastMessageHashValueTable2Command
|
||||||
= "CREATE TABLE $lastMessageHashValueTable2 ($snode TEXT, $publicKey TEXT, $lastMessageHashValue TEXT, PRIMARY KEY ($snode, $publicKey));"
|
= "CREATE TABLE $lastMessageHashValueTable2 ($snode TEXT, $publicKey TEXT, $lastMessageHashValue TEXT, PRIMARY KEY ($snode, $publicKey));"
|
||||||
// Received message hash values
|
// Received message hash values
|
||||||
private val receivedMessageHashValuesTable2 = "received_message_hash_values_table"
|
private val receivedMessageHashValuesTable3 = "received_message_hash_values_table_3"
|
||||||
private val receivedMessageHashValues = "received_message_hash_values"
|
private val receivedMessageHashValues = "received_message_hash_values"
|
||||||
@JvmStatic val createReceivedMessageHashValuesTable2Command
|
@JvmStatic val createReceivedMessageHashValuesTable3Command
|
||||||
= "CREATE TABLE $receivedMessageHashValuesTable2 ($snode STRING, $publicKey STRING, $receivedMessageHashValues TEXT, PRIMARY KEY ($snode, $publicKey));"
|
= "CREATE TABLE $receivedMessageHashValuesTable3 ($publicKey STRING PRIMARY KEY, $receivedMessageHashValues TEXT);"
|
||||||
// Open group auth tokens
|
// Open group auth tokens
|
||||||
private val openGroupAuthTokenTable = "loki_api_group_chat_auth_token_database"
|
private val openGroupAuthTokenTable = "loki_api_group_chat_auth_token_database"
|
||||||
private val server = "server"
|
private val server = "server"
|
||||||
@ -215,9 +215,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
|
|
||||||
override fun getReceivedMessageHashValues(publicKey: String): Set<String>? {
|
override fun getReceivedMessageHashValues(publicKey: String): Set<String>? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
val query = "$Companion.publicKey = ?"
|
val query = "${Companion.publicKey} = ?"
|
||||||
return database.get(receivedMessageHashValuesTable2, query, arrayOf( publicKey )) { cursor ->
|
return database.get(receivedMessageHashValuesTable3, query, arrayOf( publicKey )) { cursor ->
|
||||||
val receivedMessageHashValuesAsString = cursor.getString(cursor.getColumnIndexOrThrow(receivedMessageHashValues))
|
val receivedMessageHashValuesAsString = cursor.getString(cursor.getColumnIndexOrThrow(Companion.receivedMessageHashValues))
|
||||||
receivedMessageHashValuesAsString.split("-").toSet()
|
receivedMessageHashValuesAsString.split("-").toSet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,9 +225,9 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
override fun setReceivedMessageHashValues(publicKey: String, newValue: Set<String>) {
|
override fun setReceivedMessageHashValues(publicKey: String, newValue: Set<String>) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val receivedMessageHashValuesAsString = newValue.joinToString("-")
|
val receivedMessageHashValuesAsString = newValue.joinToString("-")
|
||||||
val row = wrap(mapOf( Companion.publicKey to publicKey, receivedMessageHashValues to receivedMessageHashValuesAsString ))
|
val row = wrap(mapOf( Companion.publicKey to publicKey, Companion.receivedMessageHashValues to receivedMessageHashValuesAsString ))
|
||||||
val query = "$Companion.publicKey = ?"
|
val query = "${Companion.publicKey} = ?"
|
||||||
database.insertOrUpdate(receivedMessageHashValuesTable2, row, query, arrayOf( publicKey ))
|
database.insertOrUpdate(receivedMessageHashValuesTable3, row, query, arrayOf( publicKey ))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAuthToken(server: String): String? {
|
override fun getAuthToken(server: String): String? {
|
||||||
|
@ -29,16 +29,15 @@ import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.Device
|
|||||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLinkingSessionListener
|
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLinkingSessionListener
|
||||||
|
|
||||||
class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListener {
|
class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListener {
|
||||||
private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) }
|
|
||||||
private lateinit var contentView: View
|
private lateinit var contentView: View
|
||||||
private var deviceLink: DeviceLink? = null
|
private var deviceLink: DeviceLink? = null
|
||||||
var delegate: LinkDeviceMasterModeDialogDelegate? = null
|
var delegate: LinkDeviceMasterModeDialogDelegate? = null
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val builder = AlertDialog.Builder(context!!)
|
val builder = AlertDialog.Builder(requireContext())
|
||||||
contentView = LayoutInflater.from(context!!).inflate(R.layout.dialog_link_device_master_mode, null)
|
contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_link_device_master_mode, null)
|
||||||
val size = toPx(128, resources)
|
val size = toPx(128, resources)
|
||||||
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context!!)
|
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(requireContext())
|
||||||
val qrCode = QRCodeUtilities.encode(hexEncodedPublicKey, size, false, false)
|
val qrCode = QRCodeUtilities.encode(hexEncodedPublicKey, size, false, false)
|
||||||
contentView.qrCodeImageView.setImageBitmap(qrCode)
|
contentView.qrCodeImageView.setImageBitmap(qrCode)
|
||||||
contentView.cancelButton.setOnClickListener { onDeviceLinkCanceled() }
|
contentView.cancelButton.setOnClickListener { onDeviceLinkCanceled() }
|
||||||
@ -52,7 +51,7 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun requestUserAuthorization(deviceLink: DeviceLink) {
|
override fun requestUserAuthorization(deviceLink: DeviceLink) {
|
||||||
if (deviceLink.type != DeviceLink.Type.REQUEST || deviceLink.masterPublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
|
if (deviceLink.type != DeviceLink.Type.REQUEST || deviceLink.masterPublicKey != TextSecurePreferences.getLocalNumber(requireContext()) || this.deviceLink != null) { return }
|
||||||
Util.runOnMain {
|
Util.runOnMain {
|
||||||
this.deviceLink = deviceLink
|
this.deviceLink = deviceLink
|
||||||
contentView.qrCodeImageView.visibility = View.GONE
|
contentView.qrCodeImageView.visibility = View.GONE
|
||||||
@ -62,7 +61,10 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
|||||||
contentView.titleTextView.text = resources.getString(R.string.dialog_link_device_master_mode_title_2)
|
contentView.titleTextView.text = resources.getString(R.string.dialog_link_device_master_mode_title_2)
|
||||||
contentView.explanationTextView.text = resources.getString(R.string.dialog_link_device_master_mode_explanation_2)
|
contentView.explanationTextView.text = resources.getString(R.string.dialog_link_device_master_mode_explanation_2)
|
||||||
contentView.mnemonicTextView.visibility = View.VISIBLE
|
contentView.mnemonicTextView.visibility = View.VISIBLE
|
||||||
contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), deviceLink.slavePublicKey)
|
val loadFileContents: (String) -> String = { fileName ->
|
||||||
|
MnemonicUtilities.loadFileContents(requireContext(), fileName)
|
||||||
|
}
|
||||||
|
contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(loadFileContents), deviceLink.slavePublicKey)
|
||||||
contentView.authorizeButton.visibility = View.VISIBLE
|
contentView.authorizeButton.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,15 +87,15 @@ class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListene
|
|||||||
contentView.authorizeButton.visibility = View.GONE
|
contentView.authorizeButton.visibility = View.GONE
|
||||||
}
|
}
|
||||||
FileServerAPI.shared.addDeviceLink(deviceLink).bind(SnodeAPI.sharedContext) {
|
FileServerAPI.shared.addDeviceLink(deviceLink).bind(SnodeAPI.sharedContext) {
|
||||||
MultiDeviceProtocol.signAndSendDeviceLinkMessage(context!!, deviceLink)
|
MultiDeviceProtocol.signAndSendDeviceLinkMessage(requireContext(), deviceLink)
|
||||||
}.success {
|
}.success {
|
||||||
TextSecurePreferences.setMultiDevice(context!!, true)
|
TextSecurePreferences.setMultiDevice(requireContext(), true)
|
||||||
}.successUi {
|
}.successUi {
|
||||||
delegate?.onDeviceLinkRequestAuthorized()
|
delegate?.onDeviceLinkRequestAuthorized()
|
||||||
dismiss()
|
dismiss()
|
||||||
}.fail {
|
}.fail {
|
||||||
FileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
|
FileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
|
||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context!!).removePreKeyBundle(deviceLink.slavePublicKey)
|
DatabaseFactory.getLokiPreKeyBundleDatabase(requireContext()).removePreKeyBundle(deviceLink.slavePublicKey)
|
||||||
}.failUi {
|
}.failUi {
|
||||||
delegate?.onDeviceLinkAuthorizationFailed()
|
delegate?.onDeviceLinkAuthorizationFailed()
|
||||||
dismiss()
|
dismiss()
|
||||||
|
@ -21,16 +21,18 @@ import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.Device
|
|||||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLinkingSessionListener
|
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLinkingSessionListener
|
||||||
|
|
||||||
class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener {
|
class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener {
|
||||||
private val languageFileDirectory by lazy { MnemonicUtilities.getLanguageFileDirectory(context!!) }
|
|
||||||
private lateinit var contentView: View
|
private lateinit var contentView: View
|
||||||
private var deviceLink: DeviceLink? = null
|
private var deviceLink: DeviceLink? = null
|
||||||
var delegate: LinkDeviceSlaveModeDialogDelegate? = null
|
var delegate: LinkDeviceSlaveModeDialogDelegate? = null
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val builder = AlertDialog.Builder(context!!)
|
val builder = AlertDialog.Builder(requireContext())
|
||||||
contentView = LayoutInflater.from(context!!).inflate(R.layout.dialog_link_device_slave_mode, null)
|
contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_link_device_slave_mode, null)
|
||||||
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), hexEncodedPublicKey)
|
val loadFileContents: (String) -> String = { fileName ->
|
||||||
|
MnemonicUtilities.loadFileContents(requireContext(), fileName)
|
||||||
|
}
|
||||||
|
contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(loadFileContents), hexEncodedPublicKey)
|
||||||
contentView.cancelButton.setOnClickListener { onDeviceLinkCanceled() }
|
contentView.cancelButton.setOnClickListener { onDeviceLinkCanceled() }
|
||||||
builder.setView(contentView)
|
builder.setView(contentView)
|
||||||
DeviceLinkingSession.shared.startListeningForLinkingRequests()
|
DeviceLinkingSession.shared.startListeningForLinkingRequests()
|
||||||
@ -41,7 +43,7 @@ class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
|
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
|
||||||
if (deviceLink.type != DeviceLink.Type.AUTHORIZATION || deviceLink.slavePublicKey != TextSecurePreferences.getLocalNumber(context!!) || this.deviceLink != null) { return }
|
if (deviceLink.type != DeviceLink.Type.AUTHORIZATION || deviceLink.slavePublicKey != TextSecurePreferences.getLocalNumber(requireContext()) || this.deviceLink != null) { return }
|
||||||
Util.runOnMain {
|
Util.runOnMain {
|
||||||
this.deviceLink = deviceLink
|
this.deviceLink = deviceLink
|
||||||
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
|
||||||
|
@ -14,6 +14,7 @@ import android.widget.Toast
|
|||||||
import kotlinx.android.synthetic.main.dialog_seed.view.*
|
import kotlinx.android.synthetic.main.dialog_seed.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||||
import org.whispersystems.signalservice.loki.utilities.hexEncodedPrivateKey
|
import org.whispersystems.signalservice.loki.utilities.hexEncodedPrivateKey
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -21,17 +22,19 @@ import java.io.File
|
|||||||
class SeedDialog : DialogFragment() {
|
class SeedDialog : DialogFragment() {
|
||||||
|
|
||||||
private val seed by lazy {
|
private val seed by lazy {
|
||||||
val languageFileDirectory = File(context!!.applicationInfo.dataDir)
|
var hexEncodedSeed = IdentityKeyUtil.retrieve(requireContext(), IdentityKeyUtil.lokiSeedKey)
|
||||||
var hexEncodedSeed = IdentityKeyUtil.retrieve(context!!, IdentityKeyUtil.lokiSeedKey)
|
|
||||||
if (hexEncodedSeed == null) {
|
if (hexEncodedSeed == null) {
|
||||||
hexEncodedSeed = IdentityKeyUtil.getIdentityKeyPair(context!!).hexEncodedPrivateKey // Legacy account
|
hexEncodedSeed = IdentityKeyUtil.getIdentityKeyPair(requireContext()).hexEncodedPrivateKey // Legacy account
|
||||||
}
|
}
|
||||||
MnemonicCodec(languageFileDirectory).encode(hexEncodedSeed!!, MnemonicCodec.Language.Configuration.english)
|
val loadFileContents: (String) -> String = { fileName ->
|
||||||
|
MnemonicUtilities.loadFileContents(requireContext(), fileName)
|
||||||
|
}
|
||||||
|
MnemonicCodec(loadFileContents).encode(hexEncodedSeed!!, MnemonicCodec.Language.Configuration.english)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val builder = AlertDialog.Builder(context!!)
|
val builder = AlertDialog.Builder(requireContext())
|
||||||
val contentView = LayoutInflater.from(context!!).inflate(R.layout.dialog_seed, null)
|
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_seed, null)
|
||||||
contentView.seedTextView.text = seed
|
contentView.seedTextView.text = seed
|
||||||
contentView.cancelButton.setOnClickListener { dismiss() }
|
contentView.cancelButton.setOnClickListener { dismiss() }
|
||||||
contentView.copyButton.setOnClickListener { copySeed() }
|
contentView.copyButton.setOnClickListener { copySeed() }
|
||||||
@ -42,10 +45,10 @@ class SeedDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copySeed() {
|
private fun copySeed() {
|
||||||
val clipboard = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
val clipboard = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
val clip = ClipData.newPlainText("Seed", seed)
|
val clip = ClipData.newPlainText("Seed", seed)
|
||||||
clipboard.setPrimaryClip(clip)
|
clipboard.setPrimaryClip(clip)
|
||||||
Toast.makeText(context!!, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -218,7 +218,7 @@ object ClosedGroupsProtocol {
|
|||||||
public fun handleSharedSenderKeysUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) {
|
public fun handleSharedSenderKeysUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) {
|
||||||
if (!isValid(closedGroupUpdate)) { return; }
|
if (!isValid(closedGroupUpdate)) { return; }
|
||||||
when (closedGroupUpdate.type) {
|
when (closedGroupUpdate.type) {
|
||||||
SignalServiceProtos.ClosedGroupUpdate.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate)
|
SignalServiceProtos.ClosedGroupUpdate.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey)
|
||||||
SignalServiceProtos.ClosedGroupUpdate.Type.INFO -> handleClosedGroupUpdate(context, closedGroupUpdate, senderPublicKey)
|
SignalServiceProtos.ClosedGroupUpdate.Type.INFO -> handleClosedGroupUpdate(context, closedGroupUpdate, senderPublicKey)
|
||||||
SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY_REQUEST -> handleSenderKeyRequest(context, closedGroupUpdate, senderPublicKey)
|
SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY_REQUEST -> handleSenderKeyRequest(context, closedGroupUpdate, senderPublicKey)
|
||||||
SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY -> handleSenderKey(context, closedGroupUpdate, senderPublicKey)
|
SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY -> handleSenderKey(context, closedGroupUpdate, senderPublicKey)
|
||||||
@ -244,7 +244,7 @@ object ClosedGroupsProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun handleNewClosedGroup(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate) {
|
public fun handleNewClosedGroup(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) {
|
||||||
// Prepare
|
// Prepare
|
||||||
val sskDatabase = DatabaseFactory.getSSKDatabase(context)
|
val sskDatabase = DatabaseFactory.getSSKDatabase(context)
|
||||||
// Unwrap the message
|
// Unwrap the message
|
||||||
@ -270,7 +270,7 @@ object ClosedGroupsProtocol {
|
|||||||
// Add the group to the user's set of public keys to poll for
|
// Add the group to the user's set of public keys to poll for
|
||||||
sskDatabase.setClosedGroupPrivateKey(groupPublicKey, groupPrivateKey.toHexString())
|
sskDatabase.setClosedGroupPrivateKey(groupPublicKey, groupPrivateKey.toHexString())
|
||||||
// Notify the user
|
// Notify the user
|
||||||
insertIncomingInfoMessage(context, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
||||||
// Establish sessions if needed
|
// Establish sessions if needed
|
||||||
establishSessionsWithMembersIfNeeded(context, members)
|
establishSessionsWithMembersIfNeeded(context, members)
|
||||||
}
|
}
|
||||||
@ -311,6 +311,7 @@ object ClosedGroupsProtocol {
|
|||||||
// • Remove the group from the user's set of public keys to poll for if the current user was among the members that were removed
|
// • Remove the group from the user's set of public keys to poll for if the current user was among the members that were removed
|
||||||
val wasCurrentUserRemoved = !members.contains(userPublicKey)
|
val wasCurrentUserRemoved = !members.contains(userPublicKey)
|
||||||
val wasAnyUserRemoved = members.toSet().intersect(oldMembers) != oldMembers.toSet()
|
val wasAnyUserRemoved = members.toSet().intersect(oldMembers) != oldMembers.toSet()
|
||||||
|
val wasSenderRemoved = !members.contains(senderPublicKey)
|
||||||
if (wasAnyUserRemoved) {
|
if (wasAnyUserRemoved) {
|
||||||
sskDatabase.removeAllClosedGroupRatchets(groupPublicKey)
|
sskDatabase.removeAllClosedGroupRatchets(groupPublicKey)
|
||||||
if (wasCurrentUserRemoved) {
|
if (wasCurrentUserRemoved) {
|
||||||
@ -331,9 +332,9 @@ object ClosedGroupsProtocol {
|
|||||||
groupDB.updateTitle(groupID, name)
|
groupDB.updateTitle(groupID, name)
|
||||||
groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) })
|
groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) })
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val type0 = if (wasAnyUserRemoved) GroupContext.Type.QUIT else GroupContext.Type.UPDATE
|
val type0 = if (wasSenderRemoved) GroupContext.Type.QUIT else GroupContext.Type.UPDATE
|
||||||
val type1 = if (wasAnyUserRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
|
val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
|
||||||
insertIncomingInfoMessage(context, groupID, type0, type1, name, members, admins)
|
insertIncomingInfoMessage(context, senderPublicKey, groupID, type0, type1, name, members, admins)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun handleSenderKeyRequest(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) {
|
public fun handleSenderKeyRequest(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) {
|
||||||
@ -410,8 +411,13 @@ object ClosedGroupsProtocol {
|
|||||||
if (GroupUtil.isOpenGroup(groupID)) {
|
if (GroupUtil.isOpenGroup(groupID)) {
|
||||||
return listOf( Address.fromSerialized(groupID) )
|
return listOf( Address.fromSerialized(groupID) )
|
||||||
} else {
|
} else {
|
||||||
val groupPublicKey = doubleDecodeGroupID(groupID).toHexString()
|
var groupPublicKey: String? = null
|
||||||
if (DatabaseFactory.getSSKDatabase(context).isSSKBasedClosedGroup(groupPublicKey)) {
|
try {
|
||||||
|
groupPublicKey = doubleDecodeGroupID(groupID).toHexString()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
if (groupPublicKey != null && DatabaseFactory.getSSKDatabase(context).isSSKBasedClosedGroup(groupPublicKey)) {
|
||||||
return listOf( Address.fromSerialized(groupPublicKey) )
|
return listOf( Address.fromSerialized(groupPublicKey) )
|
||||||
} else {
|
} else {
|
||||||
return DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, false).map { it.address }
|
return DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, false).map { it.address }
|
||||||
@ -475,8 +481,8 @@ object ClosedGroupsProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertIncomingInfoMessage(context: Context, groupID: String, type0: GroupContext.Type, type1: SignalServiceGroup.Type, name: String,
|
private fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: GroupContext.Type, type1: SignalServiceGroup.Type,
|
||||||
members: Collection<String>, admins: Collection<String>) {
|
name: String, members: Collection<String>, admins: Collection<String>) {
|
||||||
val groupContextBuilder = GroupContext.newBuilder()
|
val groupContextBuilder = GroupContext.newBuilder()
|
||||||
.setId(ByteString.copyFrom(GroupUtil.getDecodedId(groupID)))
|
.setId(ByteString.copyFrom(GroupUtil.getDecodedId(groupID)))
|
||||||
.setType(type0)
|
.setType(type0)
|
||||||
@ -484,7 +490,7 @@ object ClosedGroupsProtocol {
|
|||||||
.addAllMembers(members)
|
.addAllMembers(members)
|
||||||
.addAllAdmins(admins)
|
.addAllAdmins(admins)
|
||||||
val group = SignalServiceGroup(type1, GroupUtil.getDecodedId(groupID), GroupType.SIGNAL, name, members.toList(), null, admins.toList())
|
val group = SignalServiceGroup(type1, GroupUtil.getDecodedId(groupID), GroupType.SIGNAL, name, members.toList(), null, admins.toList())
|
||||||
val m = IncomingTextMessage(Address.fromSerialized(groupID), 1, System.currentTimeMillis(), "", Optional.of(group), 0, true)
|
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, System.currentTimeMillis(), "", Optional.of(group), 0, true)
|
||||||
val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "")
|
val infoMessage = IncomingGroupMessage(m, groupContextBuilder.build(), "")
|
||||||
val smsDB = DatabaseFactory.getSmsDatabase(context)
|
val smsDB = DatabaseFactory.getSmsDatabase(context)
|
||||||
smsDB.insertMessageInbox(infoMessage)
|
smsDB.insertMessageInbox(infoMessage)
|
||||||
|
@ -79,8 +79,12 @@ object SessionMetaProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun shouldSendDeliveryReceipt(address: Address): Boolean {
|
fun shouldSendDeliveryReceipt(message: SignalServiceDataMessage, address: Address): Boolean {
|
||||||
return !address.isGroup
|
if (address.isGroup) { return false }
|
||||||
|
val hasBody = message.body.isPresent && message.body.get().isNotEmpty()
|
||||||
|
val hasAttachment = message.attachments.isPresent && message.attachments.get().isNotEmpty()
|
||||||
|
val hasLinkPreview = message.previews.isPresent && message.previews.get().isNotEmpty()
|
||||||
|
return hasBody || hasAttachment || hasLinkPreview
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,7 +19,3 @@ fun toPx(dp: Int, resources: Resources): Int {
|
|||||||
val scale = resources.displayMetrics.density
|
val scale = resources.displayMetrics.density
|
||||||
return (dp * scale).roundToInt()
|
return (dp * scale).roundToInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPublicChat(context: Context, recipient: String): Boolean {
|
|
||||||
return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().values.map { it.server }.contains(recipient)
|
|
||||||
}
|
|
||||||
|
@ -29,8 +29,10 @@ class IP2Country private constructor(private val context: Context) {
|
|||||||
|
|
||||||
public lateinit var shared: IP2Country
|
public lateinit var shared: IP2Country
|
||||||
|
|
||||||
|
public val isInitialized: Boolean get() = ::shared.isInitialized
|
||||||
|
|
||||||
public fun configureIfNeeded(context: Context) {
|
public fun configureIfNeeded(context: Context) {
|
||||||
if (::shared.isInitialized) { return; }
|
if (isInitialized) { return; }
|
||||||
shared = IP2Country(context)
|
shared = IP2Country(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,30 +8,17 @@ import java.io.FileOutputStream
|
|||||||
|
|
||||||
object MnemonicUtilities {
|
object MnemonicUtilities {
|
||||||
|
|
||||||
@JvmStatic
|
public fun loadFileContents(context: Context, fileName: String): String {
|
||||||
public fun getLanguageFileDirectory(context: Context): File {
|
val inputStream = context.assets.open("mnemonic/$fileName.txt")
|
||||||
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
|
val size = inputStream.available()
|
||||||
val directory = File(context.applicationInfo.dataDir)
|
val buffer = ByteArray(size)
|
||||||
for (language in languages) {
|
inputStream.read(buffer)
|
||||||
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()
|
inputStream.close()
|
||||||
outputStream.close()
|
return String(buffer)
|
||||||
}
|
|
||||||
return directory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun getFirst3Words(codec: MnemonicCodec, hexEncodedPublicKey: String): String {
|
public fun getFirst3Words(codec: MnemonicCodec, hexEncodedPublicKey: String): String {
|
||||||
return codec.encode(hexEncodedPublicKey.removing05PrefixIfNeeded()).split(" ").slice(0 until 3).joinToString(" ")
|
return codec.encode(hexEncodedPublicKey.removing05PrefixIfNeeded()).split(" ").slice(0 until 3).joinToString(" ")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -23,15 +23,15 @@ object OpenGroupUtilities {
|
|||||||
val displayName = TextSecurePreferences.getProfileName(context)
|
val displayName = TextSecurePreferences.getProfileName(context)
|
||||||
val lokiPublicChatAPI = application.publicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.")
|
val lokiPublicChatAPI = application.publicChatAPI ?: throw Error("LokiPublicChatAPI is not initialized.")
|
||||||
return application.publicChatManager.addChat(url, channel).then { group ->
|
return application.publicChatManager.addChat(url, channel).then { group ->
|
||||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url)
|
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(channel, url)
|
||||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url)
|
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(channel, url)
|
||||||
lokiPublicChatAPI.getMessages(channel, url)
|
lokiPublicChatAPI.getMessages(channel, url)
|
||||||
lokiPublicChatAPI.setDisplayName(displayName, url)
|
lokiPublicChatAPI.setDisplayName(displayName, url)
|
||||||
lokiPublicChatAPI.join(channel, url)
|
lokiPublicChatAPI.join(channel, url)
|
||||||
val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(context)
|
val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(context)
|
||||||
val profileUrl: String? = TextSecurePreferences.getProfilePictureURL(context)
|
val profileUrl: String? = TextSecurePreferences.getProfilePictureURL(context)
|
||||||
lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
|
lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
|
||||||
group
|
group
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,9 +7,6 @@ import android.content.Context;
|
|||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceViewHolder;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -18,14 +15,16 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceViewHolder;
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
@ -124,8 +123,7 @@ public class ProfilePreference extends Preference {
|
|||||||
profileTagView.setVisibility(userMasterPublicKey == null ? View.GONE : View.VISIBLE);
|
profileTagView.setVisibility(userMasterPublicKey == null ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
if (userMasterPublicKey != null && shortDeviceMnemonic == null) {
|
if (userMasterPublicKey != null && shortDeviceMnemonic == null) {
|
||||||
MnemonicCodec codec = new MnemonicCodec(MnemonicUtilities.getLanguageFileDirectory(context));
|
shortDeviceMnemonic = "";
|
||||||
shortDeviceMnemonic = MnemonicUtilities.getFirst3Words(codec, userPublicKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String tag = context.getResources().getString(R.string.activity_settings_linked_device_tag);
|
String tag = context.getResources().getString(R.string.activity_settings_linked_device_tag);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user