Don't invalidate entire recipient cache when contact data changes

This commit is contained in:
Moxie Marlinspike 2017-11-20 14:48:39 -08:00
parent 8f6440ce17
commit 66e1be1aeb
9 changed files with 92 additions and 198 deletions

View File

@ -105,7 +105,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft; import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
import org.thoughtcrime.securesms.database.DraftDatabase.Drafts; import org.thoughtcrime.securesms.database.DraftDatabase.Drafts;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
@ -235,7 +234,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private AttachmentManager attachmentManager; private AttachmentManager attachmentManager;
private AudioRecorder audioRecorder; private AudioRecorder audioRecorder;
private BroadcastReceiver securityUpdateReceiver; private BroadcastReceiver securityUpdateReceiver;
private BroadcastReceiver recipientsStaleReceiver;
private Stub<EmojiDrawer> emojiDrawerStub; private Stub<EmojiDrawer> emojiDrawerStub;
protected HidingLinearLayout quickAttachmentToggle; protected HidingLinearLayout quickAttachmentToggle;
private QuickAttachmentDrawer quickAttachmentDrawer; private QuickAttachmentDrawer quickAttachmentDrawer;
@ -386,7 +384,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
saveDraft(); saveDraft();
if (recipient != null) recipient.removeListener(this); if (recipient != null) recipient.removeListener(this);
if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver); if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver);
if (recipientsStaleReceiver != null) unregisterReceiver(recipientsStaleReceiver);
super.onDestroy(); super.onDestroy();
} }
@ -1345,29 +1342,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
}; };
recipientsStaleReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.w(TAG, "Group update received...");
if (recipient != null) {
Log.w(TAG, "Looking up new recipients...");
recipient = Recipient.from(context, recipient.getAddress(), true);
recipient.addListener(ConversationActivity.this);
onModified(recipient);
fragment.reloadList();
}
}
};
IntentFilter staleFilter = new IntentFilter();
staleFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION);
staleFilter.addAction(Recipient.RECIPIENT_CLEAR_ACTION);
registerReceiver(securityUpdateReceiver, registerReceiver(securityUpdateReceiver,
new IntentFilter(SecurityEvent.SECURITY_UPDATE_EVENT), new IntentFilter(SecurityEvent.SECURITY_UPDATE_EVENT),
KeyCachingService.KEY_PERMISSION, null); KeyCachingService.KEY_PERMISSION, null);
registerReceiver(recipientsStaleReceiver, staleFilter);
} }
//////// Helper Methods //////// Helper Methods

View File

@ -20,14 +20,11 @@ import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@ -53,13 +50,13 @@ import java.util.List;
public class ConversationListActivity extends PassphraseRequiredActionBarActivity public class ConversationListActivity extends PassphraseRequiredActionBarActivity
implements ConversationListFragment.ConversationSelectedListener implements ConversationListFragment.ConversationSelectedListener
{ {
@SuppressWarnings("unused")
private static final String TAG = ConversationListActivity.class.getSimpleName(); private static final String TAG = ConversationListActivity.class.getSimpleName();
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private ConversationListFragment fragment; private ConversationListFragment fragment;
private ContentObserver observer;
private MasterSecret masterSecret; private MasterSecret masterSecret;
private SearchToolbar searchToolbar; private SearchToolbar searchToolbar;
private ImageView searchAction; private ImageView searchAction;
@ -83,7 +80,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
searchAction = findViewById(R.id.search_action); searchAction = findViewById(R.id.search_action);
fragment = initFragment(R.id.fragment_container, new ConversationListFragment(), masterSecret, dynamicLanguage.getCurrentLocale()); fragment = initFragment(R.id.fragment_container, new ConversationListFragment(), masterSecret, dynamicLanguage.getCurrentLocale());
initializeContactUpdatesReceiver();
initializeSearchListener(); initializeSearchListener();
RatingManager.showRatingDialogIfNecessary(this); RatingManager.showRatingDialogIfNecessary(this);
@ -98,7 +94,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
@Override @Override
public void onDestroy() { public void onDestroy() {
if (observer != null) getContentResolver().unregisterContentObserver(observer);
super.onDestroy(); super.onDestroy();
} }
@ -225,19 +220,4 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
Toast.makeText(this, R.string.ConversationListActivity_there_is_no_browser_installed_on_your_device, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.ConversationListActivity_there_is_no_browser_installed_on_your_device, Toast.LENGTH_LONG).show();
} }
} }
private void initializeContactUpdatesReceiver() {
observer = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.w(TAG, "Detected android contact data changed, refreshing cache");
Recipient.clearCache(ConversationListActivity.this);
ConversationListActivity.this.runOnUiThread(() -> fragment.getListAdapter().notifyDataSetChanged());
}
};
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI,
true, observer);
}
} }

