Use SIM country code when registered number is unavailable

Convert directory operations to Addresses

Fixes #6845
// FREEBIE
pull/1/head
Moxie Marlinspike 8 years ago
parent 4838fade6c
commit 7f46e99f9c

@ -1023,8 +1023,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
capabilities.getVideoCapability() == Capability.UNKNOWN) capabilities.getVideoCapability() == Capability.UNKNOWN)
{ {
try { try {
capabilities = DirectoryHelper.refreshDirectoryFor(context, masterSecret, recipients, capabilities = DirectoryHelper.refreshDirectoryFor(context, masterSecret, recipients);
TextSecurePreferences.getLocalNumber(context));
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
} }

@ -24,7 +24,6 @@ import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.GoogleApiAvailability;
import com.google.i18n.phonenumbers.AsYouTypeFormatter; import com.google.i18n.phonenumbers.AsYouTypeFormatter;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber; import com.google.i18n.phonenumbers.Phonenumber;
@ -32,6 +31,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
/** /**
@ -147,26 +147,17 @@ public class RegistrationActivity extends BaseActionBarActivity {
} }
private void initializeNumber() { private void initializeNumber() {
PhoneNumberUtil numberUtil = PhoneNumberUtil.getInstance(); Optional<Phonenumber.PhoneNumber> localNumber = Util.getDeviceNumber(this);
String localNumber = Util.getDeviceE164Number(this);
try { if (localNumber.isPresent()) {
if (!TextUtils.isEmpty(localNumber)) { this.countryCode.setText(String.valueOf(localNumber.get().getCountryCode()));
Phonenumber.PhoneNumber localNumberObject = numberUtil.parse(localNumber, null); this.number.setText(String.valueOf(localNumber.get().getNationalNumber()));
} else {
if (localNumberObject != null) { Optional<String> simCountryIso = Util.getSimCountryIso(this);
this.countryCode.setText(String.valueOf(localNumberObject.getCountryCode()));
this.number.setText(String.valueOf(localNumberObject.getNationalNumber()));
}
} else {
String simCountryIso = Util.getSimCountryIso(this);
if (!TextUtils.isEmpty(simCountryIso)) { if (simCountryIso.isPresent() && !TextUtils.isEmpty(simCountryIso.get())) {
this.countryCode.setText(numberUtil.getCountryCodeForRegion(simCountryIso)+""); this.countryCode.setText(PhoneNumberUtil.getInstance().getCountryCodeForRegion(simCountryIso.get())+"");
}
} }
} catch (NumberParseException npe) {
Log.w(TAG, npe);
} }
} }

@ -35,6 +35,7 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
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.push.ContactTokenDetails;
@ -76,29 +77,28 @@ public class ContactsDatabase {
this.context = context; this.context = context;
} }
public synchronized @NonNull List<String> setRegisteredUsers(@NonNull Account account, public synchronized @NonNull List<Address> setRegisteredUsers(@NonNull Account account,
@NonNull String localNumber, @NonNull List<ContactTokenDetails> registeredContacts,
@NonNull List<ContactTokenDetails> registeredContacts, boolean remove)
boolean remove)
throws RemoteException, OperationApplicationException throws RemoteException, OperationApplicationException
{ {
Map<String, ContactTokenDetails> registeredNumbers = new HashMap<>(); Map<Address, ContactTokenDetails> registeredAddresses = new HashMap<>();
List<String> addedNumbers = new LinkedList<>(); List<Address> addedAddresses = new LinkedList<>();
ArrayList<ContentProviderOperation> operations = new ArrayList<>(); ArrayList<ContentProviderOperation> operations = new ArrayList<>();
Map<String, SignalContact> currentContacts = getSignalRawContacts(account, localNumber); Map<Address, SignalContact> currentContacts = getSignalRawContacts(account);
for (ContactTokenDetails registeredContact : registeredContacts) { for (ContactTokenDetails registeredContact : registeredContacts) {
String registeredNumber = registeredContact.getNumber(); Address registeredAddress = Address.fromSerialized(registeredContact.getNumber());
registeredNumbers.put(registeredNumber, registeredContact); registeredAddresses.put(registeredAddress, registeredContact);
if (!currentContacts.containsKey(registeredNumber)) { if (!currentContacts.containsKey(registeredAddress)) {
Optional<SystemContactInfo> systemContactInfo = getSystemContactInfo(registeredNumber, localNumber); Optional<SystemContactInfo> systemContactInfo = getSystemContactInfo(registeredAddress);
if (systemContactInfo.isPresent()) { if (systemContactInfo.isPresent()) {
Log.w(TAG, "Adding number: " + registeredNumber); Log.w(TAG, "Adding number: " + registeredAddress);
addedNumbers.add(registeredNumber); 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);
@ -106,8 +106,8 @@ public class ContactsDatabase {
} }
} }
for (Map.Entry<String, SignalContact> currentContactEntry : currentContacts.entrySet()) { for (Map.Entry<Address, SignalContact> currentContactEntry : currentContacts.entrySet()) {
ContactTokenDetails tokenDetails = registeredNumbers.get(currentContactEntry.getKey()); ContactTokenDetails tokenDetails = registeredAddresses.get(currentContactEntry.getKey());
if (tokenDetails == null) { if (tokenDetails == null) {
if (remove) { if (remove) {
@ -129,7 +129,7 @@ public class ContactsDatabase {
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
} }
return addedNumbers; return addedAddresses;
} }
@NonNull Cursor querySystemContacts(String filter) { @NonNull Cursor querySystemContacts(String filter) {
@ -220,7 +220,7 @@ public class ContactsDatabase {
} }
private void addContactVoiceSupport(List<ContentProviderOperation> operations, private void addContactVoiceSupport(List<ContentProviderOperation> operations,
@NonNull String e164number, long rawContactId) @NonNull Address address, long rawContactId)
{ {
operations.add(ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI) operations.add(ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
.withSelection(RawContacts._ID + " = ?", new String[] {String.valueOf(rawContactId)}) .withSelection(RawContacts._ID + " = ?", new String[] {String.valueOf(rawContactId)})
@ -230,9 +230,9 @@ public class ContactsDatabase {
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build()) operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build())
.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId) .withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
.withValue(ContactsContract.Data.MIMETYPE, CALL_MIMETYPE) .withValue(ContactsContract.Data.MIMETYPE, CALL_MIMETYPE)
.withValue(ContactsContract.Data.DATA1, e164number) .withValue(ContactsContract.Data.DATA1, address.toPhoneString())
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name)) .withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, e164number)) .withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, address.toPhoneString()))
.withYieldAllowed(true) .withYieldAllowed(true)
.build()); .build());
} }
@ -346,15 +346,13 @@ public class ContactsDatabase {
.build()); .build());
} }
private @NonNull Map<String, SignalContact> getSignalRawContacts(@NonNull Account account, private @NonNull Map<Address, SignalContact> getSignalRawContacts(@NonNull Account account) {
@NonNull String localNumber)
{
Uri currentContactsUri = RawContacts.CONTENT_URI.buildUpon() Uri currentContactsUri = RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name) .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type).build(); .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type).build();
Map<String, SignalContact> signalContacts = new HashMap<>(); Map<Address, SignalContact> signalContacts = new HashMap<>();
Cursor cursor = null; Cursor cursor = null;
try { try {
String[] projection; String[] projection;
@ -368,21 +366,13 @@ public class ContactsDatabase {
cursor = context.getContentResolver().query(currentContactsUri, projection, null, null, null); cursor = context.getContentResolver().query(currentContactsUri, projection, null, null, null);
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
String currentNumber; Address currentAddress = Address.fromExternal(context, cursor.getString(1));
long rawContactId = cursor.getLong(0);
try { long contactId = cursor.getLong(3);
currentNumber = PhoneNumberFormatter.formatNumber(cursor.getString(1), localNumber); String supportsVoice = cursor.getString(2);
} catch (InvalidNumberException e) { String rawContactDisplayName = null;
Log.w(TAG, e); String aggregateDisplayName = null;
currentNumber = cursor.getString(1); int rawContactDisplayNameSource = 0;
}
long rawContactId = cursor.getLong(0);
long contactId = cursor.getLong(3);
String supportsVoice = cursor.getString(2);
String rawContactDisplayName = null;
String aggregateDisplayName = null;
int rawContactDisplayNameSource = 0;
if (Build.VERSION.SDK_INT >= 11) { if (Build.VERSION.SDK_INT >= 11) {
rawContactDisplayName = cursor.getString(4); rawContactDisplayName = cursor.getString(4);
@ -390,7 +380,7 @@ public class ContactsDatabase {
aggregateDisplayName = getDisplayName(contactId); aggregateDisplayName = getDisplayName(contactId);
} }
signalContacts.put(currentNumber, new SignalContact(rawContactId, supportsVoice, rawContactDisplayName, aggregateDisplayName, rawContactDisplayNameSource)); signalContacts.put(currentAddress, new SignalContact(rawContactId, supportsVoice, rawContactDisplayName, aggregateDisplayName, rawContactDisplayNameSource));
} }
} finally { } finally {
if (cursor != null) if (cursor != null)
@ -400,10 +390,11 @@ public class ContactsDatabase {
return signalContacts; return signalContacts;
} }
private Optional<SystemContactInfo> getSystemContactInfo(@NonNull String e164number, private Optional<SystemContactInfo> getSystemContactInfo(@NonNull Address address)
@NonNull String localNumber)
{ {
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(e164number)); if (!address.isPhone()) return Optional.absent();
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address.toPhoneString()));
String[] projection = {ContactsContract.PhoneLookup.NUMBER, String[] projection = {ContactsContract.PhoneLookup.NUMBER,
ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup._ID,
ContactsContract.PhoneLookup.DISPLAY_NAME}; ContactsContract.PhoneLookup.DISPLAY_NAME};
@ -414,25 +405,21 @@ public class ContactsDatabase {
numberCursor = context.getContentResolver().query(uri, projection, null, null, null); numberCursor = context.getContentResolver().query(uri, projection, null, null, null);
while (numberCursor != null && numberCursor.moveToNext()) { while (numberCursor != null && numberCursor.moveToNext()) {
try { String systemNumber = numberCursor.getString(0);
String systemNumber = numberCursor.getString(0); Address systemAddress = Address.fromExternal(context, systemNumber);
String canonicalizedSystemNumber = PhoneNumberFormatter.formatNumber(systemNumber, localNumber);
if (systemAddress.equals(address)) {
if (canonicalizedSystemNumber.equals(e164number)) { idCursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
idCursor = context.getContentResolver().query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
new String[] {RawContacts._ID}, RawContacts.CONTACT_ID + " = ? ",
RawContacts.CONTACT_ID + " = ? ", new String[] {String.valueOf(numberCursor.getLong(1))},
new String[] {String.valueOf(numberCursor.getLong(1))}, null);
null);
if (idCursor != null && idCursor.moveToNext()) {
if (idCursor != null && idCursor.moveToNext()) { return Optional.of(new SystemContactInfo(numberCursor.getString(2),
return Optional.of(new SystemContactInfo(numberCursor.getString(2), numberCursor.getString(0),
numberCursor.getString(0), idCursor.getLong(0)));
idCursor.getLong(0)));
}
} }
} catch (InvalidNumberException e) {
Log.w(TAG, e);
} }
} }
} finally { } finally {

@ -7,6 +7,7 @@ import android.os.Parcelable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.NumberParseException;
@ -20,7 +21,6 @@ import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
@ -60,7 +60,17 @@ public class Address implements Parcelable, Comparable<Address> {
} }
public static Address fromExternal(@NonNull Context context, @Nullable String external) { public static Address fromExternal(@NonNull Context context, @Nullable String external) {
return new Address(new ExternalAddressFormatter(TextSecurePreferences.getLocalNumber(context)).format(external)); String localNumber = TextSecurePreferences.getLocalNumber(context);
ExternalAddressFormatter formatter;
if (!TextUtils.isEmpty(localNumber)) {
formatter = new ExternalAddressFormatter(localNumber);
} else {
formatter = new ExternalAddressFormatter(Util.getSimCountryIso(context).or("US"), true);
}
return new Address(formatter.format(external));
} }
public static @NonNull List<Address> fromSerializedList(@NonNull String serialized, char delimiter) { public static @NonNull List<Address> fromSerializedList(@NonNull String serialized, char delimiter) {
@ -171,23 +181,28 @@ public class Address implements Parcelable, Comparable<Address> {
add("AC"); add("AC");
}}; }};
private final Phonenumber.PhoneNumber localNumber; private final String localNumberString;
private final String localNumberString; private final String localCountryCode;
private final String localCountryCode;
private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
private final Pattern ALPHA_PATTERN = Pattern.compile("[a-zA-Z]"); private final Pattern ALPHA_PATTERN = Pattern.compile("[a-zA-Z]");
public ExternalAddressFormatter(String localNumber) { ExternalAddressFormatter(@NonNull String localNumberString) {
try { try {
this.localNumberString = localNumber; Phonenumber.PhoneNumber localNumber = phoneNumberUtil.parse(localNumberString, null);
this.localNumber = phoneNumberUtil.parse(localNumber, null);
this.localCountryCode = phoneNumberUtil.getRegionCodeForNumber(this.localNumber); this.localNumberString = localNumberString;
this.localCountryCode = phoneNumberUtil.getRegionCodeForNumber(localNumber);
} catch (NumberParseException e) { } catch (NumberParseException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
ExternalAddressFormatter(@NonNull String localCountryCode, boolean countryCode) {
this.localNumberString = "";
this.localCountryCode = localCountryCode;
}
public String format(@Nullable String number) { public String format(@Nullable String number) {
if (number == null) return "Unknown"; if (number == null) return "Unknown";
if (number.startsWith("__textsecure_group__!")) return number; if (number.startsWith("__textsecure_group__!")) return number;

@ -8,11 +8,10 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri; import android.net.Uri;
import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import org.whispersystems.signalservice.api.push.ContactTokenDetails; 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.Collection; import java.util.Collection;
@ -173,7 +172,7 @@ public class TextSecureDirectory {
db.replace(TABLE_NAME, null, values); db.replace(TABLE_NAME, null, values);
} }
public void setNumbers(List<ContactTokenDetails> activeTokens, Collection<String> inactiveTokens) { public void setNumbers(List<ContactTokenDetails> activeTokens, Collection<Address> inactiveAddresses) {
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.beginTransaction(); db.beginTransaction();
@ -191,9 +190,9 @@ public class TextSecureDirectory {
db.replace(TABLE_NAME, null, values); db.replace(TABLE_NAME, null, values);
} }
for (String token : inactiveTokens) { for (Address address : inactiveAddresses) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(NUMBER, token); values.put(NUMBER, address.serialize());
values.put(REGISTERED, 0); values.put(REGISTERED, 0);
values.put(TIMESTAMP, timestamp); values.put(TIMESTAMP, timestamp);
db.replace(TABLE_NAME, null, values); db.replace(TABLE_NAME, null, values);
@ -205,23 +204,18 @@ public class TextSecureDirectory {
} }
} }
public Set<String> getPushEligibleContactNumbers(String localNumber) { public Set<Address> getPushEligibleContactNumbers() {
final Uri uri = Phone.CONTENT_URI; final Uri uri = Phone.CONTENT_URI;
final Set<String> results = new HashSet<>(); final Set<Address> results = new HashSet<>();
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = context.getContentResolver().query(uri, new String[] {Phone.NUMBER}, null, null, null); cursor = context.getContentResolver().query(uri, new String[] {Phone.NUMBER}, null, null, null);
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
final String rawNumber = cursor.getString(0); final String rawNumber = cursor.getString(0);
if (rawNumber != null) { if (!TextUtils.isEmpty(rawNumber)) {
try { results.add(Address.fromExternal(context, rawNumber));
final String e164Number = PhoneNumberFormatter.formatNumber(rawNumber, localNumber);
results.add(e164Number);
} catch (InvalidNumberException e) {
Log.w("Directory", "Invalid number: " + rawNumber);
}
} }
} }
@ -234,7 +228,7 @@ public class TextSecureDirectory {
null, null, null, null, null); null, null, null, null, null);
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
results.add(cursor.getString(0)); results.add(Address.fromSerialized(cursor.getString(0)));
} }
} }

@ -54,7 +54,7 @@ public class DirectoryRefreshJob extends ContextJob {
if (recipients == null) { if (recipients == null) {
DirectoryHelper.refreshDirectory(context, KeyCachingService.getMasterSecret(context)); DirectoryHelper.refreshDirectory(context, KeyCachingService.getMasterSecret(context));
} else { } else {
DirectoryHelper.refreshDirectoryFor(context, masterSecret, recipients, TextSecurePreferences.getLocalNumber(context)); DirectoryHelper.refreshDirectoryFor(context, masterSecret, recipients);
} }
SecurityEvent.broadcastSecurityUpdateEvent(context); SecurityEvent.broadcastSecurityUpdateEvent(context);
} finally { } finally {

@ -206,6 +206,7 @@ public class RegistrationService extends Service {
String challenge = waitForChallenge(); String challenge = waitForChallenge();
accountManager.verifyAccountWithCode(challenge, signalingKey, registrationId, !supportsGcm); accountManager.verifyAccountWithCode(challenge, signalingKey, registrationId, !supportsGcm);
TextSecurePreferences.setLocalNumber(this, number);
handleCommonRegistration(accountManager, number, password, signalingKey, supportsGcm); handleCommonRegistration(accountManager, number, password, signalingKey, supportsGcm);
markAsVerified(number, password, signalingKey); markAsVerified(number, password, signalingKey);
@ -257,7 +258,7 @@ public class RegistrationService extends Service {
TextSecurePreferences.setWebsocketRegistered(this, true); TextSecurePreferences.setWebsocketRegistered(this, true);
DatabaseFactory.getIdentityDatabase(this).saveIdentity(self, identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true); DatabaseFactory.getIdentityDatabase(this).saveIdentity(self, identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true);
DirectoryHelper.refreshDirectory(this, accountManager, number); DirectoryHelper.refreshDirectory(this, accountManager);
DirectoryRefreshListener.schedule(this); DirectoryRefreshListener.schedule(this);
RotateSignedPreKeyListener.schedule(this); RotateSignedPreKeyListener.schedule(this);

@ -9,6 +9,7 @@ import android.os.RemoteException;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
@ -32,6 +33,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.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -75,9 +77,9 @@ public class DirectoryHelper {
public static void refreshDirectory(@NonNull Context context, @Nullable MasterSecret masterSecret) public static void refreshDirectory(@NonNull Context context, @Nullable MasterSecret masterSecret)
throws IOException throws IOException
{ {
RefreshResult result = refreshDirectory(context, if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) return;
AccountManagerFactory.createManager(context),
TextSecurePreferences.getLocalNumber(context)); RefreshResult result = refreshDirectory(context, AccountManagerFactory.createManager(context));
if (!result.getNewUsers().isEmpty() && TextSecurePreferences.isMultiDevice(context)) { if (!result.getNewUsers().isEmpty() && TextSecurePreferences.isMultiDevice(context)) {
ApplicationContext.getInstance(context) ApplicationContext.getInstance(context)
@ -90,32 +92,38 @@ public class DirectoryHelper {
} }
} }
public static @NonNull RefreshResult refreshDirectory(@NonNull Context context, public static @NonNull RefreshResult refreshDirectory(@NonNull Context context, @NonNull SignalServiceAccountManager accountManager)
@NonNull SignalServiceAccountManager accountManager,
@NonNull String localNumber)
throws IOException throws IOException
{ {
TextSecureDirectory directory = TextSecureDirectory.getInstance(context); if (TextUtils.isEmpty(TextSecurePreferences.getLocalNumber(context))) {
Set<String> eligibleContactNumbers = directory.getPushEligibleContactNumbers(localNumber); return new RefreshResult(new LinkedList<Address>(), false);
List<ContactTokenDetails> activeTokens = accountManager.getContacts(eligibleContactNumbers); }
TextSecureDirectory directory = TextSecureDirectory.getInstance(context);
Set<Address> eligibleContactNumbers = directory.getPushEligibleContactNumbers();
Set<String> serializedAddresses = new HashSet<>();
for (Address address : eligibleContactNumbers) {
serializedAddresses.add(address.serialize());
}
List<ContactTokenDetails> activeTokens = accountManager.getContacts(serializedAddresses);
if (activeTokens != null) { if (activeTokens != null) {
for (ContactTokenDetails activeToken : activeTokens) { for (ContactTokenDetails activeToken : activeTokens) {
eligibleContactNumbers.remove(activeToken.getNumber()); eligibleContactNumbers.remove(Address.fromSerialized(activeToken.getNumber()));
activeToken.setNumber(activeToken.getNumber());
} }
directory.setNumbers(activeTokens, eligibleContactNumbers); directory.setNumbers(activeTokens, eligibleContactNumbers);
return updateContactsDatabase(context, localNumber, activeTokens, true); return updateContactsDatabase(context, activeTokens, true);
} }
return new RefreshResult(new LinkedList<String>(), false); return new RefreshResult(new LinkedList<Address>(), false);
} }
public static UserCapabilities refreshDirectoryFor(@NonNull Context context, public static UserCapabilities refreshDirectoryFor(@NonNull Context context,
@Nullable MasterSecret masterSecret, @Nullable MasterSecret masterSecret,
@NonNull Recipients recipients, @NonNull Recipients recipients)
@NonNull String localNumber)
throws IOException throws IOException
{ {
TextSecureDirectory directory = TextSecureDirectory.getInstance(context); TextSecureDirectory directory = TextSecureDirectory.getInstance(context);
@ -126,7 +134,7 @@ public class DirectoryHelper {
if (details.isPresent()) { if (details.isPresent()) {
directory.setNumber(details.get(), true); directory.setNumber(details.get(), true);
RefreshResult result = updateContactsDatabase(context, localNumber, details.get()); RefreshResult result = updateContactsDatabase(context, details.get());
if (!result.getNewUsers().isEmpty() && TextSecurePreferences.isMultiDevice(context)) { if (!result.getNewUsers().isEmpty() && TextSecurePreferences.isMultiDevice(context)) {
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context)); ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceContactUpdateJob(context));
@ -181,16 +189,12 @@ public class DirectoryHelper {
} }
private static @NonNull RefreshResult updateContactsDatabase(@NonNull Context context, private static @NonNull RefreshResult updateContactsDatabase(@NonNull Context context,
@NonNull String localNumber,
@NonNull final ContactTokenDetails activeToken) @NonNull final ContactTokenDetails activeToken)
{ {
return updateContactsDatabase(context, localNumber, return updateContactsDatabase(context, new LinkedList<ContactTokenDetails>() {{add(activeToken);}}, false);
new LinkedList<ContactTokenDetails>() {{add(activeToken);}},
false);
} }
private static @NonNull RefreshResult updateContactsDatabase(@NonNull Context context, private static @NonNull RefreshResult updateContactsDatabase(@NonNull Context context,
@NonNull String localNumber,
@NonNull List<ContactTokenDetails> activeTokens, @NonNull List<ContactTokenDetails> activeTokens,
boolean removeMissing) boolean removeMissing)
{ {
@ -198,8 +202,8 @@ public class DirectoryHelper {
if (account.isPresent()) { if (account.isPresent()) {
try { try {
List<String> newUsers = DatabaseFactory.getContactsDatabase(context) List<Address> newUsers = DatabaseFactory.getContactsDatabase(context)
.setRegisteredUsers(account.get().getAccount(), localNumber, activeTokens, removeMissing); .setRegisteredUsers(account.get().getAccount(), activeTokens, removeMissing);
return new RefreshResult(newUsers, account.get().isFresh()); return new RefreshResult(newUsers, account.get().isFresh());
} catch (RemoteException | OperationApplicationException e) { } catch (RemoteException | OperationApplicationException e) {
@ -207,18 +211,16 @@ public class DirectoryHelper {
} }
} }
return new RefreshResult(new LinkedList<String>(), false); return new RefreshResult(new LinkedList<Address>(), false);
} }
private static void notifyNewUsers(@NonNull Context context, private static void notifyNewUsers(@NonNull Context context,
@Nullable MasterSecret masterSecret, @Nullable MasterSecret masterSecret,
@NonNull List<String> newUsers) @NonNull List<Address> newUsers)
{ {
if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) return; if (!TextSecurePreferences.isNewContactsNotificationEnabled(context)) return;
for (String newUserString : newUsers) { for (Address newUser: newUsers) {
Address newUser = Address.fromSerialized(newUserString);
if (!SessionUtil.hasSession(context, masterSecret, newUser) && !Util.isOwnNumber(context, newUser)) { if (!SessionUtil.hasSession(context, masterSecret, newUser) && !Util.isOwnNumber(context, newUser)) {
IncomingJoinedMessage message = new IncomingJoinedMessage(newUser); IncomingJoinedMessage message = new IncomingJoinedMessage(newUser);
Optional<InsertResult> insertResult = DatabaseFactory.getSmsDatabase(context).insertMessageInbox(message); Optional<InsertResult> insertResult = DatabaseFactory.getSmsDatabase(context).insertMessageInbox(message);
@ -287,15 +289,15 @@ public class DirectoryHelper {
private static class RefreshResult { private static class RefreshResult {
private final List<String> newUsers; private final List<Address> newUsers;
private final boolean fresh; private final boolean fresh;
private RefreshResult(List<String> newUsers, boolean fresh) { private RefreshResult(List<Address> newUsers, boolean fresh) {
this.newUsers = newUsers; this.newUsers = newUsers;
this.fresh = fresh; this.fresh = fresh;
} }
public List<String> getNewUsers() { public List<Address> getNewUsers() {
return newUsers; return newUsers;
} }

@ -304,7 +304,7 @@ public class TextSecurePreferences {
} }
public static String getLocalNumber(Context context) { public static String getLocalNumber(Context context) {
return getStringPreference(context, LOCAL_NUMBER_PREF, "No Stored Number"); return getStringPreference(context, LOCAL_NUMBER_PREF, null);
} }
public static void setLocalNumber(Context context, String localNumber) { public static void setLocalNumber(Context context, String localNumber) {

@ -42,11 +42,14 @@ import android.widget.EditText;
import com.google.android.mms.pdu_alt.CharacterSets; import com.google.android.mms.pdu_alt.CharacterSets;
import com.google.android.mms.pdu_alt.EncodedStringValue; import com.google.android.mms.pdu_alt.EncodedStringValue;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection; import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -234,22 +237,24 @@ public class Util {
return total; return total;
} }
public static @Nullable String getDeviceE164Number(Context context) { public static Optional<Phonenumber.PhoneNumber> getDeviceNumber(Context context) {
final String localNumber = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number(); try {
final String countryIso = getSimCountryIso(context); final String localNumber = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
final Integer countryCode = PhoneNumberUtil.getInstance().getCountryCodeForRegion(countryIso); final Optional<String> countryIso = getSimCountryIso(context);
if (TextUtils.isEmpty(localNumber)) return null; if (TextUtils.isEmpty(localNumber)) return Optional.absent();
if (!countryIso.isPresent()) return Optional.absent();
if (localNumber.startsWith("+")) return localNumber; return Optional.fromNullable(PhoneNumberUtil.getInstance().parse(localNumber, countryIso.get()));
else if (!TextUtils.isEmpty(countryIso)) return PhoneNumberFormatter.formatE164(String.valueOf(countryCode), localNumber); } catch (NumberParseException e) {
else if (localNumber.length() == 10) return "+1" + localNumber; Log.w(TAG, e);
else return "+" + localNumber; return Optional.absent();
}
} }
public static @Nullable String getSimCountryIso(Context context) { public static Optional<String> getSimCountryIso(Context context) {
String simCountryIso = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getSimCountryIso(); String simCountryIso = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getSimCountryIso();
return simCountryIso != null ? simCountryIso.toUpperCase() : null; return Optional.fromNullable(simCountryIso != null ? simCountryIso.toUpperCase() : null);
} }
public static <T> List<List<T>> partition(List<T> list, int partitionSize) { public static <T> List<List<T>> partition(List<T> list, int partitionSize) {

@ -39,6 +39,9 @@ public class AddressTest {
assertEquals(formatter.format("+1 415.111.1126"), "+14151111126"); assertEquals(formatter.format("+1 415.111.1126"), "+14151111126");
assertEquals(formatter.format("+1 415 111 1127"), "+14151111127"); assertEquals(formatter.format("+1 415 111 1127"), "+14151111127");
assertEquals(formatter.format("+1 (415) 111 1128"), "+14151111128"); assertEquals(formatter.format("+1 (415) 111 1128"), "+14151111128");
formatter = new Address.ExternalAddressFormatter("+442079460010");
assertEquals(formatter.format("(020) 7946 0018"), "+442079460018");
} }
@Test @Test
@ -47,4 +50,10 @@ public class AddressTest {
assertEquals(formatter.format("__textsecure_group__!foobar"), "__textsecure_group__!foobar"); assertEquals(formatter.format("__textsecure_group__!foobar"), "__textsecure_group__!foobar");
} }
@Test
public void testLostLocalNumber() throws Exception {
Address.ExternalAddressFormatter formatter = new Address.ExternalAddressFormatter("US", true);
assertEquals(formatter.format("(415) 111-1122"), "+14151111122");
}
} }

Loading…
Cancel
Save