Remove DirectoryRefresh.

This was causing crashes on some devices and it's used for accessing contacts on a device which we don't need.
This commit is contained in:
Mikunj Varsani 2020-03-13 09:35:47 +11:00
parent 9228c7399a
commit e484a95fcb
20 changed files with 25 additions and 1020 deletions

View File

@ -615,11 +615,6 @@
<action android:name="network.loki.securesms.RESTART" /> <action android:name="network.loki.securesms.RESTART" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name="org.thoughtcrime.securesms.service.DirectoryRefreshListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.service.RotateSignedPreKeyListener"> <receiver android:name="org.thoughtcrime.securesms.service.RotateSignedPreKeyListener">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />

View File

@ -75,7 +75,6 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
@ -353,7 +352,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private void initializePeriodicTasks() { private void initializePeriodicTasks() {
RotateSignedPreKeyListener.schedule(this); RotateSignedPreKeyListener.schedule(this);
DirectoryRefreshListener.schedule(this);
LocalBackupListener.schedule(this); LocalBackupListener.schedule(this);
RotateSenderCertificateListener.schedule(this); RotateSenderCertificateListener.schedule(this);
BackgroundPollWorker.schedule(this); // Session BackgroundPollWorker.schedule(this); // Session

View File

@ -16,24 +16,17 @@
*/ */
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout;
import org.thoughtcrime.securesms.components.ContactFilterToolbar; import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode; import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
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.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import java.io.IOException;
import java.lang.ref.WeakReference;
import network.loki.messenger.R; import network.loki.messenger.R;
/** /**
@ -109,44 +102,11 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
} }
@Override @Override
public void onRefresh() { public void onRefresh() { }
new RefreshDirectoryTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getApplicationContext());
}
@Override @Override
public void onContactSelected(String number) {} public void onContactSelected(String number) {}
@Override @Override
public void onContactDeselected(String number) {} public void onContactDeselected(String number) {}
private static class RefreshDirectoryTask extends AsyncTask<Context, Void, Void> {
private final WeakReference<ContactSelectionActivity> activity;
private RefreshDirectoryTask(ContactSelectionActivity activity) {
this.activity = new WeakReference<>(activity);
}
@Override
protected Void doInBackground(Context... params) {
try {
DirectoryHelper.refreshDirectory(params[0], true);
} catch (IOException e) {
Log.w(TAG, e);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
ContactSelectionActivity activity = this.activity.get();
if (activity != null && !activity.isFinishing()) {
activity.toolbar.clear();
activity.contactsFragment.resetQueryFilter();
}
}
}
} }

View File

@ -17,7 +17,6 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.database.Cursor; import android.database.Cursor;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -44,14 +43,11 @@ import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader; import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode; import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -259,12 +255,6 @@ public class ContactSelectionListFragment extends Fragment
@Override @Override
protected Boolean doInBackground(Void... voids) { protected Boolean doInBackground(Void... voids) {
try {
DirectoryHelper.refreshDirectory(getContext(), false);
return true;
} catch (IOException e) {
Log.w(TAG, e);
}
return false; return false;
} }

View File

@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.PushDecryptJob; import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
@ -250,12 +249,6 @@ public class DatabaseUpgradeActivity extends BaseActivity {
scheduleMessagesInPushDatabase(context);; scheduleMessagesInPushDatabase(context);;
} }
if (params[0] < CONTACTS_ACCOUNT_VERSION) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(false));
}
if (params[0] < MEDIA_DOWNLOAD_CONTROLS_VERSION) { if (params[0] < MEDIA_DOWNLOAD_CONTROLS_VERSION) {
schedulePendingIncomingParts(context); schedulePendingIncomingParts(context);
} }
@ -264,15 +257,6 @@ public class DatabaseUpgradeActivity extends BaseActivity {
ApplicationContext.getInstance(getApplicationContext()) ApplicationContext.getInstance(getApplicationContext())
.getJobManager() .getJobManager()
.add(new RefreshAttributesJob()); .add(new RefreshAttributesJob());
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(false));
}
if (params[0] < PROFILES) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(false));
} }
if (params[0] < SCREENSHOTS) { if (params[0] < SCREENSHOTS) {

View File

@ -49,7 +49,6 @@ 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.database.NoExternalStorageException; import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob; import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.lock.RegistrationLockReminders; import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
@ -57,7 +56,6 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.registration.CaptchaActivity; import org.thoughtcrime.securesms.registration.CaptchaActivity;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.util.BackupUtil; import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
@ -648,10 +646,8 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
} }
private void handleSuccessfulRegistration() { private void handleSuccessfulRegistration() {
ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new DirectoryRefreshJob(false));
ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new RotateCertificateJob(RegistrationActivity.this)); ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new RotateCertificateJob(RegistrationActivity.this));
DirectoryRefreshListener.schedule(RegistrationActivity.this);
RotateSignedPreKeyListener.schedule(RegistrationActivity.this); RotateSignedPreKeyListener.schedule(RegistrationActivity.this);
Intent nextIntent = getIntent().getParcelableExtra("next_intent"); Intent nextIntent = getIntent().getParcelableExtra("next_intent");

View File

@ -16,31 +16,19 @@
*/ */
package org.thoughtcrime.securesms.contacts; package org.thoughtcrime.securesms.contacts;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.database.MergeCursor;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set;
import network.loki.messenger.R;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
@ -58,91 +46,14 @@ import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
public class ContactAccessor { public class ContactAccessor {
public static final String PUSH_COLUMN = "push";
private static final ContactAccessor instance = new ContactAccessor(); private static final ContactAccessor instance = new ContactAccessor();
public static synchronized ContactAccessor getInstance() { public static synchronized ContactAccessor getInstance() {
return instance; return instance;
} }
public Set<Address> getAllContactsWithNumbers(Context context) {
Set<Address> results = new HashSet<>();
try (Cursor cursor = context.getContentResolver().query(Phone.CONTENT_URI, new String[] {Phone.NUMBER}, null ,null, null)) {
while (cursor != null && cursor.moveToNext()) {
if (!TextUtils.isEmpty(cursor.getString(0))) {
results.add(Address.fromExternal(context, cursor.getString(0)));
}
}
}
return results;
}
public Cursor getAllSystemContacts(Context context) {
return context.getContentResolver().query(Phone.CONTENT_URI, new String[] {Phone.NUMBER, Phone.DISPLAY_NAME, Phone.LABEL, Phone.PHOTO_URI, Phone._ID, Phone.LOOKUP_KEY}, null, null, null);
}
public boolean isSystemContact(Context context, String number) {
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
String[] projection = new String[]{PhoneLookup.DISPLAY_NAME, PhoneLookup.LOOKUP_KEY,
PhoneLookup._ID, PhoneLookup.NUMBER};
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
return true;
}
} finally {
if (cursor != null) cursor.close();
}
return false;
}
public Collection<ContactData> getContactsWithPush(Context context) {
final ContentResolver resolver = context.getContentResolver();
final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME};
final List<Address> registeredAddresses = DatabaseFactory.getRecipientDatabase(context).getRegistered();
final Collection<ContactData> lookupData = new ArrayList<>(registeredAddresses.size());
for (Address registeredAddress : registeredAddresses) {
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(registeredAddress.serialize()));
Cursor lookupCursor = resolver.query(uri, inProjection, null, null, null);
try {
if (lookupCursor != null && lookupCursor.moveToFirst()) {
final ContactData contactData = new ContactData(lookupCursor.getLong(0), lookupCursor.getString(1));
contactData.numbers.add(new NumberData("TextSecure", registeredAddress.serialize()));
lookupData.add(contactData);
}
} finally {
if (lookupCursor != null)
lookupCursor.close();
}
}
return lookupData;
}
public String getNameFromContact(Context context, Uri uri) { public String getNameFromContact(Context context, Uri uri) {
Cursor cursor = null; return "Anonymous";
try {
cursor = context.getContentResolver().query(uri, new String[] {Contacts.DISPLAY_NAME},
null, null, null);
if (cursor != null && cursor.moveToFirst())
return cursor.getString(0);
} finally {
if (cursor != null)
cursor.close();
}
return null;
} }
public ContactData getContactData(Context context, Uri uri) { public ContactData getContactData(Context context, Uri uri) {
@ -150,47 +61,11 @@ public class ContactAccessor {
} }
private ContactData getContactData(Context context, String displayName, long id) { private ContactData getContactData(Context context, String displayName, long id) {
ContactData contactData = new ContactData(id, displayName); return new ContactData(id, displayName);
Cursor numberCursor = null;
try {
numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null,
Phone.CONTACT_ID + " = ?",
new String[] {contactData.id + ""}, null);
while (numberCursor != null && numberCursor.moveToNext()) {
int type = numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE));
String label = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL));
String number = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER));
String typeLabel = Phone.getTypeLabel(context.getResources(), type, label).toString();
contactData.numbers.add(new NumberData(typeLabel, number));
}
} finally {
if (numberCursor != null)
numberCursor.close();
}
return contactData;
} }
public List<String> getNumbersForThreadSearchFilter(Context context, String constraint) { public List<String> getNumbersForThreadSearchFilter(Context context, String constraint) {
LinkedList<String> numberList = new LinkedList<>(); LinkedList<String> numberList = new LinkedList<>();
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI,
Uri.encode(constraint)),
null, null, null, null);
while (cursor != null && cursor.moveToNext()) {
numberList.add(cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER)));
}
} finally {
if (cursor != null)
cursor.close();
}
GroupDatabase.Reader reader = null; GroupDatabase.Reader reader = null;
GroupRecord record; GroupRecord record;
@ -216,7 +91,7 @@ public class ContactAccessor {
} }
public CharSequence phoneTypeToString(Context mContext, int type, CharSequence label) { public CharSequence phoneTypeToString(Context mContext, int type, CharSequence label) {
return Phone.getTypeLabel(mContext.getResources(), type, label); return label;
} }
public static class NumberData implements Parcelable { public static class NumberData implements Parcelable {
@ -294,82 +169,4 @@ public class ContactAccessor {
} }
} }
/***
* If the code below looks shitty to you, that's because it was taken
* directly from the Android source, where shitty code is all you get.
*/
public Cursor getCursorForRecipientFilter(CharSequence constraint,
ContentResolver mContentResolver)
{
final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," +
Contacts.DISPLAY_NAME + "," +
Contacts.Data.IS_SUPER_PRIMARY + " DESC," +
Phone.TYPE;
final String[] PROJECTION_PHONE = {
Phone._ID, // 0
Phone.CONTACT_ID, // 1
Phone.TYPE, // 2
Phone.NUMBER, // 3
Phone.LABEL, // 4
Phone.DISPLAY_NAME, // 5
};
String phone = "";
String cons = null;
if (constraint != null) {
cons = constraint.toString();
if (RecipientsAdapter.usefulAsDigits(cons)) {
phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons);
if (phone.equals(cons) && !PhoneNumberUtils.isWellFormedSmsAddress(phone)) {
phone = "";
} else {
phone = phone.trim();
}
}
}
Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons));
String selection = String.format("%s=%s OR %s=%s OR %s=%s",
Phone.TYPE,
Phone.TYPE_MOBILE,
Phone.TYPE,
Phone.TYPE_WORK_MOBILE,
Phone.TYPE,
Phone.TYPE_MMS);
Cursor phoneCursor = mContentResolver.query(uri,
PROJECTION_PHONE,
null,
null,
SORT_ORDER);
if (phone.length() > 0) {
ArrayList result = new ArrayList();
result.add(Integer.valueOf(-1)); // ID
result.add(Long.valueOf(-1)); // CONTACT_ID
result.add(Integer.valueOf(Phone.TYPE_CUSTOM)); // TYPE
result.add(phone); // NUMBER
/*
* The "\u00A0" keeps Phone.getDisplayLabel() from deciding
* to display the default label ("Home") next to the transformation
* of the letters into numbers.
*/
result.add("\u00A0"); // LABEL
result.add(cons); // NAME
ArrayList<ArrayList> wrap = new ArrayList<ArrayList>();
wrap.add(result);
ArrayListCursor translated = new ArrayListCursor(PROJECTION_PHONE, wrap);
return new MergeCursor(new Cursor[] { translated, phoneCursor });
} else {
return phoneCursor;
}
}
} }

