Signal-Android/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
Moxie Marlinspike 737810475e Remove the Canonical Address Database
This was a holdover from Signal's origins as a pure SMS app.
It causes problems, depends on undefined device specific behavior,
and should no longer be necessary now that we have all the
information we need to E164 all numbers.

// FREEBIE
2017-07-31 12:02:29 -07:00

540 lines
21 KiB
Java

package org.thoughtcrime.securesms;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.preference.PreferenceFragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.color.MaterialColors;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.preferences.AdvancedRingtonePreference;
import org.thoughtcrime.securesms.preferences.ColorPreference;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.concurrent.ExecutionException;
public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActivity implements Recipients.RecipientsModifiedListener
{
private static final String TAG = RecipientPreferenceActivity.class.getSimpleName();
public static final String ADDRESSES_EXTRA = "recipient_addresses";
public static final String CAN_HAVE_SAFETY_NUMBER_EXTRA = "can_have_safety_number";
private static final String PREFERENCE_MUTED = "pref_key_recipient_mute";
private static final String PREFERENCE_TONE = "pref_key_recipient_ringtone";
private static final String PREFERENCE_VIBRATE = "pref_key_recipient_vibrate";
private static final String PREFERENCE_BLOCK = "pref_key_recipient_block";
private static final String PREFERENCE_COLOR = "pref_key_recipient_color";
private static final String PREFERENCE_IDENTITY = "pref_key_recipient_identity";
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private AvatarImageView avatar;
private Toolbar toolbar;
private TextView title;
private TextView blockedIndicator;
private BroadcastReceiver staleReceiver;
@Override
public void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
public void onCreate(Bundle instanceState, @NonNull MasterSecret masterSecret) {
setContentView(R.layout.recipient_preference_activity);
Address[] addresses = Address.fromParcelable(getIntent().getParcelableArrayExtra(ADDRESSES_EXTRA));
Recipients recipients = RecipientFactory.getRecipientsFor(this, addresses, true);
initializeToolbar();
initializeReceivers();
setHeader(recipients);
recipients.addListener(this);
Bundle bundle = new Bundle();
bundle.putParcelableArray(ADDRESSES_EXTRA, addresses);
initFragment(R.id.preference_fragment, new RecipientPreferenceFragment(), masterSecret, null, bundle);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(staleReceiver);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.preference_fragment);
fragment.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home:
super.onBackPressed();
return true;
}
return false;
}
private void initializeToolbar() {
this.toolbar = (Toolbar) findViewById(R.id.toolbar);
this.toolbar.setLogo(null);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowTitleEnabled(false);
this.avatar = (AvatarImageView) toolbar.findViewById(R.id.avatar);
this.title = (TextView) toolbar.findViewById(R.id.name);
this.blockedIndicator = (TextView) toolbar.findViewById(R.id.blocked_indicator);
}
private void initializeReceivers() {
this.staleReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Recipients recipients = RecipientFactory.getRecipientsFor(context, Address.fromParcelable(getIntent().getParcelableArrayExtra(ADDRESSES_EXTRA)), true);
recipients.addListener(RecipientPreferenceActivity.this);
onModified(recipients);
}
};
IntentFilter staleFilter = new IntentFilter();
staleFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION);
staleFilter.addAction(RecipientFactory.RECIPIENT_CLEAR_ACTION);
registerReceiver(staleReceiver, staleFilter);
}
private void setHeader(Recipients recipients) {
this.avatar.setAvatar(recipients, true);
this.title.setText(recipients.toShortString());
this.toolbar.setBackgroundColor(recipients.getColor().toActionBarColor(this));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(recipients.getColor().toStatusBarColor(this));
}
if (recipients.isBlocked()) this.blockedIndicator.setVisibility(View.VISIBLE);
else this.blockedIndicator.setVisibility(View.GONE);
}
@Override
public void onModified(final Recipients recipients) {
title.post(new Runnable() {
@Override
public void run() {
setHeader(recipients);
}
});
}
public static class RecipientPreferenceFragment
extends PreferenceFragment
implements Recipients.RecipientsModifiedListener
{
private final Handler handler = new Handler();
private Recipients recipients;
private BroadcastReceiver staleReceiver;
private MasterSecret masterSecret;
private boolean canHaveSafetyNumber;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.recipient_preferences);
initializeRecipients();
this.masterSecret = getArguments().getParcelable("master_secret");
this.canHaveSafetyNumber = getActivity().getIntent()
.getBooleanExtra(RecipientPreferenceActivity.CAN_HAVE_SAFETY_NUMBER_EXTRA, false);
this.findPreference(PREFERENCE_TONE)
.setOnPreferenceChangeListener(new RingtoneChangeListener());
this.findPreference(PREFERENCE_VIBRATE)
.setOnPreferenceChangeListener(new VibrateChangeListener());
this.findPreference(PREFERENCE_MUTED)
.setOnPreferenceClickListener(new MuteClickedListener());
this.findPreference(PREFERENCE_BLOCK)
.setOnPreferenceClickListener(new BlockClickedListener());
this.findPreference(PREFERENCE_COLOR)
.setOnPreferenceChangeListener(new ColorChangeListener());
}
@Override
public void onResume() {
super.onResume();
setSummaries(recipients);
}
@Override
public void onDestroy() {
super.onDestroy();
this.recipients.removeListener(this);
getActivity().unregisterReceiver(staleReceiver);
}
private void initializeRecipients() {
this.recipients = RecipientFactory.getRecipientsFor(getActivity(),
Address.fromParcelable(getArguments().getParcelableArray(ADDRESSES_EXTRA)),
true);
this.recipients.addListener(this);
this.staleReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
recipients.removeListener(RecipientPreferenceFragment.this);
recipients = RecipientFactory.getRecipientsFor(getActivity(), Address.fromParcelable(getArguments().getParcelableArray(ADDRESSES_EXTRA)), true);
onModified(recipients);
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(GroupDatabase.DATABASE_UPDATE_ACTION);
intentFilter.addAction(RecipientFactory.RECIPIENT_CLEAR_ACTION);
getActivity().registerReceiver(staleReceiver, intentFilter);
}
private void setSummaries(Recipients recipients) {
CheckBoxPreference mutePreference = (CheckBoxPreference) this.findPreference(PREFERENCE_MUTED);
AdvancedRingtonePreference ringtonePreference = (AdvancedRingtonePreference) this.findPreference(PREFERENCE_TONE);
ListPreference vibratePreference = (ListPreference) this.findPreference(PREFERENCE_VIBRATE);
ColorPreference colorPreference = (ColorPreference) this.findPreference(PREFERENCE_COLOR);
Preference blockPreference = this.findPreference(PREFERENCE_BLOCK);
final Preference identityPreference = this.findPreference(PREFERENCE_IDENTITY);
mutePreference.setChecked(recipients.isMuted());
final Uri toneUri = recipients.getRingtone();
if (toneUri == null) {
ringtonePreference.setSummary(R.string.preferences__default);
ringtonePreference.setCurrentRingtone(Settings.System.DEFAULT_NOTIFICATION_URI);
} else if (toneUri.toString().isEmpty()) {
ringtonePreference.setSummary(R.string.preferences__silent);
ringtonePreference.setCurrentRingtone(null);
} else {
Ringtone tone = RingtoneManager.getRingtone(getActivity(), toneUri);
if (tone != null) {
ringtonePreference.setSummary(tone.getTitle(getActivity()));
ringtonePreference.setCurrentRingtone(toneUri);
}
}
if (recipients.getVibrate() == VibrateState.DEFAULT) {
vibratePreference.setSummary(R.string.preferences__default);
vibratePreference.setValueIndex(0);
} else if (recipients.getVibrate() == VibrateState.ENABLED) {
vibratePreference.setSummary(R.string.RecipientPreferenceActivity_enabled);
vibratePreference.setValueIndex(1);
} else {
vibratePreference.setSummary(R.string.RecipientPreferenceActivity_disabled);
vibratePreference.setValueIndex(2);
}
if (!recipients.isSingleRecipient() || recipients.isGroupRecipient()) {
if (colorPreference != null) getPreferenceScreen().removePreference(colorPreference);
if (blockPreference != null) getPreferenceScreen().removePreference(blockPreference);
if (identityPreference != null) getPreferenceScreen().removePreference(identityPreference);
} else {
colorPreference.setChoices(MaterialColors.CONVERSATION_PALETTE.asConversationColorArray(getActivity()));
colorPreference.setValue(recipients.getColor().toActionBarColor(getActivity()));
if (recipients.isBlocked()) blockPreference.setTitle(R.string.RecipientPreferenceActivity_unblock);
else blockPreference.setTitle(R.string.RecipientPreferenceActivity_block);
IdentityUtil.getRemoteIdentityKey(getActivity(), recipients.getPrimaryRecipient()).addListener(new ListenableFuture.Listener<Optional<IdentityRecord>>() {
@Override
public void onSuccess(Optional<IdentityRecord> result) {
if (result.isPresent()) {
if (identityPreference != null) identityPreference.setOnPreferenceClickListener(new IdentityClickedListener(result.get()));
if (identityPreference != null) identityPreference.setEnabled(true);
} else if (canHaveSafetyNumber) {
if (identityPreference != null) identityPreference.setSummary(R.string.RecipientPreferenceActivity_available_once_a_message_has_been_sent_or_received);
if (identityPreference != null) identityPreference.setEnabled(false);
} else {
if (identityPreference != null) getPreferenceScreen().removePreference(identityPreference);
}
}
@Override
public void onFailure(ExecutionException e) {
if (identityPreference != null) getPreferenceScreen().removePreference(identityPreference);
}
});
}
}
@Override
public void onModified(final Recipients recipients) {
handler.post(new Runnable() {
@Override
public void run() {
setSummaries(recipients);
}
});
}
private class RingtoneChangeListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String value = (String)newValue;
final Uri uri;
if (Settings.System.DEFAULT_NOTIFICATION_URI.toString().equals(value)) {
uri = null;
} else {
uri = Uri.parse(value);
}
recipients.setRingtone(uri);
new AsyncTask<Uri, Void, Void>() {
@Override
protected Void doInBackground(Uri... params) {
DatabaseFactory.getRecipientPreferenceDatabase(getActivity())
.setRingtone(recipients, params[0]);
return null;
}
}.execute(uri);
return false;
}
}
private class VibrateChangeListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
int value = Integer.parseInt((String) newValue);
final VibrateState vibrateState = VibrateState.fromId(value);
recipients.setVibrate(vibrateState);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(getActivity())
.setVibrate(recipients, vibrateState);
return null;
}
}.execute();
return false;
}
}
private class ColorChangeListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final int value = (Integer) newValue;
final MaterialColor selectedColor = MaterialColors.CONVERSATION_PALETTE.getByColor(getActivity(), value);
final MaterialColor currentColor = recipients.getColor();
if (selectedColor == null) return true;
if (preference.isEnabled() && !currentColor.equals(selectedColor)) {
recipients.setColor(selectedColor);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
Context context = getActivity();
DatabaseFactory.getRecipientPreferenceDatabase(context)
.setColor(recipients, selectedColor);
if (DirectoryHelper.getUserCapabilities(context, recipients)
.getTextCapability() == DirectoryHelper.UserCapabilities.Capability.SUPPORTED)
{
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceContactUpdateJob(context, recipients.getPrimaryRecipient().getAddress()));
}
return null;
}
}.execute();
}
return true;
}
}
private class MuteClickedListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (recipients.isMuted()) handleUnmute();
else handleMute();
return true;
}
private void handleMute() {
MuteDialog.show(getActivity(), new MuteDialog.MuteSelectionListener() {
@Override
public void onMuted(long until) {
setMuted(recipients, until);
}
});
setSummaries(recipients);
}
private void handleUnmute() {
setMuted(recipients, 0);
}
private void setMuted(final Recipients recipients, final long until) {
recipients.setMuted(until);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(getActivity())
.setMuted(recipients, until);
return null;
}
}.execute();
}
}
private class IdentityClickedListener implements Preference.OnPreferenceClickListener {
private final IdentityRecord identityKey;
private IdentityClickedListener(IdentityRecord identityKey) {
Log.w(TAG, "Identity record: " + identityKey);
this.identityKey = identityKey;
}
@Override
public boolean onPreferenceClick(Preference preference) {
Intent verifyIdentityIntent = new Intent(getActivity(), VerifyIdentityActivity.class);
verifyIdentityIntent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, recipients.getPrimaryRecipient().getAddress());
verifyIdentityIntent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey.getIdentityKey()));
verifyIdentityIntent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, identityKey.getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
startActivity(verifyIdentityIntent);
return true;
}
}
private class BlockClickedListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (recipients.isBlocked()) handleUnblock();
else handleBlock();
return true;
}
private void handleBlock() {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.RecipientPreferenceActivity_block_this_contact_question)
.setMessage(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact)
.setCancelable(true)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.RecipientPreferenceActivity_block, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setBlocked(recipients, true);
}
}).show();
}
private void handleUnblock() {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.RecipientPreferenceActivity_unblock_this_contact_question)
.setMessage(R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact)
.setCancelable(true)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setBlocked(recipients, false);
}
}).show();
}
private void setBlocked(final Recipients recipients, final boolean blocked) {
recipients.setBlocked(blocked);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
Context context = getActivity();
DatabaseFactory.getRecipientPreferenceDatabase(context)
.setBlocked(recipients, blocked);
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceBlockedUpdateJob(context));
return null;
}
}.execute();
}
}
}
}