View File

@ -4,7 +4,6 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Color; import android.graphics.Color;
import android.media.Ringtone; import android.media.Ringtone;
@ -42,7 +41,6 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
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.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
@ -93,7 +91,6 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
private TextView threadPhotoRailLabel; private TextView threadPhotoRailLabel;
private ThreadPhotoRailView threadPhotoRailView; private ThreadPhotoRailView threadPhotoRailView;
private CollapsingToolbarLayout toolbarLayout; private CollapsingToolbarLayout toolbarLayout;
private BroadcastReceiver staleReceiver;
@Override @Override
public void onPreCreate() { public void onPreCreate() {
@ -111,7 +108,6 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
Recipient recipient = Recipient.from(this, address, true); Recipient recipient = Recipient.from(this, address, true);
initializeToolbar(); initializeToolbar();
initializeReceivers();
setHeader(recipient); setHeader(recipient);
recipient.addListener(this); recipient.addListener(this);
@ -125,12 +121,6 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
dynamicLanguage.onResume(this); dynamicLanguage.onResume(this);
} }
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(staleReceiver);
}
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
@ -192,23 +182,6 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
} }
} }
private void initializeReceivers() {
this.staleReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Recipient recipient = Recipient.from(context, (Address)getIntent().getParcelableExtra(ADDRESS_EXTRA), true);
recipient.addListener(RecipientPreferenceActivity.this);
onModified(recipient);
}
};
IntentFilter staleFilter = new IntentFilter();
staleFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION);
staleFilter.addAction(Recipient.RECIPIENT_CLEAR_ACTION);
registerReceiver(staleReceiver, staleFilter);
}
private void setHeader(@NonNull Recipient recipient) { private void setHeader(@NonNull Recipient recipient) {
glideRequests.load(recipient.getContactPhoto()) glideRequests.load(recipient.getContactPhoto())
.fallback(recipient.getFallbackContactPhoto().asCallCard(this)) .fallback(recipient.getFallbackContactPhoto().asCallCard(this))
@ -306,23 +279,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
private void initializeRecipients() { private void initializeRecipients() {
this.recipient = Recipient.from(getActivity(), getArguments().getParcelable(ADDRESS_EXTRA), true); this.recipient = Recipient.from(getActivity(), getArguments().getParcelable(ADDRESS_EXTRA), true);
this.recipient.addListener(this); this.recipient.addListener(this);
this.staleReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
recipient.removeListener(RecipientPreferenceFragment.this);
recipient = Recipient.from(getActivity(), getArguments().getParcelable(ADDRESS_EXTRA), true);
onModified(recipient);
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION);
intentFilter.addAction(Recipient.RECIPIENT_CLEAR_ACTION);
getActivity().registerReceiver(staleReceiver, intentFilter);
} }
private void setSummaries(Recipient recipient) { private void setSummaries(Recipient recipient) {

View File

@ -38,14 +38,10 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.push.ContactTokenDetails;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -80,13 +76,12 @@ public class ContactsDatabase {
this.context = context; this.context = context;
} }
public synchronized @NonNull List<Address> setRegisteredUsers(@NonNull Account account, public synchronized void setRegisteredUsers(@NonNull Account account,
@NonNull List<Address> registeredAddressList, @NonNull List<Address> registeredAddressList,
boolean remove) boolean remove)
throws RemoteException, OperationApplicationException throws RemoteException, OperationApplicationException
{ {
Set<Address> registeredAddressSet = new HashSet<>(); Set<Address> registeredAddressSet = new HashSet<>();
List<Address> addedAddresses = new LinkedList<>();
ArrayList<ContentProviderOperation> operations = new ArrayList<>(); ArrayList<ContentProviderOperation> operations = new ArrayList<>();
Map<Address, SignalContact> currentContacts = getSignalRawContacts(account); Map<Address, SignalContact> currentContacts = getSignalRawContacts(account);
@ -98,7 +93,6 @@ public class ContactsDatabase {
if (systemContactInfo.isPresent()) { if (systemContactInfo.isPresent()) {
Log.w(TAG, "Adding number: " + registeredAddress); Log.w(TAG, "Adding number: " + registeredAddress);
addedAddresses.add(registeredAddress);
addTextSecureRawContact(operations, account, systemContactInfo.get().number, addTextSecureRawContact(operations, account, systemContactInfo.get().number,
systemContactInfo.get().name, systemContactInfo.get().id, systemContactInfo.get().name, systemContactInfo.get().id,
true); true);
@ -126,8 +120,6 @@ public class ContactsDatabase {
if (!operations.isEmpty()) { if (!operations.isEmpty()) {
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
} }
return addedAddresses;
} }
@NonNull Cursor querySystemContacts(@Nullable String filter) { @NonNull Cursor querySystemContacts(@Nullable String filter) {

View File

@ -1,9 +1,9 @@
package org.thoughtcrime.securesms.database; package org.thoughtcrime.securesms.database;
import android.annotation.SuppressLint;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
@ -14,6 +14,7 @@ import android.text.TextUtils;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.contacts.avatars.GroupRecordContactPhoto;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
@ -30,8 +31,7 @@ import java.util.List;
public class GroupDatabase extends Database { public class GroupDatabase extends Database {
public static final String DATABASE_UPDATE_ACTION = "org.thoughtcrime.securesms.database.GroupDatabase.UPDATE"; @SuppressWarnings("unused")
private static final String TAG = GroupDatabase.class.getSimpleName(); private static final String TAG = GroupDatabase.class.getSimpleName();
static final String TABLE_NAME = "groups"; static final String TABLE_NAME = "groups";
@ -65,7 +65,7 @@ public class GroupDatabase extends Database {
AVATAR_DIGEST + " BLOB, " + AVATAR_DIGEST + " BLOB, " +
MMS + " INTEGER DEFAULT 0);"; MMS + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = { static final String[] CREATE_INDEXS = {
"CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");", "CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON " + TABLE_NAME + " (" + GROUP_ID + ");",
}; };
@ -103,9 +103,10 @@ public class GroupDatabase extends Database {
} }
public Reader getGroupsFilteredByTitle(String constraint) { public Reader getGroupsFilteredByTitle(String constraint) {
@SuppressLint("Recycle")
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, TITLE + " LIKE ?", Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, TITLE + " LIKE ?",
new String[]{"%" + constraint + "%"}, new String[]{"%" + constraint + "%"},
null, null, null); null, null, null);
return new Reader(cursor); return new Reader(cursor);
} }
@ -131,6 +132,7 @@ public class GroupDatabase extends Database {
} }
public Reader getGroups() { public Reader getGroups() {
@SuppressLint("Recycle")
Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null); Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null);
return new Reader(cursor); return new Reader(cursor);
} }
@ -172,7 +174,14 @@ public class GroupDatabase extends Database {
contentValues.put(MMS, GroupUtil.isMmsGroup(groupId)); contentValues.put(MMS, GroupUtil.isMmsGroup(groupId));
databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues); databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
Recipient.clearCache(context);
Address address = Address.fromSerialized(groupId);
Recipient recipient = Recipient.from(context, Address.fromSerialized(groupId), false);
recipient.setName(title);
if (avatar != null) recipient.setContactPhoto(new GroupRecordContactPhoto(address, avatar.getId()));
recipient.setParticipants(Stream.of(members).map(memberAddress -> Recipient.from(context, memberAddress, true)).toList());
notifyConversationListListeners(); notifyConversationListListeners();
} }
@ -191,8 +200,11 @@ public class GroupDatabase extends Database {
GROUP_ID + " = ?", GROUP_ID + " = ?",
new String[] {groupId}); new String[] {groupId});
Recipient.clearCache(context); Address address = Address.fromSerialized(groupId);
notifyDatabaseListeners(); Recipient recipient = Recipient.from(context, address, false);
recipient.setName(title);
if (avatar != null) recipient.setContactPhoto(new GroupRecordContactPhoto(address, avatar.getId()));
notifyConversationListListeners(); notifyConversationListListeners();
} }
@ -202,8 +214,8 @@ public class GroupDatabase extends Database {
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
new String[] {groupId}); new String[] {groupId});
Recipient.clearCache(context); Recipient recipient = Recipient.from(context, Address.fromSerialized(groupId), false);
notifyDatabaseListeners(); recipient.setName(title);
} }
public void updateAvatar(String groupId, Bitmap avatar) { public void updateAvatar(String groupId, Bitmap avatar) {
@ -211,14 +223,17 @@ public class GroupDatabase extends Database {
} }
public void updateAvatar(String groupId, byte[] avatar) { public void updateAvatar(String groupId, byte[] avatar) {
long avatarId = Math.abs(new SecureRandom().nextLong());
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(AVATAR, avatar); contentValues.put(AVATAR, avatar);
contentValues.put(AVATAR_ID, avatarId);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
new String[] {groupId}); new String[] {groupId});
Recipient.clearCache(context); Address address = Address.fromSerialized(groupId);
notifyDatabaseListeners(); Recipient recipient = Recipient.from(context, address, false);
recipient.setContactPhoto(new GroupRecordContactPhoto(address, avatarId));
} }
public void updateMembers(String groupId, List<Address> members) { public void updateMembers(String groupId, List<Address> members) {
@ -287,11 +302,6 @@ public class GroupDatabase extends Database {
} }
} }
private void notifyDatabaseListeners() {
Intent intent = new Intent(DATABASE_UPDATE_ACTION);
context.sendBroadcast(intent);
}
public static class Reader { public static class Reader {
private final Cursor cursor; private final Cursor cursor;

View File

@ -399,7 +399,7 @@ public class RecipientDatabase extends Database {
database.setTransactionSuccessful(); database.setTransactionSuccessful();
database.endTransaction(); database.endTransaction();
Stream.of(pendingDisplayNames).forEach(pair -> pair.first().resolve().setSystemDisplayName(pair.second())); Stream.of(pendingDisplayNames).forEach(pair -> pair.first().resolve().setName(pair.second()));
context.getContentResolver().notifyChange(Uri.parse(RECIPIENT_PREFERENCES_URI), null); context.getContentResolver().notifyChange(Uri.parse(RECIPIENT_PREFERENCES_URI), null);
} }

View File

@ -1,5 +1,6 @@
/** /*
* Copyright (C) 2011 Whisper Systems * Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 - 2017 Open Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -17,7 +18,6 @@
package org.thoughtcrime.securesms.recipients; package org.thoughtcrime.securesms.recipients;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -50,9 +50,8 @@ import java.util.concurrent.ExecutionException;
public class Recipient implements RecipientModifiedListener { public class Recipient implements RecipientModifiedListener {
public static final String RECIPIENT_CLEAR_ACTION = "org.thoughtcrime.securesms.database.RecipientFactory.CLEAR"; private static final String TAG = Recipient.class.getSimpleName();
private static final String TAG = Recipient.class.getSimpleName(); private static final RecipientProvider provider = new RecipientProvider();
private static final RecipientProvider provider = new RecipientProvider();
private final Set<RecipientModifiedListener> listeners = Collections.newSetFromMap(new WeakHashMap<RecipientModifiedListener, Boolean>()); private final Set<RecipientModifiedListener> listeners = Collections.newSetFromMap(new WeakHashMap<RecipientModifiedListener, Boolean>());
@ -61,7 +60,6 @@ public class Recipient implements RecipientModifiedListener {
private @Nullable String name; private @Nullable String name;
private @Nullable String customLabel; private @Nullable String customLabel;
private boolean stale;
private boolean resolving; private boolean resolving;
private @Nullable ContactPhoto contactPhoto; private @Nullable ContactPhoto contactPhoto;
@ -84,21 +82,18 @@ public class Recipient implements RecipientModifiedListener {
private boolean isSystemContact; private boolean isSystemContact;
@SuppressWarnings("ConstantConditions")
public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, boolean asynchronous) { public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, boolean asynchronous) {
if (address == null) throw new AssertionError(address); if (address == null) throw new AssertionError(address);
return provider.getRecipient(context, address, Optional.absent(), Optional.absent(), asynchronous); return provider.getRecipient(context, address, Optional.absent(), Optional.absent(), asynchronous);
} }
@SuppressWarnings("ConstantConditions")
public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, @NonNull Optional<RecipientSettings> settings, @NonNull Optional<GroupDatabase.GroupRecord> groupRecord, boolean asynchronous) { public static @NonNull Recipient from(@NonNull Context context, @NonNull Address address, @NonNull Optional<RecipientSettings> settings, @NonNull Optional<GroupDatabase.GroupRecord> groupRecord, boolean asynchronous) {
if (address == null) throw new AssertionError(address); if (address == null) throw new AssertionError(address);
return provider.getRecipient(context, address, settings, groupRecord, asynchronous); return provider.getRecipient(context, address, settings, groupRecord, asynchronous);
} }
public static void clearCache(Context context) {
provider.clearCache();
context.sendBroadcast(new Intent(RECIPIENT_CLEAR_ACTION));
}
Recipient(@NonNull Address address, Recipient(@NonNull Address address,
@Nullable Recipient stale, @Nullable Recipient stale,
@NonNull Optional<RecipientDetails> details, @NonNull Optional<RecipientDetails> details,
@ -246,6 +241,19 @@ public class Recipient implements RecipientModifiedListener {
return this.name; return this.name;
} }
public void setName(@Nullable String name) {
boolean notify = false;
synchronized (this) {
if (!Util.equals(this.name, name)) {
this.name = name;
notify = true;
}
}
if (notify) notifyListeners();
}
public synchronized @NonNull MaterialColor getColor() { public synchronized @NonNull MaterialColor getColor() {
if (isGroupRecipient()) return MaterialColor.GROUP; if (isGroupRecipient()) return MaterialColor.GROUP;
else if (color != null) return color; else if (color != null) return color;
@ -333,6 +341,15 @@ public class Recipient implements RecipientModifiedListener {
return participants; return participants;
} }
public void setParticipants(@NonNull List<Recipient> participants) {
synchronized (this) {
this.participants.clear();
this.participants.addAll(participants);
}
notifyListeners();
}
public synchronized void addListener(RecipientModifiedListener listener) { public synchronized void addListener(RecipientModifiedListener listener) {
if (listeners.isEmpty()) { if (listeners.isEmpty()) {
for (Recipient recipient : participants) recipient.addListener(this); for (Recipient recipient : participants) recipient.addListener(this);
@ -452,11 +469,16 @@ public class Recipient implements RecipientModifiedListener {
} }
public void setRegistered(@NonNull RegisteredState value) { public void setRegistered(@NonNull RegisteredState value) {
boolean notify = false;
synchronized (this) { synchronized (this) {
this.registered = value; if (this.registered != value) {
this.registered = value;
notify = true;
}
} }
notifyListeners(); if (notify) notifyListeners();
} }
public synchronized @Nullable byte[] getProfileKey() { public synchronized @Nullable byte[] getProfileKey() {
@ -475,15 +497,6 @@ public class Recipient implements RecipientModifiedListener {
return isSystemContact; return isSystemContact;
} }
public void setSystemDisplayName(@Nullable String displayName) {
synchronized (this) {
if (displayName == null) this.name = profileName;
else this.name = displayName;
}
notifyListeners();
}
public synchronized Recipient resolve() { public synchronized Recipient resolve() {
while (resolving) Util.wait(this, 0); while (resolving) Util.wait(this, 0);
return this; return this;
@ -521,15 +534,6 @@ public class Recipient implements RecipientModifiedListener {
notifyListeners(); notifyListeners();
} }
boolean isStale() {
return stale;
}
void setStale() {
this.stale = true;
}
// XXX This shouldn't be public, temporary workaround
public synchronized boolean isResolving() { public synchronized boolean isResolving() {
return resolving; return resolving;
} }

View File

@ -77,7 +77,7 @@ class RecipientProvider {
@NonNull Recipient getRecipient(Context context, Address address, Optional<RecipientSettings> settings, Optional<GroupRecord> groupRecord, boolean asynchronous) { @NonNull Recipient getRecipient(Context context, Address address, Optional<RecipientSettings> settings, Optional<GroupRecord> groupRecord, boolean asynchronous) {
Recipient cachedRecipient = recipientCache.get(address); Recipient cachedRecipient = recipientCache.get(address);
if (cachedRecipient != null && !cachedRecipient.isStale() && (asynchronous || !cachedRecipient.isResolving()) && ((!groupRecord.isPresent() && !settings.isPresent()) || !cachedRecipient.isResolving() || cachedRecipient.getName() != null)) { if (cachedRecipient != null && (asynchronous || !cachedRecipient.isResolving()) && ((!groupRecord.isPresent() && !settings.isPresent()) || !cachedRecipient.isResolving() || cachedRecipient.getName() != null)) {
return cachedRecipient; return cachedRecipient;
} }
@ -93,10 +93,6 @@ class RecipientProvider {
return cachedRecipient; return cachedRecipient;
} }
void clearCache() {
recipientCache.reset();
}
private @NonNull Optional<RecipientDetails> createPrefetchedRecipientDetails(@NonNull Context context, @NonNull Address address, private @NonNull Optional<RecipientDetails> createPrefetchedRecipientDetails(@NonNull Context context, @NonNull Address address,
@NonNull Optional<RecipientSettings> settings, @NonNull Optional<RecipientSettings> settings,
@NonNull Optional<GroupRecord> groupRecord) @NonNull Optional<GroupRecord> groupRecord)
@ -276,12 +272,6 @@ class RecipientProvider {
cache.put(address, recipient); cache.put(address, recipient);
} }
public synchronized void reset() {
for (Recipient recipient : cache.values()) {
recipient.setStale();
}
}
} }
} }

View File

@ -25,7 +25,6 @@ import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
@ -38,6 +37,7 @@ import org.whispersystems.signalservice.api.push.ContactTokenDetails;
import java.io.IOException; import java.io.IOException;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -52,24 +52,22 @@ public class DirectoryHelper {
{ {
if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) return; if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) return;
RefreshResult result = refreshDirectory(context, AccountManagerFactory.createManager(context)); List<Address> newlyActiveUsers = refreshDirectory(context, AccountManagerFactory.createManager(context));
if (!result.getNewUsers().isEmpty() && TextSecurePreferences.isMultiDevice(context)) { if (!newlyActiveUsers.isEmpty() && TextSecurePreferences.isMultiDevice(context)) {
ApplicationContext.getInstance(context) ApplicationContext.getInstance(context)
.getJobManager() .getJobManager()
.add(new MultiDeviceContactUpdateJob(context)); .add(new MultiDeviceContactUpdateJob(context));
} }
if (!result.isFresh()) { notifyNewUsers(context, masterSecret, newlyActiveUsers);
notifyNewUsers(context, masterSecret, result.getNewUsers());
}
} }
public static @NonNull RefreshResult refreshDirectory(@NonNull Context context, @NonNull SignalServiceAccountManager accountManager) public static @NonNull List<Address> refreshDirectory(@NonNull Context context, @NonNull SignalServiceAccountManager accountManager)
throws IOException throws IOException
{ {
if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) { if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) {
return new RefreshResult(new LinkedList<>(), false); return new LinkedList<>();
} }
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
@ -94,11 +92,19 @@ public class DirectoryHelper {
inactiveRecipients.add(Recipient.from(context, Address.fromSerialized(inactiveContactNumber), true)); inactiveRecipients.add(Recipient.from(context, Address.fromSerialized(inactiveContactNumber), true));
} }
Set<Address> currentActiveAddresses = new HashSet<>(recipientDatabase.getRegistered());
List<Address> newlyActiveAddresses = Stream.of(activeRecipients)
.map(Recipient::getAddress)
.filter(address -> !currentActiveAddresses.contains(address))
.toList();
recipientDatabase.setRegistered(activeRecipients, inactiveRecipients); recipientDatabase.setRegistered(activeRecipients, inactiveRecipients);
return updateContactsDatabase(context, Stream.of(activeRecipients).map(Recipient::getAddress).toList(), true); updateContactsDatabase(context, Stream.of(activeRecipients).map(Recipient::getAddress).toList(), true);
return newlyActiveAddresses;
} }
return new RefreshResult(new LinkedList<>(), false); return new LinkedList<>();
} }
public static RegisteredState refreshDirectoryFor(@NonNull Context context, public static RegisteredState refreshDirectoryFor(@NonNull Context context,
@ -108,20 +114,21 @@ public class DirectoryHelper {
{ {
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context); SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context);
boolean activeUser = recipient.resolve().getRegistered() == RegisteredState.REGISTERED;
String number = recipient.getAddress().serialize(); String number = recipient.getAddress().serialize();
Optional<ContactTokenDetails> details = accountManager.getContact(number); Optional<ContactTokenDetails> details = accountManager.getContact(number);
if (details.isPresent()) { if (details.isPresent()) {
recipientDatabase.setRegistered(recipient, RegisteredState.REGISTERED); recipientDatabase.setRegistered(recipient, RegisteredState.REGISTERED);
RefreshResult result = updateContactsDatabase(context, Util.asList(recipient.getAddress()), false); updateContactsDatabase(context, Util.asList(recipient.getAddress()), false);
if (!result.getNewUsers().isEmpty() && TextSecurePreferences.isMultiDevice(context)) { if (!activeUser && TextSecurePreferences.isMultiDevice(context)) {
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context)); ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context));
} }
if (!result.isFresh()) { if (!activeUser) {
notifyNewUsers(context, masterSecret, result.getNewUsers()); notifyNewUsers(context, masterSecret, Collections.singletonList(recipient.getAddress()));
} }
return RegisteredState.REGISTERED; return RegisteredState.REGISTERED;
@ -131,13 +138,12 @@ public class DirectoryHelper {
} }
} }
private static @NonNull RefreshResult updateContactsDatabase(@NonNull Context context, @NonNull List<Address> activeAddresses, boolean removeMissing) { private static void updateContactsDatabase(@NonNull Context context, @NonNull List<Address> activeAddresses, boolean removeMissing) {
Optional<AccountHolder> account = getOrCreateAccount(context); Optional<AccountHolder> account = getOrCreateAccount(context);
if (account.isPresent()) { if (account.isPresent()) {
try { try {
List<Address> newUsers = DatabaseFactory.getContactsDatabase(context) DatabaseFactory.getContactsDatabase(context).setRegisteredUsers(account.get().getAccount(), activeAddresses, removeMissing);
.setRegisteredUsers(account.get().getAccount(), activeAddresses, removeMissing);
Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context); Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context);
RecipientDatabase.BulkOperationsHandle handle = DatabaseFactory.getRecipientDatabase(context).resetAllDisplayNames(); RecipientDatabase.BulkOperationsHandle handle = DatabaseFactory.getRecipientDatabase(context).resetAllDisplayNames();
@ -158,13 +164,10 @@ public class DirectoryHelper {
handle.finish(); handle.finish();
} }
return new RefreshResult(newUsers, account.get().isFresh());
} catch (RemoteException | OperationApplicationException e) { } catch (RemoteException | OperationApplicationException e) {
Log.w(TAG, e); Log.w(TAG, e);
} }
} }
return new RefreshResult(new LinkedList<Address>(), false);
} }
private static void notifyNewUsers(@NonNull Context context, private static void notifyNewUsers(@NonNull Context context,
@ -240,23 +243,4 @@ public class DirectoryHelper {
} }
private static class RefreshResult {
private final List<Address> newUsers;
private final boolean fresh;
private RefreshResult(List<Address> newUsers, boolean fresh) {
this.newUsers = newUsers;
this.fresh = fresh;
}
public List<Address> getNewUsers() {
return newUsers;
}
public boolean isFresh() {
return fresh;
}
}
} }