View File

@ -17,9 +17,6 @@
package org.thoughtcrime.securesms.contacts; package org.thoughtcrime.securesms.contacts;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.recipients.RecipientsFormatter;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -31,6 +28,10 @@ import android.view.View;
import android.widget.ResourceCursorAdapter; import android.widget.ResourceCursorAdapter;
import android.widget.TextView; import android.widget.TextView;
import org.thoughtcrime.securesms.recipients.RecipientsFormatter;
import network.loki.messenger.R;
/** /**
* This adapter is used to filter contacts on both name and number. * This adapter is used to filter contacts on both name and number.
*/ */
@ -118,7 +119,7 @@ public class RecipientsAdapter extends ResourceCursorAdapter {
@Override @Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) { public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
return mContactAccessor.getCursorForRecipientFilter( constraint, mContentResolver ); return null;
} }
/** /**

View File

@ -20,11 +20,8 @@ import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -41,7 +38,9 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.*; import network.loki.messenger.R;
import static org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
public class SharedContactDetailsActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener { public class SharedContactDetailsActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener {
@ -246,14 +245,4 @@ public class SharedContactDetailsActivity extends PassphraseRequiredActionBarAct
engageContainerView.setVisibility(View.GONE); engageContainerView.setVisibility(View.GONE);
} }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CODE_ADD_EDIT_CONTACT && contact != null) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(false));
}
}
} }

View File

@ -211,7 +211,6 @@ import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.ExpirationUtil;
@ -348,8 +347,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private int distributionType; private int distributionType;
private boolean archived; private boolean archived;
private boolean isSecureText; private boolean isSecureText;
private boolean isDefaultSms = true; private boolean isDefaultSms = false;
private boolean isMmsEnabled = true; private boolean isMmsEnabled = false;
private boolean isSecurityInitialized = false; private boolean isSecurityInitialized = false;
private int expandedKeyboardHeight = 0; private int expandedKeyboardHeight = 0;
private int collapsedKeyboardHeight = Integer.MAX_VALUE; private int collapsedKeyboardHeight = Integer.MAX_VALUE;
@ -1478,41 +1477,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
new AsyncTask<Recipient, Void, boolean[]>() { new AsyncTask<Recipient, Void, boolean[]>() {
@Override @Override
protected boolean[] doInBackground(Recipient... params) { protected boolean[] doInBackground(Recipient... params) {
Context context = ConversationActivity.this;
Recipient recipient = params[0];
Log.i(TAG, "Resolving registered state...");
RegisteredState registeredState;
if (recipient.isPushGroupRecipient()) {
Log.i(TAG, "Push group recipient...");
registeredState = RegisteredState.REGISTERED;
} else if (recipient.isResolving()) {
Log.i(TAG, "Talking to DB directly.");
registeredState = DatabaseFactory.getRecipientDatabase(ConversationActivity.this).isRegistered(recipient.getAddress());
} else {
Log.i(TAG, "Checking through resolved recipient");
registeredState = recipient.resolve().getRegistered();
}
// Loki - Override the flag below
registeredState = RegisteredState.REGISTERED;
Log.i(TAG, "Resolved registered state: " + registeredState);
// Loki - Override the flag below // Loki - Override the flag below
boolean signalEnabled = true; // TextSecurePreferences.isPushRegistered(context); boolean signalEnabled = true; // TextSecurePreferences.isPushRegistered(context);
if (registeredState == RegisteredState.UNKNOWN) {
try {
Log.i(TAG, "Refreshing directory for user: " + recipient.getAddress().serialize());
registeredState = DirectoryHelper.refreshDirectoryFor(context, recipient);
} catch (IOException e) {
Log.w(TAG, e);
}
}
Log.i(TAG, "Returning registered state..."); return new boolean[] { signalEnabled, false};
return new boolean[] {registeredState == RegisteredState.REGISTERED && signalEnabled,
Util.isDefaultSmsProvider(context)};
} }
@Override @Override

View File

@ -77,7 +77,6 @@ import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.redesign.views.FriendRequestViewDelegate; import org.thoughtcrime.securesms.loki.redesign.views.FriendRequestViewDelegate;
@ -1076,17 +1075,6 @@ public class ConversationFragment extends Fragment
} }
} }
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CODE_ADD_EDIT_CONTACT && getContext() != null) {
ApplicationContext.getInstance(getContext().getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(false));
}
}
private class ActionModeCallback implements ActionMode.Callback { private class ActionModeCallback implements ActionMode.Callback {
private int statusBarColor; private int statusBarColor;

View File

@ -8,7 +8,6 @@ import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob; import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob; import org.thoughtcrime.securesms.jobs.CleanPreKeysJob;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.LocalBackupJob; import org.thoughtcrime.securesms.jobs.LocalBackupJob;
import org.thoughtcrime.securesms.jobs.MmsDownloadJob; import org.thoughtcrime.securesms.jobs.MmsDownloadJob;
import org.thoughtcrime.securesms.jobs.MmsReceiveJob; import org.thoughtcrime.securesms.jobs.MmsReceiveJob;
@ -57,7 +56,6 @@ public class WorkManagerFactoryMappings {
put(AvatarDownloadJob.class.getName(), AvatarDownloadJob.KEY); put(AvatarDownloadJob.class.getName(), AvatarDownloadJob.KEY);
put(CleanPreKeysJob.class.getName(), CleanPreKeysJob.KEY); put(CleanPreKeysJob.class.getName(), CleanPreKeysJob.KEY);
put(CreateSignedPreKeyJob.class.getName(), CreateSignedPreKeyJob.KEY); put(CreateSignedPreKeyJob.class.getName(), CreateSignedPreKeyJob.KEY);
put(DirectoryRefreshJob.class.getName(), DirectoryRefreshJob.KEY);
put(LocalBackupJob.class.getName(), LocalBackupJob.KEY); put(LocalBackupJob.class.getName(), LocalBackupJob.KEY);
put(MmsDownloadJob.class.getName(), MmsDownloadJob.KEY); put(MmsDownloadJob.class.getName(), MmsDownloadJob.KEY);
put(MmsReceiveJob.class.getName(), MmsReceiveJob.KEY); put(MmsReceiveJob.class.getName(), MmsReceiveJob.KEY);

View File

@ -1,102 +0,0 @@
package org.thoughtcrime.securesms.jobs;
import android.app.Application;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import java.io.IOException;
public class DirectoryRefreshJob extends BaseJob {
public static final String KEY = "DirectoryRefreshJob";
private static final String TAG = DirectoryRefreshJob.class.getSimpleName();
private static final String KEY_ADDRESS = "address";
private static final String KEY_NOTIFY_OF_NEW_USERS = "notify_of_new_users";
@Nullable private Recipient recipient;
private boolean notifyOfNewUsers;
public DirectoryRefreshJob(boolean notifyOfNewUsers) {
this(null, notifyOfNewUsers);
}
public DirectoryRefreshJob(@Nullable Recipient recipient,
boolean notifyOfNewUsers)
{
this(new Job.Parameters.Builder()
.setQueue("DirectoryRefreshJob")
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(10)
.build(),
recipient,
notifyOfNewUsers);
}
private DirectoryRefreshJob(@NonNull Job.Parameters parameters, @Nullable Recipient recipient, boolean notifyOfNewUsers) {
super(parameters);
this.recipient = recipient;
this.notifyOfNewUsers = notifyOfNewUsers;
}
@Override
public @NonNull Data serialize() {
return new Data.Builder().putString(KEY_ADDRESS, recipient != null ? recipient.getAddress().serialize() : null)
.putBoolean(KEY_NOTIFY_OF_NEW_USERS, notifyOfNewUsers)
.build();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@Override
public void onRun() throws IOException {
Log.i(TAG, "DirectoryRefreshJob.onRun()");
// if (recipient == null) {
// DirectoryHelper.refreshDirectory(context, notifyOfNewUsers);
// } else {
// DirectoryHelper.refreshDirectoryFor(context, recipient);
// }
}
@Override
public boolean onShouldRetry(@NonNull Exception exception) {
if (exception instanceof PushNetworkException) return true;
return false;
}
@Override
public void onCanceled() {}
public static final class Factory implements Job.Factory<DirectoryRefreshJob> {
private final Application application;
public Factory(@NonNull Application application) {
this.application = application;
}
@Override
public @NonNull DirectoryRefreshJob create(@NonNull Parameters parameters, @NonNull Data data) {
String serializedAddress = data.getString(KEY_ADDRESS);
Address address = serializedAddress != null ? Address.fromSerialized(serializedAddress) : null;
Recipient recipient = address != null ? Recipient.from(application, address, true) : null;
boolean notifyOfNewUsers = data.getBoolean(KEY_NOTIFY_OF_NEW_USERS);
return new DirectoryRefreshJob(parameters, recipient, notifyOfNewUsers);
}
}
}

View File

@ -31,7 +31,6 @@ public final class JobManagerFactories {
put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory()); put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory());
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory()); put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory()); put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory());
put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory(application));
put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); put(LocalBackupJob.KEY, new LocalBackupJob.Factory());
put(MmsDownloadJob.KEY, new MmsDownloadJob.Factory()); put(MmsDownloadJob.KEY, new MmsDownloadJob.Factory());
put(MmsReceiveJob.KEY, new MmsReceiveJob.Factory()); put(MmsReceiveJob.KEY, new MmsReceiveJob.Factory());

View File

@ -224,7 +224,6 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
if (messageId >= 0) { if (messageId >= 0) {
database.markAsPendingInsecureSmsFallback(messageId); database.markAsPendingInsecureSmsFallback(messageId);
notifyMediaMessageDeliveryFailed(context, messageId); notifyMediaMessageDeliveryFailed(context, messageId);
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(false));
} }
} catch (UntrustedIdentityException uie) { } catch (UntrustedIdentityException uie) {
warn(TAG, "Failure", uie); warn(TAG, "Failure", uie);

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.jobs;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.ApplicationContext;
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.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
@ -32,7 +31,6 @@ public abstract class PushReceivedJob extends BaseJob {
if (!isActiveNumber(recipient)) { if (!isActiveNumber(recipient)) {
DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED); DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED);
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(recipient, false));
} }
} }

View File

@ -165,7 +165,6 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
if (messageId >= 0) { if (messageId >= 0) {
database.markAsPendingInsecureSmsFallback(record.getId()); database.markAsPendingInsecureSmsFallback(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId()); MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId());
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(false));
} }
} catch (UntrustedIdentityException e) { } catch (UntrustedIdentityException e) {
warn(TAG, "Failure", e); warn(TAG, "Failure", e);

View File

@ -12,7 +12,9 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import kotlinx.android.synthetic.main.activity_linked_devices.* import kotlinx.android.synthetic.main.activity_linked_devices.*
import network.loki.messenger.R import network.loki.messenger.R
import nl.komponents.kovenant.Kovenant
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.failUi import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
@ -24,8 +26,8 @@ import org.thoughtcrime.securesms.loki.signAndSendDeviceLinkMessage
import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.DeviceLink import org.whispersystems.signalservice.loki.api.DeviceLink
import org.whispersystems.signalservice.loki.api.LokiAPI
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI import org.whispersystems.signalservice.loki.api.LokiFileServerAPI
import org.whispersystems.signalservice.loki.utilities.createContext
import java.util.* import java.util.*
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
@ -145,7 +147,11 @@ class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager
} }
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) { override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
LokiFileServerAPI.shared.addDeviceLink(deviceLink).bind(LokiAPI.sharedWorkContext) { val context = Kovenant.createContext("Multi-device")
LokiFileServerAPI.shared.addDeviceLink(deviceLink)
task(context) {
1
}.bind {
signAndSendDeviceLinkMessage(this, deviceLink) signAndSendDeviceLinkMessage(this, deviceLink)
}.successUi { }.successUi {
LoaderManager.getInstance(this).restartLoader(0, null, this) LoaderManager.getInstance(this).restartLoader(0, null, this)

View File

@ -1,39 +0,0 @@
package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class DirectoryRefreshListener extends PersistentAlarmManagerListener {
private static final long INTERVAL = TimeUnit.HOURS.toMillis(12);
@Override
protected long getNextScheduledExecutionTime(Context context) {
return TextSecurePreferences.getDirectoryRefreshTime(context);
}
@Override
protected long onAlarm(Context context, long scheduledTime) {
if (scheduledTime != 0 && TextSecurePreferences.isPushRegistered(context)) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new DirectoryRefreshJob(true));
}
long newTime = System.currentTimeMillis() + INTERVAL;
TextSecurePreferences.setDirectoryRefreshTime(context, newTime);
return newTime;
}
public static void schedule(Context context) {
new DirectoryRefreshListener().onReceive(context, new Intent());
}
}

View File

@ -1,520 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.thoughtcrime.securesms.logging.Log;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.ApplicationContext;
import network.loki.messenger.BuildConfig;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.push.IasTrustStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.IncomingJoinedMessage;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.ContactTokenDetails;
import org.whispersystems.signalservice.api.push.TrustStore;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.internal.contacts.crypto.Quote;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class DirectoryHelper {
private static final String TAG = DirectoryHelper.class.getSimpleName();
private static final int CONTACT_DISCOVERY_BATCH_SIZE = 2048;
public static void refreshDirectory(@NonNull Context context, boolean notifyOfNewUsers)
throws IOException
{
if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) return;
if (!Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) return;
List<Address> newlyActiveUsers = refreshDirectory(context, AccountManagerFactory.createManager(context));
if (TextSecurePreferences.isMultiDevice(context)) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceContactUpdateJob(context));
}
if (notifyOfNewUsers) notifyNewUsers(context, newlyActiveUsers);
}
@SuppressLint("CheckResult")
private static @NonNull List<Address> refreshDirectory(@NonNull Context context, @NonNull SignalServiceAccountManager accountManager)
throws IOException
{
if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) {
return Collections.emptyList();
}
if (!Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) {
return Collections.emptyList();
}
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
Stream<String> eligibleRecipientDatabaseContactNumbers = Stream.of(recipientDatabase.getAllAddresses()).filter(Address::isPhone).map(Address::toPhoneString);
Stream<String> eligibleSystemDatabaseContactNumbers = Stream.of(ContactAccessor.getInstance().getAllContactsWithNumbers(context)).map(Address::serialize);
Set<String> eligibleContactNumbers = Stream.concat(eligibleRecipientDatabaseContactNumbers, eligibleSystemDatabaseContactNumbers).collect(Collectors.toSet());
Future<DirectoryResult> legacyRequest = getLegacyDirectoryResult(context, accountManager, recipientDatabase, eligibleContactNumbers);
List<Future<Set<String>>> contactServiceRequest = getContactServiceDirectoryResult(context, accountManager, eligibleContactNumbers);
try {
DirectoryResult legacyResult = legacyRequest.get();
Optional<Set<String>> contactServiceResult = executeAndMergeContactDiscoveryRequests(accountManager, contactServiceRequest);
if (!contactServiceResult.isPresent()) {
Log.i(TAG, "[Batch] New contact discovery service failed, so we're skipping the comparison.");
return legacyResult.getNewlyActiveAddresses();
}
if (legacyResult.getNumbers().size() == contactServiceResult.get().size() && legacyResult.getNumbers().containsAll(contactServiceResult.get())) {
Log.i(TAG, "[Batch] New contact discovery service request matched existing results.");
accountManager.reportContactDiscoveryServiceMatch();
} else {
Log.w(TAG, "[Batch] New contact discovery service request did NOT match existing results.");
accountManager.reportContactDiscoveryServiceMismatch();
}
return legacyResult.getNewlyActiveAddresses();
} catch (InterruptedException e) {
throw new IOException("[Batch] Operation was interrupted.", e);
} catch (ExecutionException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
Log.e(TAG, "[Batch] Experienced an unexpected exception.", e);
throw new AssertionError(e);
}
}
}
public static RegisteredState refreshDirectoryFor(@NonNull Context context,
@NonNull Recipient recipient)
throws IOException
{
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context);
Future<RegisteredState> legacyRequest = getLegacyRegisteredState(context, accountManager, recipientDatabase, recipient);
List<Future<Set<String>>> contactServiceRequest = getContactServiceDirectoryResult(context, accountManager, Collections.singleton(recipient.getAddress().serialize()));
try {
RegisteredState legacyState = legacyRequest.get();
Optional<Set<String>> contactServiceResult = executeAndMergeContactDiscoveryRequests(accountManager, contactServiceRequest);
if (!contactServiceResult.isPresent()) {
Log.i(TAG, "[Singular] New contact discovery service failed, so we're skipping the comparison.");
return legacyState;
}
RegisteredState contactServiceState = contactServiceResult.get().size() == 1 ? RegisteredState.REGISTERED : RegisteredState.NOT_REGISTERED;
if (legacyState == contactServiceState) {
Log.i(TAG, "[Singular] New contact discovery service request matched existing results.");
accountManager.reportContactDiscoveryServiceMatch();
} else {
Log.w(TAG, "[Singular] New contact discovery service request did NOT match existing results.");
accountManager.reportContactDiscoveryServiceMismatch();
}
return legacyState;
} catch (InterruptedException e) {
throw new IOException("[Singular] Operation was interrupted.", e);
} catch (ExecutionException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
} else {
Log.e(TAG, "[Singular] Experienced an unexpected exception.", e);
throw new AssertionError(e);
}
}
}
private static void updateContactsDatabase(@NonNull Context context, @NonNull List<Address> activeAddresses, boolean removeMissing) {
Optional<AccountHolder> account = getOrCreateAccount(context);
if (account.isPresent()) {
try {
DatabaseFactory.getContactsDatabase(context).removeDeletedRawContacts(account.get().getAccount());
DatabaseFactory.getContactsDatabase(context).setRegisteredUsers(account.get().getAccount(), activeAddresses, removeMissing);
Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context);
RecipientDatabase.BulkOperationsHandle handle = DatabaseFactory.getRecipientDatabase(context).resetAllSystemContactInfo();
try {
while (cursor != null && cursor.moveToNext()) {
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER));
if (!TextUtils.isEmpty(number)) {
Address address = Address.fromExternal(context, number);
String displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String contactPhotoUri = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.PHOTO_URI));
String contactLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL));
Uri contactUri = ContactsContract.Contacts.getLookupUri(cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone._ID)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY)));
handle.setSystemContactInfo(address, displayName, contactPhotoUri, contactLabel, contactUri.toString());
}
}
} finally {
handle.finish();
}
if (NotificationChannels.supported()) {
try (RecipientDatabase.RecipientReader recipients = DatabaseFactory.getRecipientDatabase(context).getRecipientsWithNotificationChannels()) {
Recipient recipient;
while ((recipient = recipients.getNext()) != null) {
NotificationChannels.updateContactChannelName(context, recipient);
}
}
}
} catch (RemoteException | OperationApplicationException e) {
Log.w(TAG, "Failed to update contacts.", e);
}
}
}
private static void notifyNewUsers(@NonNull Context context,
@NonNull List<Address> newUsers)
{
if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) return;
for (Address newUser: newUsers) {
if (!SessionUtil.hasSession(context, newUser) && !Util.isOwnNumber(context, newUser)) {
IncomingJoinedMessage message = new IncomingJoinedMessage(newUser);
Optional<InsertResult> insertResult = DatabaseFactory.getSmsDatabase(context).insertMessageInbox(message);
if (insertResult.isPresent()) {
int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
if (hour >= 9 && hour < 23) {
MessageNotifier.updateNotification(context, insertResult.get().getThreadId(), true);
} else {
MessageNotifier.updateNotification(context, insertResult.get().getThreadId(), false);
}
}
}
}
}
private static Optional<AccountHolder> getOrCreateAccount(Context context) {
AccountManager accountManager = AccountManager.get(context);
Account[] accounts = accountManager.getAccountsByType("org.thoughtcrime.securesms");
Optional<AccountHolder> account;
if (accounts.length == 0) account = createAccount(context);
else account = Optional.of(new AccountHolder(accounts[0], false));
if (account.isPresent() && !ContentResolver.getSyncAutomatically(account.get().getAccount(), ContactsContract.AUTHORITY)) {
ContentResolver.setSyncAutomatically(account.get().getAccount(), ContactsContract.AUTHORITY, true);
}
return account;
}
private static Optional<AccountHolder> createAccount(Context context) {
AccountManager accountManager = AccountManager.get(context);
Account account = new Account(context.getString(R.string.app_name), "org.thoughtcrime.securesms");
if (accountManager.addAccountExplicitly(account, null, null)) {
Log.i(TAG, "Created new account...");
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
return Optional.of(new AccountHolder(account, true));
} else {
Log.w(TAG, "Failed to create account!");
return Optional.absent();
}
}
private static Future<DirectoryResult> getLegacyDirectoryResult(@NonNull Context context,
@NonNull SignalServiceAccountManager accountManager,
@NonNull RecipientDatabase recipientDatabase,
@NonNull Set<String> eligibleContactNumbers)
{
return SignalExecutors.UNBOUNDED.submit(() -> {
List<ContactTokenDetails> activeTokens = accountManager.getContacts(eligibleContactNumbers);
if (activeTokens != null) {
List<Address> activeAddresses = new LinkedList<>();
List<Address> inactiveAddresses = new LinkedList<>();
Set<String> inactiveContactNumbers = new HashSet<>(eligibleContactNumbers);
for (ContactTokenDetails activeToken : activeTokens) {
activeAddresses.add(Address.fromSerialized(activeToken.getNumber()));
inactiveContactNumbers.remove(activeToken.getNumber());
}
for (String inactiveContactNumber : inactiveContactNumbers) {
inactiveAddresses.add(Address.fromSerialized(inactiveContactNumber));
}
Set<Address> currentActiveAddresses = new HashSet<>(recipientDatabase.getRegistered());
Set<Address> contactAddresses = new HashSet<>(recipientDatabase.getSystemContacts());
List<Address> newlyActiveAddresses = Stream.of(activeAddresses)
.filter(address -> !currentActiveAddresses.contains(address))
.filter(contactAddresses::contains)
.toList();
recipientDatabase.setRegistered(activeAddresses, inactiveAddresses);
updateContactsDatabase(context, activeAddresses, true);
Set<String> activeContactNumbers = Stream.of(activeAddresses).map(Address::serialize).collect(Collectors.toSet());
if (TextSecurePreferences.hasSuccessfullyRetrievedDirectory(context)) {
return new DirectoryResult(activeContactNumbers, newlyActiveAddresses);
} else {
TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(context, true);
return new DirectoryResult(activeContactNumbers);
}
}
return new DirectoryResult(Collections.emptySet(), Collections.emptyList());
});
}
private static Future<RegisteredState> getLegacyRegisteredState(@NonNull Context context,
@NonNull SignalServiceAccountManager accountManager,
@NonNull RecipientDatabase recipientDatabase,
@NonNull Recipient recipient)
{
return SignalExecutors.UNBOUNDED.submit(() -> {
boolean activeUser = recipient.resolve().getRegistered() == RegisteredState.REGISTERED;
boolean systemContact = recipient.isSystemContact();
String number = recipient.getAddress().serialize();
Optional<ContactTokenDetails> details = accountManager.getContact(number);
if (details.isPresent()) {
recipientDatabase.setRegistered(recipient, RegisteredState.REGISTERED);
if (Permissions.hasAll(context, Manifest.permission.WRITE_CONTACTS)) {
updateContactsDatabase(context, Util.asList(recipient.getAddress()), false);
}
if (!activeUser && TextSecurePreferences.isMultiDevice(context)) {
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context));
}
if (!activeUser && systemContact && !TextSecurePreferences.getNeedsSqlCipherMigration(context)) {
notifyNewUsers(context, Collections.singletonList(recipient.getAddress()));
}
return RegisteredState.REGISTERED;
} else {
recipientDatabase.setRegistered(recipient, RegisteredState.NOT_REGISTERED);
return RegisteredState.NOT_REGISTERED;
}
});
}
private static List<Future<Set<String>>> getContactServiceDirectoryResult(@NonNull Context context,
@NonNull SignalServiceAccountManager accountManager,
@NonNull Set<String> eligibleContactNumbers)
{
Set<String> sanitizedNumbers = sanitizeNumbers(eligibleContactNumbers);
List<Set<String>> batches = splitIntoBatches(sanitizedNumbers, CONTACT_DISCOVERY_BATCH_SIZE);
List<Future<Set<String>>> futures = new ArrayList<>(batches.size());
KeyStore iasKeyStore = getIasKeyStore(context);
for (Set<String> batch : batches) {
Future<Set<String>> future = SignalExecutors.UNBOUNDED.submit(() -> {
return new HashSet<>(accountManager.getRegisteredUsers(iasKeyStore, batch, BuildConfig.MRENCLAVE));
});
futures.add(future);
}
return futures;
}
private static Set<String> sanitizeNumbers(@NonNull Set<String> numbers) {
return Stream.of(numbers).filter(number -> {
try {
return number.startsWith("+") && number.length() > 1 && Long.parseLong(number.substring(1)) > 0;
} catch (NumberFormatException e) {
return false;
}
}).collect(Collectors.toSet());
}
private static List<Set<String>> splitIntoBatches(@NonNull Set<String> numbers, int batchSize) {
List<String> numberList = new ArrayList<>(numbers);
List<Set<String>> batches = new LinkedList<>();
for (int i = 0; i < numberList.size(); i += batchSize) {
List<String> batch = numberList.subList(i, Math.min(numberList.size(), i + batchSize));
batches.add(new HashSet<>(batch));
}
return batches;
}
private static Optional<Set<String>> executeAndMergeContactDiscoveryRequests(@NonNull SignalServiceAccountManager accountManager, @NonNull List<Future<Set<String>>> futures) {
Set<String> results = new HashSet<>();
try {
for (Future<Set<String>> future : futures) {
results.addAll(future.get());
}
} catch (InterruptedException e) {
Log.w(TAG, "Contact discovery batch was interrupted.", e);
accountManager.reportContactDiscoveryServiceUnexpectedError(buildErrorReason(e));
return Optional.absent();
} catch (ExecutionException e) {
if (isAttestationError(e.getCause())) {
Log.w(TAG, "Failed during attestation.", e);
accountManager.reportContactDiscoveryServiceAttestationError(buildErrorReason(e.getCause()));
return Optional.absent();
} else if (e.getCause() instanceof PushNetworkException) {
Log.w(TAG, "Failed due to poor network.", e);
return Optional.absent();
} else if (e.getCause() instanceof NonSuccessfulResponseCodeException) {
Log.w(TAG, "Failed due to non successful response code.", e);
return Optional.absent();
} else {
Log.w(TAG, "Failed for an unknown reason.", e);
accountManager.reportContactDiscoveryServiceUnexpectedError(buildErrorReason(e.getCause()));
return Optional.absent();
}
}
return Optional.of(results);
}
private static boolean isAttestationError(Throwable e) {
return e instanceof CertificateException ||
e instanceof SignatureException ||
e instanceof UnauthenticatedQuoteException ||
e instanceof UnauthenticatedResponseException ||
e instanceof Quote.InvalidQuoteFormatException;
}
private static KeyStore getIasKeyStore(@NonNull Context context) {
try {
TrustStore contactTrustStore = new IasTrustStore(context);
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(contactTrustStore.getKeyStoreInputStream(), contactTrustStore.getKeyStorePassword().toCharArray());
return keyStore;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private static String buildErrorReason(@Nullable Throwable t) {
if (t == null) {
return "null";
}
String rawString = android.util.Log.getStackTraceString(t);
List<String> lines = Arrays.asList(rawString.split("\\n"));
String errorString;
if (lines.size() > 1) {
errorString = t.getClass().getName() + "\n" + Util.join(lines.subList(1, lines.size()), "\n");
} else {
errorString = t.getClass().getName();
}
if (errorString.length() > 1000) {
return errorString.substring(0, 1000);
} else {
return errorString;
}
}
private static class DirectoryResult {
private final Set<String> numbers;
private final List<Address> newlyActiveAddresses;
DirectoryResult(@NonNull Set<String> numbers) {
this(numbers, Collections.emptyList());
}
DirectoryResult(@NonNull Set<String> numbers, @NonNull List<Address> newlyActiveAddresses) {
this.numbers = numbers;
this.newlyActiveAddresses = newlyActiveAddresses;
}
Set<String> getNumbers() {
return numbers;
}
List<Address> getNewlyActiveAddresses() {
return newlyActiveAddresses;
}
}
private static class AccountHolder {
private final boolean fresh;
private final Account account;
private AccountHolder(Account account, boolean fresh) {
this.fresh = fresh;
this.account = account;
}
@SuppressWarnings("unused")
public boolean isFresh() {
return fresh;
}
public Account getAccount() {
return account;
}
}
}