Support for per-recipient muting, blocking, and ringtones.

Fixes #757
Fixes #354
Fixes #222
Closes #1815
Closes #3378

// FREEBIE
This commit is contained in:
Moxie Marlinspike
2015-06-09 07:37:20 -07:00
parent cb3cf7789f
commit 40af2a81db
80 changed files with 1858 additions and 321 deletions

View File

@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.ViewConfiguration;
@@ -10,7 +10,7 @@ import android.view.ViewConfiguration;
import java.lang.reflect.Field;
public abstract class BaseActionBarActivity extends ActionBarActivity {
public abstract class BaseActionBarActivity extends AppCompatActivity {
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
@Override
@@ -46,7 +46,9 @@ public abstract class BaseActionBarActivity extends ActionBarActivity {
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
}
} catch (IllegalAccessException | NoSuchFieldException e) {
} catch (IllegalAccessException e) {
Log.w(TAG, "Failed to force overflow menu.");
} catch (NoSuchFieldException e) {
Log.w(TAG, "Failed to force overflow menu.");
}
}

View File

@@ -0,0 +1,136 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader;
import org.thoughtcrime.securesms.preferences.BlockedContactListItem;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity {
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@Override
public void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
public void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.BlockedContactsActivity_blocked_contacts);
initFragment(android.R.id.content, new BlockedContactsFragment(), masterSecret);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: finish(); return true;
}
return false;
}
public static class BlockedContactsFragment
extends ListFragment
implements LoaderManager.LoaderCallbacks<Cursor>, ListView.OnItemClickListener
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
return inflater.inflate(R.layout.blocked_contacts_fragment, container, false);
}
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setListAdapter(new BlockedContactAdapter(getActivity(), null));
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
getListView().setOnItemClickListener(this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new BlockedContactsLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (getListAdapter() != null) {
((CursorAdapter) getListAdapter()).changeCursor(data);
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
if (getListAdapter() != null) {
((CursorAdapter) getListAdapter()).changeCursor(null);
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Recipients recipients = ((BlockedContactListItem)view).getRecipients();
Intent intent = new Intent(getActivity(), RecipientPreferenceActivity.class);
intent.putExtra(RecipientPreferenceActivity.RECIPIENTS_EXTRA, recipients.getIds());
startActivity(intent);
}
private static class BlockedContactAdapter extends CursorAdapter {
public BlockedContactAdapter(Context context, Cursor c) {
super(context, c);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context)
.inflate(R.layout.blocked_contact_list_item, parent, false);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
String recipientIds = cursor.getString(1);
Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientIds, true);
((BlockedContactListItem) view).set(recipients);
}
}
}
}

View File

@@ -27,13 +27,13 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.telephony.PhoneNumberUtils;
import android.support.v7.app.ActionBar.LayoutParams;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -44,6 +44,7 @@ import android.view.View.OnKeyListener;
import android.view.ViewStub;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
@@ -54,9 +55,9 @@ import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.components.ComposeText;
import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.crypto.MasterCipher;
@@ -83,6 +84,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.recipients.Recipients.RecipientsModifiedListener;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
@@ -107,7 +109,6 @@ import java.util.List;
import static org.thoughtcrime.securesms.TransportOption.Type;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import static org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext;
/**
@@ -120,7 +121,7 @@ import static org.whispersystems.textsecure.internal.push.PushMessageProtos.Push
public class ConversationActivity extends PassphraseRequiredActionBarActivity
implements ConversationFragment.ConversationFragmentListener,
AttachmentManager.AttachmentListener,
RecipientModifiedListener
RecipientsModifiedListener
{
private static final String TAG = ConversationActivity.class.getSimpleName();
@@ -139,13 +140,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private static final int GROUP_EDIT = 5;
private static final int CAPTURE_PHOTO = 6;
private MasterSecret masterSecret;
private ComposeText composeText;
private AnimatingToggle buttonToggle;
private SendButton sendButton;
private ImageButton attachButton;
private TextView charactersLeft;
private ConversationFragment fragment;
private MasterSecret masterSecret;
private ComposeText composeText;
private AnimatingToggle buttonToggle;
private SendButton sendButton;
private ImageButton attachButton;
private ConversationTitleView titleView;
private TextView charactersLeft;
private ConversationFragment fragment;
private Button unblockButton;
private View composePanel;
private AttachmentTypeSelectorAdapter attachmentAdapter;
private AttachmentManager attachmentManager;
@@ -175,10 +179,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
this.masterSecret = masterSecret;
setContentView(R.layout.conversation_activity);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), masterSecret, dynamicLanguage.getCurrentLocale());
fragment = initFragment(R.id.fragment_content, new ConversationFragment(),
masterSecret, dynamicLanguage.getCurrentLocale());
initializeReceivers();
initializeActionBar();
initializeViews();
initializeResources();
initializeDraft();
@@ -210,10 +216,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
dynamicLanguage.onResume(this);
initializeSecurity();
initializeTitleBar();
initializeEnabledCheck();
initializeMmsEnabledCheck();
initializeIme();
titleView.setTitle(recipients);
setBlockedUserState(recipients);
calculateCharactersRemaining();
MessageNotifier.setVisibleThread(threadId);
@@ -263,7 +271,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
break;
case GROUP_EDIT:
this.recipients = RecipientFactory.getRecipientsForIds(this, data.getLongArrayExtra(GroupCreateActivity.GROUP_RECIPIENT_EXTRA), true);
initializeTitleBar();
titleView.setTitle(recipients);
setBlockedUserState(recipients);
supportInvalidateOptionsMenu();
break;
}
}
@@ -299,6 +309,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inflater.inflate(R.menu.conversation, menu);
if (recipients != null && recipients.isMuted()) inflater.inflate(R.menu.conversation_muted, menu);
else inflater.inflate(R.menu.conversation_unmuted, menu);
if (isSingleConversation() && getRecipients().getPrimaryRecipient().getContactUri() == null) {
inflater.inflate(R.menu.conversation_add_to_contacts, menu);
}
@@ -324,6 +337,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
case R.id.menu_edit_group: handleEditPushGroup(); return true;
case R.id.menu_leave: handleLeavePushGroup(); return true;
case R.id.menu_invite: handleInviteLink(); return true;
case R.id.menu_mute_notifications: handleMuteNotifications(); return true;
case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true;
case android.R.id.home: handleReturnToConversationList(); return true;
}
@@ -349,6 +364,61 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
finish();
}
private void handleMuteNotifications() {
MuteDialog.show(this, new MuteDialog.MuteSelectionListener() {
@Override
public void onMuted(final long until) {
recipients.setMuted(until);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
.setMuted(recipients, until);
return null;
}
}.execute();
}
});
}
private void handleUnmuteNotifications() {
recipients.setMuted(0);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
.setMuted(recipients, 0);
return null;
}
}.execute();
}
private void handleUnblock() {
new AlertDialogWrapper.Builder(this)
.setTitle(R.string.ConversationActivity_unblock_question)
.setMessage(R.string.ConversationActivity_are_you_sure_you_want_to_unblock_this_contact)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.ConversationActivity_unblock, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
recipients.setBlocked(false);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
.setBlocked(recipients, false);
return null;
}
}.execute();
}
}).show();
}
private void handleInviteLink() {
try {
boolean a = SecureRandom.getInstance("SHA1PRNG").nextBoolean();
@@ -551,45 +621,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
///// Initializers
private void initializeTitleBar() {
final String title;
final String subtitle;
final Recipient recipient = getRecipients().getPrimaryRecipient();
if (isSingleConversation()) {
if (TextUtils.isEmpty(recipient.getName())) {
title = recipient.getNumber();
subtitle = null;
} else {
title = recipient.getName();
subtitle = PhoneNumberUtils.formatNumber(recipient.getNumber());
}
} else if (isGroupConversation()) {
if (isPushGroupConversation()) {
final String groupName = recipient.getName();
title = (!TextUtils.isEmpty(groupName)) ? groupName : getString(R.string.ConversationActivity_unnamed_group);
subtitle = null;
} else {
final int size = getRecipients().getRecipientsList().size();
title = getString(R.string.ConversationActivity_group_conversation);
subtitle = (size == 1) ? getString(R.string.ConversationActivity_d_recipients_in_group_singular)
: String.format(getString(R.string.ConversationActivity_d_recipients_in_group), size);
}
} else {
title = getString(R.string.ConversationActivity_compose_message);
subtitle = null;
}
getSupportActionBar().setTitle(title);
getSupportActionBar().setSubtitle(subtitle);
getWindow().getDecorView().setContentDescription(getString(R.string.conversation_activity__window_description, title));
this.supportInvalidateOptionsMenu();
}
private void initializeDraft() {
String draftText = getIntent().getStringExtra(DRAFT_TEXT_EXTRA);
Uri draftImage = getIntent().getParcelableExtra(DRAFT_IMAGE_EXTRA);
@@ -659,6 +690,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
else sendButton.setDefaultTransport(Type.SMS);
calculateCharactersRemaining();
supportInvalidateOptionsMenu();
}
private void initializeMmsEnabledCheck() {
@@ -692,6 +724,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
composeText = (ComposeText) findViewById(R.id.embedded_text_editor);
charactersLeft = (TextView) findViewById(R.id.space_left);
emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle);
titleView = (ConversationTitleView) getSupportActionBar().getCustomView();
unblockButton = (Button) findViewById(R.id.unblock_button);
composePanel = findViewById(R.id.bottom_panel);
attachmentAdapter = new AttachmentTypeSelectorAdapter(this);
attachmentManager = new AttachmentManager(this, this);
@@ -710,6 +745,22 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
});
titleView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ConversationActivity.this, RecipientPreferenceActivity.class);
intent.putExtra(RecipientPreferenceActivity.RECIPIENTS_EXTRA, recipients.getIds());
startActivity(intent);
}
});
unblockButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleUnblock();
}
});
composeText.setOnKeyListener(composeKeyPressedListener);
composeText.addTextChangedListener(composeKeyPressedListener);
composeText.setOnEditorActionListener(sendButtonListener);
@@ -718,6 +769,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
emojiToggle.setOnClickListener(new EmojiToggleListener());
}
private void initializeActionBar() {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setCustomView(R.layout.conversation_title_view);
getSupportActionBar().setDisplayShowCustomEnabled(true);
getSupportActionBar().setDisplayShowTitleEnabled(false);
}
private EmojiDrawer getEmojiDrawer() {
if (emojiDrawer.isPresent()) return emojiDrawer.get();
EmojiDrawer emojiDrawer = (EmojiDrawer)((ViewStub)findViewById(R.id.emoji_drawer_stub)).inflate();
@@ -739,8 +797,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
@Override
public void onModified(Recipient recipient) {
initializeTitleBar();
public void onModified(Recipients recipients) {
titleView.setTitle(recipients);
setBlockedUserState(recipients);
}
private void initializeReceivers() {
@@ -751,7 +810,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (eventThreadId == threadId || eventThreadId == -2) {
initializeSecurity();
initializeTitleBar();
calculateCharactersRemaining();
}
}
@@ -765,7 +823,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
long[] ids = recipients.getIds();
Log.w("ConversationActivity", "Looking up new recipients...");
recipients = RecipientFactory.getRecipientsForIds(context, ids, false);
initializeTitleBar();
titleView.setTitle(recipients);
}
}
};
@@ -917,6 +975,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}.execute(thisThreadId);
}
private void setBlockedUserState(Recipients recipients) {
if (recipients.isBlocked()) {
unblockButton.setVisibility(View.VISIBLE);
composePanel.setVisibility(View.GONE);
} else {
composePanel.setVisibility(View.VISIBLE);
unblockButton.setVisibility(View.GONE);
}
}
private void calculateCharactersRemaining() {
int charactersSpent = composeText.getText().toString().length();
TransportOption transportOption = sendButton.getSelectedTransport();
@@ -994,7 +1062,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (refreshFragment) {
fragment.reload(recipients, threadId);
initializeTitleBar();
initializeSecurity();
}

View File

@@ -11,7 +11,7 @@ import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.text.ClipboardManager;
import android.util.Log;
@@ -335,7 +335,7 @@ public class ConversationFragment extends ListFragment
((ConversationAdapter) getListAdapter()).toggleBatchSelected(messageRecord);
((ConversationAdapter) getListAdapter()).notifyDataSetChanged();
actionMode = ((ActionBarActivity)getActivity()).startSupportActionMode(actionModeCallback);
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
return true;
}

View File

@@ -25,7 +25,7 @@ import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -248,7 +248,7 @@ public class ConversationListFragment extends Fragment
@Override
public void onItemLongClick(ConversationListItem item) {
actionMode = ((ActionBarActivity)getActivity()).startSupportActionMode(ConversationListFragment.this);
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(ConversationListFragment.this);
getListAdapter().initializeBatchMode(true);
getListAdapter().toggleThreadInBatchSet(item.getThreadId());

View File

@@ -27,7 +27,6 @@ import android.widget.TextView;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.FromTextView;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DateUtils;
@@ -44,7 +43,7 @@ import static org.thoughtcrime.securesms.util.SpanUtil.color;
*/
public class ConversationListItem extends RelativeLayout
implements Recipient.RecipientModifiedListener
implements Recipients.RecipientsModifiedListener
{
private final static String TAG = ConversationListItem.class.getSimpleName();
@@ -76,12 +75,11 @@ public class ConversationListItem extends RelativeLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
this.subjectView = (TextView) findViewById(R.id.subject);
this.fromView = (FromTextView) findViewById(R.id.from);
this.dateView = (TextView) findViewById(R.id.date);
this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image);
initializeContactWidgetVisibility();
}
public void set(ThreadRecord thread, Locale locale, Set<Long> selectedThreads, boolean batchMode) {
@@ -112,10 +110,6 @@ public class ConversationListItem extends RelativeLayout
this.recipients.removeListener(this);
}
private void initializeContactWidgetVisibility() {
contactPhotoImage.setVisibility(View.VISIBLE);
}
private void setBatchState(boolean batch) {
setSelected(batch && selectedThreads.contains(threadId));
}
@@ -133,7 +127,7 @@ public class ConversationListItem extends RelativeLayout
}
@Override
public void onModified(Recipient recipient) {
public void onModified(final Recipients recipients) {
handler.post(new Runnable() {
@Override
public void run() {

View File

@@ -0,0 +1,94 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
public class ConversationTitleView extends LinearLayout {
private static final String TAG = ConversationTitleView.class.getSimpleName();
private TextView title;
private TextView subtitle;
public ConversationTitleView(Context context) {
this(context, null);
}
public ConversationTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
this.title = (TextView) findViewById(R.id.title);
this.subtitle = (TextView) findViewById(R.id.subtitle);
}
public void setTitle(@Nullable Recipients recipients) {
if (recipients == null) setComposeTitle();
else if (recipients.isSingleRecipient()) setRecipientTitle(recipients.getPrimaryRecipient());
else setRecipientsTitle(recipients);
if (recipients != null && recipients.isBlocked()) {
title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_block_white_18dp, 0, 0, 0);
} else if (recipients != null && recipients.isMuted()) {
title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_volume_off_white_18dp, 0, 0, 0);
} else {
title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
}
private void setComposeTitle() {
this.title.setText(R.string.ConversationActivity_compose_message);
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
}
private void setRecipientTitle(Recipient recipient) {
if (!recipient.isGroupRecipient()) {
if (TextUtils.isEmpty(recipient.getName())) {
this.title.setText(recipient.getNumber());
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
} else {
this.title.setText(recipient.getName());
this.subtitle.setText(recipient.getNumber());
this.subtitle.setVisibility(View.VISIBLE);
}
} else {
String groupName = (!TextUtils.isEmpty(recipient.getName())) ?
recipient.getName() :
getContext().getString(R.string.ConversationActivity_unnamed_group);
this.title.setText(groupName);
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
}
}
private void setRecipientsTitle(Recipients recipients) {
int size = recipients.getRecipientsList().size();
title.setText(getContext().getString(R.string.ConversationActivity_group_conversation));
subtitle.setText((size == 1) ? getContext().getString(R.string.ConversationActivity_d_recipients_in_group_singular) :
String.format(getContext().getString(R.string.ConversationActivity_d_recipients_in_group), size));
subtitle.setVisibility(View.VISIBLE);
}
}

View File

@@ -465,7 +465,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
}
private long handleCreateMmsGroup(Set<Recipient> members) {
Recipients recipients = new Recipients(new LinkedList<Recipient>(members));
Recipients recipients = RecipientFactory.getRecipientsFor(this, new LinkedList<>(members), false);
return DatabaseFactory.getThreadDatabase(this)
.getThreadIdFor(recipients,
ThreadDatabase.DistributionTypes.CONVERSATION);
@@ -532,7 +532,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
ArrayList<Recipient> selectedContactsList = setToArrayList(selectedContacts);
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, new Recipients(selectedContactsList).getIds());
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, RecipientFactory.getRecipientsFor(GroupCreateActivity.this, selectedContactsList, true).getIds());
startActivity(intent);
finish();
} else {

View File

@@ -12,6 +12,7 @@ import com.afollestad.materialdialogs.AlertDialogWrapper;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -45,7 +46,7 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, Recipients> {
.getGroupMembers(GroupUtil.getDecodedId(groupId), true);
} catch (IOException e) {
Log.w(TAG, e);
return new Recipients(new LinkedList<Recipient>());
return RecipientFactory.getRecipientsFor(context, new LinkedList<Recipient>(), true);
}
}

View File

@@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.loaders.MessageDetailsLoader;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DirectoryHelper;
@@ -276,7 +277,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
.getGroupMembers(GroupUtil.getDecodedId(groupId), false);
} catch (IOException e) {
Log.w(TAG, e);
recipients = new Recipients(new LinkedList<Recipient>());
recipients = RecipientFactory.getRecipientsFor(MessageDetailsActivity.this, new LinkedList<Recipient>(), false);
}
}

View File

@@ -0,0 +1,43 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import com.afollestad.materialdialogs.AlertDialogWrapper;
import java.util.concurrent.TimeUnit;
public class MuteDialog extends AlertDialogWrapper {
private MuteDialog() {}
public static void show(final Context context, final @NonNull MuteSelectionListener listener) {
AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(context);
builder.setTitle(R.string.MuteDialog_mute_notifications);
builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, final int which) {
final long muteUntil;
switch (which) {
case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2); break;
case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break;
case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break;
default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
}
listener.onMuted(muteUntil);
}
});
builder.show();
}
public interface MuteSelectionListener {
public void onMuted(long until);
}
}

View File

@@ -80,14 +80,35 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
else finish();
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment,
@NonNull MasterSecret masterSecret)
{
return initFragment(target, fragment, masterSecret, null);
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment,
@NonNull MasterSecret masterSecret,
@Nullable Locale locale) {
@Nullable Locale locale)
{
return initFragment(target, fragment, masterSecret, locale, null);
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment,
@NonNull MasterSecret masterSecret,
@Nullable Locale locale,
@Nullable Bundle extras)
{
Bundle args = new Bundle();
args.putParcelable("master_secret", masterSecret);
args.putSerializable(LOCALE_EXTRA, locale);
if (extras != null) {
args.putAll(extras);
}
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(target, fragment)
@@ -95,12 +116,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
return fragment;
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment,
@NonNull MasterSecret masterSecret) {
return initFragment(target, fragment, masterSecret, null);
}
private void routeApplicationState(MasterSecret masterSecret) {
Intent intent = getIntentForState(masterSecret, getApplicationState(masterSecret));
if (intent != null) {

View File

@@ -0,0 +1,348 @@
package org.thoughtcrime.securesms;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.preference.CheckBoxPreference;
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.widget.Toolbar;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.afollestad.materialdialogs.AlertDialogWrapper;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActivity implements Recipients.RecipientsModifiedListener
{
private static final String TAG = RecipientPreferenceActivity.class.getSimpleName();
public static final String RECIPIENTS_EXTRA = "recipient_ids";
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 final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private AvatarImageView avatar;
private TextView title;
private TextView blockedIndicator;
@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);
long[] recipientIds = getIntent().getLongArrayExtra(RECIPIENTS_EXTRA);
Recipients recipients = RecipientFactory.getRecipientsForIds(this, recipientIds, true);
initializeToolbar();
setHeader(recipients);
recipients.addListener(this);
Bundle bundle = new Bundle();
bundle.putLongArray(RECIPIENTS_EXTRA, recipientIds);
initFragment(R.id.preference_fragment, new RecipientPreferenceFragment(), masterSecret, null, bundle);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@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: finish(); return true;
}
return false;
}
private void initializeToolbar() {
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
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 setHeader(Recipients recipients) {
this.avatar.setAvatar(recipients.getPrimaryRecipient(), true);
this.title.setText(recipients.toShortString());
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;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.recipient_preferences);
this.recipients = RecipientFactory.getRecipientsForIds(getActivity(),
getArguments().getLongArray(RECIPIENTS_EXTRA),
true);
this.recipients.addListener(this);
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());
}
@Override
public void onResume() {
super.onResume();
setSummaries(recipients);
}
private void setSummaries(Recipients recipients) {
CheckBoxPreference mutePreference = (CheckBoxPreference) this.findPreference(PREFERENCE_MUTED);
Preference ringtonePreference = this.findPreference(PREFERENCE_TONE);
Preference vibratePreference = this.findPreference(PREFERENCE_VIBRATE);
Preference blockPreference = this.findPreference(PREFERENCE_BLOCK);
mutePreference.setChecked(recipients.isMuted());
if (recipients.getRingtone() != null) {
Ringtone tone = RingtoneManager.getRingtone(getActivity(), recipients.getRingtone());
if (tone != null) {
ringtonePreference.setSummary(tone.getTitle(getActivity()));
}
} else {
ringtonePreference.setSummary(R.string.preferences__default);
}
if (recipients.getVibrate() == VibrateState.DEFAULT) {
vibratePreference.setSummary(R.string.preferences__default);
} else if (recipients.getVibrate() == VibrateState.ENABLED) {
vibratePreference.setSummary("Enabled");
} else {
vibratePreference.setSummary("Disabled");
}
if (!recipients.isSingleRecipient() || recipients.isGroupRecipient()) {
blockPreference.setEnabled(false);
} else {
blockPreference.setEnabled(true);
if (recipients.isBlocked()) blockPreference.setTitle("Unblock");
else blockPreference.setTitle("Block");
}
}
@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 (TextUtils.isEmpty(value) || 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 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 BlockClickedListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (recipients.isBlocked()) handleUnblock();
else handleBlock();
return true;
}
private void handleBlock() {
new AlertDialogWrapper.Builder(getActivity())
.setTitle(R.string.RecipientPreferenceActivity_block_this_contact_question)
.setMessage(R.string.RecipientPreferenceActivity_you_will_no_longer_see_messages_from_this_user)
.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 AlertDialogWrapper.Builder(getActivity())
.setTitle(R.string.RecipientPreferenceActivity_unblock_this_contact_question)
.setMessage(R.string.RecipientPreferenceActivity_are_you_sure_you_want_to_unblock_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) {
DatabaseFactory.getRecipientPreferenceDatabase(getActivity())
.setBlocked(recipients, blocked);
return null;
}
}.execute();
}
}
}
}

View File

@@ -25,7 +25,6 @@ import android.widget.RelativeLayout;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.FromTextView;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
@@ -34,7 +33,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
* @author Jake McGinty
*/
public class ShareListItem extends RelativeLayout
implements Recipient.RecipientModifiedListener
implements Recipients.RecipientsModifiedListener
{
private final static String TAG = ShareListItem.class.getSimpleName();
@@ -102,7 +101,7 @@ public class ShareListItem extends RelativeLayout
}
@Override
public void onModified(Recipient recipient) {
public void onModified(final Recipients recipients) {
handler.post(new Runnable() {
@Override
public void run() {

View File

@@ -3,19 +3,24 @@ package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.util.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
public class FromTextView extends EmojiTextView {
private static final String TAG = FromTextView.class.getSimpleName();
public FromTextView(Context context) {
super(context);
}
@@ -25,7 +30,7 @@ public class FromTextView extends EmojiTextView {
}
public void setText(Recipient recipient) {
setText(new Recipients(recipient));
setText(RecipientFactory.getRecipientsFor(getContext(), recipient, true));
}
public void setText(Recipients recipients) {
@@ -63,6 +68,10 @@ public class FromTextView extends EmojiTextView {
colors.recycle();
setText(builder);
if (recipients.isBlocked()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_block_grey600_18dp, 0, 0, 0);
else if (recipients.isMuted()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_volume_off_grey600_18dp, 0, 0, 0);
else setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}

View File

@@ -127,7 +127,7 @@ public class PushRecipientsPanel extends RelativeLayout {
try {
recipients = getRecipients();
} catch (RecipientFormattingException e) {
recipients = new Recipients( new LinkedList<Recipient>() );
recipients = RecipientFactory.getRecipientsFor(getContext(), new LinkedList<Recipient>(), true);
}
recipientsText.setAdapter(new RecipientsAdapter(this.getContext()));

View File

@@ -130,7 +130,7 @@ public class SingleRecipientPanel extends RelativeLayout {
try {
recipients = getRecipients();
} catch (RecipientFormattingException e) {
recipients = new Recipients( new LinkedList<Recipient>() );
recipients = RecipientFactory.getRecipientsFor(getContext(), new LinkedList<Recipient>(), true);
}
recipientsText.setAdapter(new RecipientsAdapter(this.getContext()));

View File

@@ -96,11 +96,6 @@ public class ContactPhotoFactory {
localUserContactPhotoCache.clear();
}
public static void clearCache(Recipient recipient) {
if (localUserContactPhotoCache.containsKey(recipient.getContactUri()))
localUserContactPhotoCache.remove(recipient.getContactUri());
}
public static Drawable getContactPhoto(Context context, Uri uri, String name) {
final InputStream inputStream = getContactPhotoStream(context, uri);
final int targetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);

View File

@@ -61,7 +61,8 @@ public class DatabaseFactory {
private static final int INTRODUCED_THUMBNAILS_VERSION = 15;
private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16;
private static final int INTRODUCED_UNIQUE_PART_IDS_VERSION = 17;
private static final int DATABASE_VERSION = 17;
private static final int INTRODUCED_RECIPIENT_PREFS_DB = 18;
private static final int DATABASE_VERSION = 18;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@@ -82,6 +83,7 @@ public class DatabaseFactory {
private final DraftDatabase draftDatabase;
private final PushDatabase pushDatabase;
private final GroupDatabase groupDatabase;
private final RecipientPreferenceDatabase recipientPreferenceDatabase;
public static DatabaseFactory getInstance(Context context) {
synchronized (lock) {
@@ -140,20 +142,25 @@ public class DatabaseFactory {
return getInstance(context).groupDatabase;
}
public static RecipientPreferenceDatabase getRecipientPreferenceDatabase(Context context) {
return getInstance(context).recipientPreferenceDatabase;
}
private DatabaseFactory(Context context) {
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
this.sms = new SmsDatabase(context, databaseHelper);
this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper);
this.mms = new MmsDatabase(context, databaseHelper);
this.part = new PartDatabase(context, databaseHelper);
this.thread = new ThreadDatabase(context, databaseHelper);
this.address = CanonicalAddressDatabase.getInstance(context);
this.mmsAddress = new MmsAddressDatabase(context, databaseHelper);
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
this.draftDatabase = new DraftDatabase(context, databaseHelper);
this.pushDatabase = new PushDatabase(context, databaseHelper);
this.groupDatabase = new GroupDatabase(context, databaseHelper);
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
this.sms = new SmsDatabase(context, databaseHelper);
this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper);
this.mms = new MmsDatabase(context, databaseHelper);
this.part = new PartDatabase(context, databaseHelper);
this.thread = new ThreadDatabase(context, databaseHelper);
this.address = CanonicalAddressDatabase.getInstance(context);
this.mmsAddress = new MmsAddressDatabase(context, databaseHelper);
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
this.draftDatabase = new DraftDatabase(context, databaseHelper);
this.pushDatabase = new PushDatabase(context, databaseHelper);
this.groupDatabase = new GroupDatabase(context, databaseHelper);
this.recipientPreferenceDatabase = new RecipientPreferenceDatabase(context, databaseHelper);
}
public void reset(Context context) {
@@ -171,6 +178,7 @@ public class DatabaseFactory {
this.draftDatabase.reset(databaseHelper);
this.pushDatabase.reset(databaseHelper);
this.groupDatabase.reset(databaseHelper);
this.recipientPreferenceDatabase.reset(databaseHelper);
old.close();
this.address.reset(context);
@@ -480,6 +488,7 @@ public class DatabaseFactory {
db.execSQL(DraftDatabase.CREATE_TABLE);
db.execSQL(PushDatabase.CREATE_TABLE);
db.execSQL(GroupDatabase.CREATE_TABLE);
db.execSQL(RecipientPreferenceDatabase.CREATE_TABLE);
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
@@ -722,6 +731,12 @@ public class DatabaseFactory {
db.execSQL("ALTER TABLE part ADD COLUMN unique_id INTEGER NOT NULL DEFAULT 0");
}
if (oldVersion < INTRODUCED_RECIPIENT_PREFS_DB) {
db.execSQL("CREATE TABLE recipient_preferences " +
"(_id INTEGER PRIMARY KEY, recipient_ids TEXT UNIQUE, block INTEGER DEFAULT 0, " +
"notification TEXT DEFAULT NULL, vibrate INTEGER DEFAULT 0, mute_until INTEGER DEFAULT 0)");
}
db.setTransactionSuccessful();
db.endTransaction();
}

View File

@@ -90,7 +90,7 @@ public class GroupDatabase extends Database {
public Recipients getGroupMembers(byte[] groupId, boolean includeSelf) {
String localNumber = TextSecurePreferences.getLocalNumber(context);
List<String> members = getCurrentMembers(groupId);
List<Recipient> recipients = new LinkedList<Recipient>();
List<Recipient> recipients = new LinkedList<>();
for (String member : members) {
if (!includeSelf && member.equals(localNumber))
@@ -100,7 +100,7 @@ public class GroupDatabase extends Database {
.getRecipientsList());
}
return new Recipients(recipients);
return RecipientFactory.getRecipientsFor(context, recipients, false);
}
public void create(byte[] groupId, String title, List<String> members,

View File

@@ -145,7 +145,7 @@ public class MmsAddressDatabase extends Database {
}
}
return new Recipients(results);
return RecipientFactory.getRecipientsFor(context, results, false);
}

View File

@@ -345,7 +345,7 @@ public class MmsDatabase extends MessagingDatabase {
? Util.toIsoString(notification.getFrom().getTextString())
: "";
Recipients recipients = RecipientFactory.getRecipientsFromString(context, fromString, false);
if (recipients.isEmpty()) recipients = new Recipients(Recipient.getUnknownRecipient(context));
if (recipients.isEmpty()) recipients = RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
}
@@ -1054,13 +1054,13 @@ public class MmsDatabase extends MessagingDatabase {
private Recipients getRecipientsFor(String address) {
if (TextUtils.isEmpty(address) || address.equals("insert-address-token")) {
return new Recipients(Recipient.getUnknownRecipient(context));
return RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
}
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address, false);
if (recipients == null || recipients.isEmpty()) {
return new Recipients(Recipient.getUnknownRecipient(context));
return RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
}
return recipients;

View File

@@ -0,0 +1,172 @@
package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.Arrays;
public class RecipientPreferenceDatabase extends Database {
private static final String TAG = RecipientPreferenceDatabase.class.getSimpleName();
private static final String TABLE_NAME = "recipient_preferences";
private static final String ID = "_id";
private static final String RECIPIENT_IDS = "recipient_ids";
private static final String BLOCK = "block";
private static final String NOTIFICATION = "notification";
private static final String VIBRATE = "vibrate";
private static final String MUTE_UNTIL = "mute_until";
public enum VibrateState {
DEFAULT(0), ENABLED(1), DISABLED(2);
private final int id;
VibrateState(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static VibrateState fromId(int id) {
return values()[id];
}
}
public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME +
" (" + ID + " INTEGER PRIMARY KEY, " +
RECIPIENT_IDS + " TEXT UNIQUE, " +
BLOCK + " INTEGER DEFAULT 0," +
NOTIFICATION + " TEXT DEFAULT NULL, " +
VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " +
MUTE_UNTIL + " INTEGER DEFAULT 0);";
public RecipientPreferenceDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public Cursor getBlocked() {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
return database.query(TABLE_NAME, new String[] {ID, RECIPIENT_IDS}, BLOCK + " = 1",
null, null, null, null, null);
}
public Optional<RecipientsPreferences> getRecipientsPreferences(@NonNull long[] recipients) {
Arrays.sort(recipients);
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, RECIPIENT_IDS + " = ?",
new String[] {Util.join(recipients, " ")},
null, null, null);
if (cursor != null && cursor.moveToNext()) {
boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1;
String notification = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION));
int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE));
long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
Uri notificationUri = notification == null ? null : Uri.parse(notification);
Log.w(TAG, "Muted until: " + muteUntil);
return Optional.of(new RecipientsPreferences(blocked, muteUntil,
VibrateState.fromId(vibrateState),
notificationUri));
}
return Optional.absent();
} finally {
if (cursor != null) cursor.close();
}
}
public void setBlocked(Recipients recipients, boolean blocked) {
ContentValues values = new ContentValues();
values.put(BLOCK, blocked ? 1 : 0);
updateOrInsert(recipients, values);
}
public void setRingtone(Recipients recipients, @Nullable Uri notification) {
ContentValues values = new ContentValues();
values.put(NOTIFICATION, notification == null ? null : notification.toString());
updateOrInsert(recipients, values);
}
public void setVibrate(Recipients recipients, @NonNull VibrateState enabled) {
ContentValues values = new ContentValues();
values.put(VIBRATE, enabled.getId());
updateOrInsert(recipients, values);
}
public void setMuted(Recipients recipients, long until) {
Log.w(TAG, "Setting muted until: " + until);
ContentValues values = new ContentValues();
values.put(MUTE_UNTIL, until);
updateOrInsert(recipients, values);
}
private void updateOrInsert(Recipients recipients, ContentValues contentValues) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.beginTransaction();
int updated = database.update(TABLE_NAME, contentValues, RECIPIENT_IDS + " = ?",
new String[] {String.valueOf(recipients.getSortedIdsString())});
if (updated < 1) {
contentValues.put(RECIPIENT_IDS, recipients.getSortedIdsString());
database.insert(TABLE_NAME, null, contentValues);
}
database.setTransactionSuccessful();
database.endTransaction();
}
public static class RecipientsPreferences {
private final boolean blocked;
private final long muteUntil;
private final VibrateState vibrateState;
private final Uri notification;
public RecipientsPreferences(boolean blocked, long muteUntil, VibrateState vibrateState, Uri notification) {
this.blocked = blocked;
this.muteUntil = muteUntil;
this.vibrateState = vibrateState;
this.notification = notification;
}
public boolean isBlocked() {
return blocked;
}
public long getMuteUntil() {
return muteUntil;
}
public @NonNull VibrateState getVibrateState() {
return vibrateState;
}
public @Nullable Uri getRingtone() {
return notification;
}
}
}

View File

@@ -368,7 +368,7 @@ public class SmsDatabase extends MessagingDatabase {
recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), true);
} else {
Log.w(TAG, "Sender is null, returning unknown recipient");
recipients = new Recipients(Recipient.getUnknownRecipient(context));
recipients = RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
}
Recipients groupRecipients;
@@ -615,13 +615,13 @@ public class SmsDatabase extends MessagingDatabase {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address, false);
if (recipients == null || recipients.isEmpty()) {
return new Recipients(Recipient.getUnknownRecipient(context));
return RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
}
return recipients;
} else {
Log.w(TAG, "getRecipientsFor() address is null");
return new Recipients(Recipient.getUnknownRecipient(context));
return RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
}
}

View File

@@ -0,0 +1,21 @@
package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import android.database.Cursor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
public class BlockedContactsLoader extends AbstractCursorLoader {
public BlockedContactsLoader(Context context) {
super(context);
}
@Override
public Cursor getCursor() {
return DatabaseFactory.getRecipientPreferenceDatabase(getContext())
.getBlocked();
}
}

View File

@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import org.thoughtcrime.securesms.BuildConfig;
@@ -11,19 +10,15 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.push.TextSecurePushTrustStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream;
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
import org.whispersystems.textsecure.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
import java.io.File;
@@ -72,10 +67,6 @@ public class AvatarDownloadJob extends MasterSecretJob {
Bitmap avatar = BitmapUtil.createScaledBitmap(measureInputStream, scaleInputStream, 500, 500);
database.updateAvatar(groupId, avatar);
Recipient groupRecipient = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(groupId), true)
.getPrimaryRecipient();
groupRecipient.setContactPhoto(new BitmapDrawable(avatar));
}
} catch (InvalidMessageException | BitmapDecodingException | NonSuccessfulResponseCodeException e) {
Log.w(TAG, e);

View File

@@ -7,6 +7,9 @@ import android.util.Pair;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobParameters;
import ws.com.google.android.mms.pdu.GenericPdu;
@@ -49,7 +52,7 @@ public class MmsReceiveJob extends ContextJob {
Log.w(TAG, e);
}
if (pdu != null && pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
if (isNotification(pdu) && !isBlocked(pdu)) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu);
@@ -61,6 +64,8 @@ public class MmsReceiveJob extends ContextJob {
messageAndThreadId.first,
messageAndThreadId.second,
true));
} else if (isNotification(pdu)) {
Log.w(TAG, "*** Received blocked MMS, ignoring...");
}
}
@@ -73,4 +78,17 @@ public class MmsReceiveJob extends ContextJob {
public boolean onShouldRetry(Exception exception) {
return false;
}
private boolean isBlocked(GenericPdu pdu) {
if (pdu.getFrom() != null && pdu.getFrom().getTextString() != null) {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, Util.toIsoString(pdu.getFrom().getTextString()), false);
return recipients.isBlocked();
}
return false;
}
private boolean isNotification(GenericPdu pdu) {
return pdu != null && pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
}
}

View File

@@ -7,6 +7,8 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.NotInDirectoryException;
import org.thoughtcrime.securesms.database.TextSecureDirectory;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
@@ -34,16 +36,21 @@ public abstract class PushReceivedJob extends ContextJob {
}
private void handleMessage(TextSecureEnvelope envelope, boolean sendExplicitReceipt) {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
long messageId = DatabaseFactory.getPushDatabase(context).insert(envelope);
if (!recipients.isBlocked()) {
long messageId = DatabaseFactory.getPushDatabase(context).insert(envelope);
jobManager.add(new PushDecryptJob(context, messageId, envelope.getSource()));
} else {
Log.w(TAG, "*** Received blocked push message, ignoring...");
}
if (sendExplicitReceipt) {
jobManager.add(new DeliveryReceiptJob(context, envelope.getSource(),
envelope.getTimestamp(),
envelope.getRelay()));
}
jobManager.add(new PushDecryptJob(context, messageId, envelope.getSource()));
}
private void handleReceipt(TextSecureEnvelope envelope) {

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.telephony.SmsMessage;
import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.ApplicationContext;
@@ -11,6 +12,8 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
@@ -42,9 +45,11 @@ public class SmsReceiveJob extends ContextJob {
public void onRun() {
Optional<IncomingTextMessage> message = assembleMessageFragments(pdus);
if (message.isPresent()) {
if (message.isPresent() && !isBlocked(message.get())) {
Pair<Long, Long> messageAndThreadId = storeMessage(message.get());
MessageNotifier.updateNotification(context, KeyCachingService.getMasterSecret(context), messageAndThreadId.second);
} else if (message.isPresent()) {
Log.w(TAG, "*** Received blocked SMS, ignoring...");
}
}
@@ -58,6 +63,15 @@ public class SmsReceiveJob extends ContextJob {
return false;
}
private boolean isBlocked(IncomingTextMessage message) {
if (message.getSender() != null) {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), false);
return recipients.isBlocked();
}
return false;
}
private Pair<Long, Long> storeMessage(IncomingTextMessage message) {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);

View File

@@ -31,6 +31,7 @@ import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Action;
import android.support.v4.app.NotificationCompat.BigTextStyle;
@@ -49,7 +50,9 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
@@ -88,7 +91,7 @@ public class MessageNotifier {
public static void notifyMessageDeliveryFailed(Context context, Recipients recipients, long threadId) {
if (visibleThread == threadId) {
sendInThreadNotification(context);
sendInThreadNotification(context, recipients);
} else {
Intent intent = new Intent(context, ConversationActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
@@ -105,7 +108,7 @@ public class MessageNotifier {
builder.setTicker(context.getString(R.string.MessageNotifier_error_delivering_message));
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
builder.setAutoCancel(true);
setNotificationAlarms(context, builder, true);
setNotificationAlarms(context, builder, true, null, VibrateState.DEFAULT);
((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))
.notify((int)threadId, builder.build());
@@ -126,8 +129,9 @@ public class MessageNotifier {
}
if (visibleThread == threadId) {
DatabaseFactory.getThreadDatabase(context).setRead(threadId);
sendInThreadNotification(context);
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
threads.setRead(threadId);
sendInThreadNotification(context, threads.getRecipientsForThreadId(threadId));
} else {
updateNotification(context, masterSecret, true, 0);
}
@@ -225,7 +229,9 @@ public class MessageNotifier {
builder.setStyle(new BigTextStyle().bigText(content));
setNotificationAlarms(context, builder, signal);
setNotificationAlarms(context, builder, signal,
notificationState.getRingtone(),
notificationState.getVibrate());
if (signal) {
builder.setTicker(notifications.get(0).getTickerText());
@@ -283,7 +289,9 @@ public class MessageNotifier {
builder.setStyle(style);
setNotificationAlarms(context, builder, signal);
setNotificationAlarms(context, builder, signal,
notificationState.getRingtone(),
notificationState.getVibrate());
if (signal) {
builder.setTicker(notifications.get(0).getTickerText());
@@ -293,23 +301,27 @@ public class MessageNotifier {
.notify(NOTIFICATION_ID, builder.build());
}
private static void sendInThreadNotification(Context context) {
private static void sendInThreadNotification(Context context, Recipients recipients) {
try {
if (!TextSecurePreferences.isInThreadNotifications(context)) {
return;
}
String ringtone = TextSecurePreferences.getNotificationRingtone(context);
if (ringtone == null) {
Log.w(TAG, "ringtone preference was null.");
return;
}
Uri uri = Uri.parse(ringtone);
Uri uri = recipients.getRingtone();
if (uri == null) {
Log.w(TAG, "couldn't parse ringtone uri " + ringtone);
String ringtone = TextSecurePreferences.getNotificationRingtone(context);
if (ringtone == null) {
Log.w(TAG, "ringtone preference was null.");
return;
} else {
uri = Uri.parse(ringtone);
}
}
if (uri == null) {
Log.w(TAG, "couldn't parse ringtone uri " + TextSecurePreferences.getNotificationRingtone(context));
return;
}
@@ -358,7 +370,9 @@ public class MessageNotifier {
SpannableString body = new SpannableString(context.getString(R.string.MessageNotifier_encrypted_message));
body.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
notificationState.addNotification(new NotificationItem(recipient, recipients, null, threadId, body, null, 0));
if (!recipients.isMuted()) {
notificationState.addNotification(new NotificationItem(recipient, recipients, null, threadId, body, null, 0));
}
}
} finally {
if (reader != null)
@@ -403,7 +417,9 @@ public class MessageNotifier {
body = SpanUtil.italic(message, italicLength);
}
notificationState.addNotification(new NotificationItem(recipient, recipients, threadRecipients, threadId, body, image, timestamp));
if (threadRecipients == null || !threadRecipients.isMuted()) {
notificationState.addNotification(new NotificationItem(recipient, recipients, threadRecipients, threadId, body, image, timestamp));
}
}
reader.close();
@@ -412,18 +428,23 @@ public class MessageNotifier {
private static void setNotificationAlarms(Context context,
NotificationCompat.Builder builder,
boolean signal)
boolean signal,
@Nullable Uri ringtone,
VibrateState vibrate)
{
String ringtone = TextSecurePreferences.getNotificationRingtone(context);
boolean vibrate = TextSecurePreferences.isNotificationVibrateEnabled(context);
String defaultRingtoneName = TextSecurePreferences.getNotificationRingtone(context);
boolean defaultVibrate = TextSecurePreferences.isNotificationVibrateEnabled(context);
String ledColor = TextSecurePreferences.getNotificationLedColor(context);
String ledBlinkPattern = TextSecurePreferences.getNotificationLedPattern(context);
String ledBlinkPatternCustom = TextSecurePreferences.getNotificationLedPatternCustom(context);
String[] blinkPatternArray = parseBlinkPattern(ledBlinkPattern, ledBlinkPatternCustom);
builder.setSound(TextUtils.isEmpty(ringtone) || !signal ? null : Uri.parse(ringtone));
if (signal && ringtone != null) builder.setSound(ringtone);
else if (signal && !TextUtils.isEmpty(defaultRingtoneName)) builder.setSound(Uri.parse(defaultRingtoneName));
else builder.setSound(null);
if (signal && vibrate) {
if (signal && (vibrate == VibrateState.ENABLED || (vibrate == VibrateState.DEFAULT && defaultVibrate))) {
builder.setDefaults(Notification.DEFAULT_VIBRATE);
}

View File

@@ -4,6 +4,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder;
import org.thoughtcrime.securesms.ConversationActivity;
@@ -34,6 +35,10 @@ public class NotificationItem {
this.timestamp = timestamp;
}
public @Nullable Recipients getRecipients() {
return threadRecipients;
}
public Recipient getIndividualRecipient() {
return individualRecipient;
}

View File

@@ -3,9 +3,14 @@ package org.thoughtcrime.securesms.notifications;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.HashSet;
import java.util.LinkedList;
@@ -14,8 +19,8 @@ import java.util.Set;
public class NotificationState {
private final LinkedList<NotificationItem> notifications = new LinkedList<NotificationItem>();
private final Set<Long> threads = new HashSet<Long>();
private final LinkedList<NotificationItem> notifications = new LinkedList<>();
private final Set<Long> threads = new HashSet<>();
private int notificationCount = 0;
@@ -25,6 +30,30 @@ public class NotificationState {
notificationCount++;
}
public @Nullable Uri getRingtone() {
if (!notifications.isEmpty()) {
Recipients recipients = notifications.getFirst().getRecipients();
if (recipients != null) {
return recipients.getRingtone();
}
}
return null;
}
public VibrateState getVibrate() {
if (!notifications.isEmpty()) {
Recipients recipients = notifications.getFirst().getRecipients();
if (recipients != null) {
return recipients.getVibrate();
}
}
return VibrateState.DEFAULT;
}
public boolean hasMultipleThreads() {
return threads.size() > 1;
}

View File

@@ -17,6 +17,7 @@ import com.doomonafireball.betterpickers.hmspicker.HmsPickerBuilder;
import com.doomonafireball.betterpickers.hmspicker.HmsPickerDialogFragment;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.BlockedContactsActivity;
import org.thoughtcrime.securesms.PassphraseChangeActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@@ -27,6 +28,9 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class AppProtectionPreferenceFragment extends PreferenceFragment {
private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
private MasterSecret masterSecret;
private CheckBoxPreference disablePassphrase;
@@ -42,6 +46,8 @@ public class AppProtectionPreferenceFragment extends PreferenceFragment {
.setOnPreferenceClickListener(new ChangePassphraseClickListener());
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF)
.setOnPreferenceClickListener(new PassphraseIntervalClickListener());
this.findPreference(PREFERENCE_CATEGORY_BLOCKED)
.setOnPreferenceClickListener(new BlockedContactsClickListener());
disablePassphrase
.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
}
@@ -73,6 +79,15 @@ public class AppProtectionPreferenceFragment extends PreferenceFragment {
.setSummary(getString(R.string.AppProtectionPreferenceFragment_minutes, timeoutMinutes));
}
private class BlockedContactsClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(getActivity(), BlockedContactsActivity.class);
startActivity(intent);
return true;
}
}
private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {

View File

@@ -0,0 +1,53 @@
package org.thoughtcrime.securesms.preferences;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.recipients.Recipients;
public class BlockedContactListItem extends RelativeLayout implements Recipients.RecipientsModifiedListener {
private AvatarImageView contactPhotoImage;
private TextView nameView;
private Recipients recipients;
public BlockedContactListItem(Context context) {
super(context);
}
public BlockedContactListItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BlockedContactListItem(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
this.contactPhotoImage = (AvatarImageView)findViewById(R.id.contact_photo_image);
this.nameView = (TextView) findViewById(R.id.name);
}
public void set(Recipients recipients) {
this.recipients = recipients;
onModified(recipients);
recipients.addListener(this);
}
@Override
public void onModified(Recipients recipients) {
this.contactPhotoImage.setAvatar(recipients.getPrimaryRecipient(), false);
this.nameView.setText(recipients.toShortString());
}
public Recipients getRecipients() {
return recipients;
}
}

View File

@@ -91,16 +91,6 @@ public class Recipient {
return this.contactUri;
}
public synchronized void setContactPhoto(Drawable bitmap) {
this.contactPhoto = bitmap;
notifyListeners();
}
public synchronized void setName(String name) {
this.name = name;
notifyListeners();
}
public synchronized String getName() {
return this.name;
}

View File

@@ -19,10 +19,11 @@ package org.thoughtcrime.securesms.recipients;
import android.content.Context;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.LinkedList;
import java.util.List;
@@ -34,59 +35,73 @@ public class RecipientFactory {
public static Recipients getRecipientsForIds(Context context, String recipientIds, boolean asynchronous) {
if (TextUtils.isEmpty(recipientIds))
return new Recipients(new LinkedList<Recipient>());
return new Recipients();
List<Recipient> results = new LinkedList<>();
StringTokenizer tokenizer = new StringTokenizer(recipientIds.trim(), " ");
return getRecipientsForIds(context, Util.split(recipientIds, " "), asynchronous);
}
while (tokenizer.hasMoreTokens()) {
String recipientId = tokenizer.nextToken();
Recipient recipient = getRecipientFromProviderId(context, Long.parseLong(recipientId), asynchronous);
public static Recipients getRecipientsFor(Context context, List<Recipient> recipients, boolean asynchronous) {
long[] ids = new long[recipients.size()];
int i = 0;
results.add(recipient);
for (Recipient recipient : recipients) {
ids[i++] = recipient.getRecipientId();
}
return new Recipients(results);
return provider.getRecipients(context, ids, asynchronous);
}
public static Recipients getRecipientsFor(Context context, Recipient recipient, boolean asynchronous) {
long[] ids = new long[1];
ids[0] = recipient.getRecipientId();
return provider.getRecipients(context, ids, asynchronous);
}
public static Recipient getRecipientForId(Context context, long recipientId, boolean asynchronous) {
return getRecipientFromProviderId(context, recipientId, asynchronous);
}
public static Recipients getRecipientsForIds(Context context, long[] recipientIds, boolean asynchronous) {
List<Recipient> results = new LinkedList<>();
if (recipientIds == null) return new Recipients(results);
for (long recipientId : recipientIds) {
results.add(getRecipientFromProviderId(context, recipientId, asynchronous));
}
return new Recipients(results);
}
private static Recipient getRecipientForNumber(Context context, String number, boolean asynchronous) {
long recipientId = CanonicalAddressDatabase.getInstance(context).getCanonicalAddressId(number);
return provider.getRecipient(context, recipientId, asynchronous);
}
public static Recipients getRecipientsFromString(Context context, @NonNull String rawText, boolean asynchronous) {
List<Recipient> results = new LinkedList<>();
StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
while (tokenizer.hasMoreTokens()) {
Recipient recipient = parseRecipient(context, tokenizer.nextToken(), asynchronous);
if( recipient != null )
results.add(recipient);
}
return new Recipients(results);
public static Recipients getRecipientsForIds(Context context, long[] recipientIds, boolean asynchronous) {
return provider.getRecipients(context, recipientIds, asynchronous);
}
private static Recipient getRecipientFromProviderId(Context context, long recipientId, boolean asynchronous) {
try {
return provider.getRecipient(context, recipientId, asynchronous);
} catch (NumberFormatException e) {
Log.w("RecipientFactory", e);
return Recipient.getUnknownRecipient(context);
public static Recipients getRecipientsFromString(Context context, @NonNull String rawText, boolean asynchronous) {
StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
List<String> ids = new LinkedList<>();
while (tokenizer.hasMoreTokens()) {
Optional<Long> id = getRecipientIdFromNumber(context, tokenizer.nextToken());
if (id.isPresent()) {
ids.add(String.valueOf(id.get()));
}
}
return getRecipientsForIds(context, ids, asynchronous);
}
private static Recipients getRecipientsForIds(Context context, List<String> idStrings, boolean asynchronous) {
long[] ids = new long[idStrings.size()];
int i = 0;
for (String id : idStrings) {
ids[i++] = Long.parseLong(id);
}
return provider.getRecipients(context, ids, asynchronous);
}
private static Optional<Long> getRecipientIdFromNumber(Context context, String number) {
number = number.trim();
if (number.isEmpty()) return Optional.absent();
if (hasBracketedNumber(number)) {
number = parseBracketedNumber(number);
}
return Optional.of(CanonicalAddressDatabase.getInstance(context).getCanonicalAddressId(number));
}
private static boolean hasBracketedNumber(String recipient) {
@@ -104,26 +119,9 @@ public class RecipientFactory {
return value;
}
private static Recipient parseRecipient(Context context, String recipient, boolean asynchronous) {
recipient = recipient.trim();
if( recipient.length() == 0 )
return null;
if (hasBracketedNumber(recipient))
return getRecipientForNumber(context, parseBracketedNumber(recipient), asynchronous);
return getRecipientForNumber(context, recipient, asynchronous);
}
public static void clearCache() {
ContactPhotoFactory.clearCache();
provider.clearCache();
}
public static void clearCache(Recipient recipient) {
ContactPhotoFactory.clearCache(recipient);
provider.clearCache(recipient);
}
}

View File

@@ -22,27 +22,34 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.support.annotation.Nullable;
import android.util.Log;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
public class RecipientProvider {
private static final Map<Long,Recipient> recipientCache = Collections.synchronizedMap(new LRUCache<Long,Recipient>(1000));
private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
private static final Map<Long,Recipient> recipientCache = Collections.synchronizedMap(new LRUCache<Long,Recipient>(1000));
private static final Map<RecipientIds,Recipients> recipientsCache = Collections.synchronizedMap(new LRUCache<RecipientIds, Recipients>(1000));
private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
private static final String[] CALLER_ID_PROJECTION = new String[] {
PhoneLookup.DISPLAY_NAME,
@@ -51,12 +58,29 @@ public class RecipientProvider {
PhoneLookup.NUMBER
};
public Recipient getRecipient(Context context, long recipientId, boolean asynchronous) {
Recipient getRecipient(Context context, long recipientId, boolean asynchronous) {
Recipient cachedRecipient = recipientCache.get(recipientId);
if (cachedRecipient != null) return cachedRecipient;
else if (asynchronous) return getAsynchronousRecipient(context, recipientId);
else return getSynchronousRecipient(context, recipientId);
if (cachedRecipient != null) return cachedRecipient;
else if (asynchronous) return getAsynchronousRecipient(context, recipientId);
else return getSynchronousRecipient(context, recipientId);
}
Recipients getRecipients(Context context, long[] recipientIds, boolean asynchronous) {
Recipients cachedRecipients = recipientsCache.get(new RecipientIds(recipientIds));
if (cachedRecipients != null) return cachedRecipients;
List<Recipient> recipientList = new LinkedList<>();
for (long recipientId : recipientIds) {
recipientList.add(getRecipient(context, recipientId, false));
}
if (asynchronous) cachedRecipients = new Recipients(recipientList, getRecipientsPreferencesAsync(context, recipientIds));
else cachedRecipients = new Recipients(recipientList, getRecipientsPreferencesSync(context, recipientIds));
recipientsCache.put(new RecipientIds(recipientIds), cachedRecipients);
return cachedRecipients;
}
private Recipient getSynchronousRecipient(final Context context, final long recipientId) {
@@ -116,13 +140,9 @@ public class RecipientProvider {
return recipient;
}
public void clearCache() {
void clearCache() {
recipientCache.clear();
}
public void clearCache(Recipient recipient) {
if (recipientCache.containsKey(recipient.getRecipientId()))
recipientCache.remove(recipient.getRecipientId());
recipientsCache.clear();
}
private RecipientDetails getRecipientDetails(Context context, String number) {
@@ -164,6 +184,25 @@ public class RecipientProvider {
}
}
private @Nullable RecipientsPreferences getRecipientsPreferencesSync(Context context, long[] recipientIds) {
return DatabaseFactory.getRecipientPreferenceDatabase(context)
.getRecipientsPreferences(recipientIds)
.orNull();
}
private ListenableFutureTask<RecipientsPreferences> getRecipientsPreferencesAsync(final Context context, final long[] recipientIds) {
ListenableFutureTask<RecipientsPreferences> task = new ListenableFutureTask<>(new Callable<RecipientsPreferences>() {
@Override
public RecipientsPreferences call() throws Exception {
return getRecipientsPreferencesSync(context, recipientIds);
}
});
asyncRecipientResolver.execute(task);
return task;
}
public static class RecipientDetails {
public final String name;
public final String number;
@@ -178,5 +217,23 @@ public class RecipientProvider {
}
}
private static class RecipientIds {
private final long[] ids;
private RecipientIds(long[] ids) {
this.ids = ids;
}
public boolean equals(Object other) {
if (other == null || !(other instanceof RecipientIds)) return false;
return Arrays.equals(this.ids, ((RecipientIds) other).ids);
}
public int hashCode() {
return Arrays.hashCode(ids);
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2015 Open Whisper Systems
*
* 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
@@ -16,52 +16,155 @@
*/
package org.thoughtcrime.securesms.recipients;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Patterns;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.Util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
public class Recipients implements Iterable<Recipient> {
public class Recipients implements Iterable<Recipient>, RecipientModifiedListener {
private List<Recipient> recipients;
private static final String TAG = Recipients.class.getSimpleName();
public Recipients(List<Recipient> recipients) {
private final Set<RecipientsModifiedListener> listeners = Collections.newSetFromMap(new WeakHashMap<RecipientsModifiedListener, Boolean>());
private final List<Recipient> recipients;
private Uri ringtone = null;
private long mutedUntil = 0;
private boolean blocked = false;
private VibrateState vibrate = VibrateState.DEFAULT;
Recipients() {
this(new LinkedList<Recipient>(), (RecipientsPreferences)null);
}
Recipients(List<Recipient> recipients, @Nullable RecipientsPreferences preferences) {
this.recipients = recipients;
}
public Recipients(final Recipient recipient) {
this.recipients = new LinkedList<Recipient>() {{
add(recipient);
}};
}
public void append(Recipients recipients) {
this.recipients.addAll(recipients.getRecipientsList());
}
// public Recipients truncateToSingleRecipient() {
// assert(!this.recipients.isEmpty());
// this.recipients = this.recipients.subList(0, 1);
// return this;
// }
public void addListener(RecipientModifiedListener listener) {
for (Recipient recipient : recipients) {
recipient.addListener(listener);
if (preferences != null) {
ringtone = preferences.getRingtone();
mutedUntil = preferences.getMuteUntil();
vibrate = preferences.getVibrateState();
blocked = preferences.isBlocked();
}
}
public void removeListener(RecipientModifiedListener listener) {
for (Recipient recipient : recipients) {
recipient.removeListener(listener);
Recipients(List<Recipient> recipients, ListenableFutureTask<RecipientsPreferences> preferences) {
this.recipients = recipients;
preferences.addListener(new FutureTaskListener<RecipientsPreferences>() {
@Override
public void onSuccess(RecipientsPreferences result) {
if (result != null) {
Set<RecipientsModifiedListener> localListeners;
synchronized (Recipients.this) {
ringtone = result.getRingtone();
mutedUntil = result.getMuteUntil();
vibrate = result.getVibrateState();
blocked = result.isBlocked();
localListeners = new HashSet<>(listeners);
}
for (RecipientsModifiedListener listener : localListeners) {
listener.onModified(Recipients.this);
}
}
}
@Override
public void onFailure(Throwable error) {
Log.w(TAG, error);
}
});
}
public synchronized @Nullable Uri getRingtone() {
return ringtone;
}
public void setRingtone(Uri ringtone) {
synchronized (this) {
this.ringtone = ringtone;
}
notifyListeners();
}
public synchronized boolean isMuted() {
return System.currentTimeMillis() <= mutedUntil;
}
public void setMuted(long mutedUntil) {
synchronized (this) {
this.mutedUntil = mutedUntil;
}
notifyListeners();
}
public synchronized boolean isBlocked() {
return blocked;
}
public void setBlocked(boolean blocked) {
synchronized (this) {
this.blocked = blocked;
}
notifyListeners();
}
public synchronized VibrateState getVibrate() {
return vibrate;
}
public void setVibrate(VibrateState vibrate) {
synchronized (this) {
this.vibrate = vibrate;
}
notifyListeners();
}
public synchronized void addListener(RecipientsModifiedListener listener) {
if (listeners.isEmpty()) {
for (Recipient recipient : recipients) {
recipient.addListener(this);
}
}
synchronized (this) {
listeners.add(listener);
}
}
public synchronized void removeListener(RecipientsModifiedListener listener) {
listeners.remove(listener);
if (listeners.isEmpty()) {
for (Recipient recipient : recipients) {
recipient.removeListener(this);
}
}
}
@@ -78,30 +181,6 @@ public class Recipients implements Iterable<Recipient> {
return isSingleRecipient() && GroupUtil.isEncodedGroup(recipients.get(0).getNumber());
}
// public Recipients getSecureSessionRecipients(Context context) {
// List<Recipient> secureRecipients = new LinkedList<Recipient>();
//
// for (Recipient recipient : recipients) {
// if (KeyUtil.isSessionFor(context, recipient)) {
// secureRecipients.add(recipient);
// }
// }
//
// return new Recipients(secureRecipients);
// }
//
// public Recipients getInsecureSessionRecipients(Context context) {
// List<Recipient> insecureRecipients = new LinkedList<Recipient>();
//
// for (Recipient recipient : recipients) {
// if (!KeyUtil.isSessionFor(context, recipient)) {
// insecureRecipients.add(recipient);
// }
// }
//
// return new Recipients(insecureRecipients);
// }
public boolean isEmpty() {
return this.recipients.isEmpty();
}
@@ -129,6 +208,25 @@ public class Recipients implements Iterable<Recipient> {
return ids;
}
public String getSortedIdsString() {
Set<Long> recipientSet = new HashSet<>();
for (Recipient recipient : this.recipients) {
recipientSet.add(recipient.getRecipientId());
}
long[] recipientArray = new long[recipientSet.size()];
int i = 0;
for (Long recipientId : recipientSet) {
recipientArray[i++] = recipientId;
}
Arrays.sort(recipientArray);
return Util.join(recipientArray, " ");
}
public String[] toNumberStringArray(boolean scrub) {
String[] recipientsArray = new String[recipients.size()];
Iterator<Recipient> iterator = recipients.iterator();
@@ -163,12 +261,31 @@ public class Recipients implements Iterable<Recipient> {
return fromString;
}
public int describeContents() {
return 0;
}
@Override
public Iterator<Recipient> iterator() {
return recipients.iterator();
}
@Override
public void onModified(Recipient recipient) {
notifyListeners();
}
private void notifyListeners() {
Set<RecipientsModifiedListener> localListeners;
synchronized (this) {
localListeners = new HashSet<>(listeners);
}
for (RecipientsModifiedListener listener : localListeners) {
listener.onModified(this);
}
}
public interface RecipientsModifiedListener {
public void onModified(Recipients recipient);
}
}

View File

@@ -9,10 +9,6 @@ public class OutgoingEncryptedMessage extends OutgoingTextMessage {
super(recipients, body);
}
public OutgoingEncryptedMessage(Recipient recipient, String body) {
super(recipient, body);
}
private OutgoingEncryptedMessage(OutgoingEncryptedMessage base, String body) {
super(base, body);
}

View File

@@ -1,11 +1,11 @@
package org.thoughtcrime.securesms.sms;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
public class OutgoingKeyExchangeMessage extends OutgoingTextMessage {
public OutgoingKeyExchangeMessage(Recipient recipient, String message) {
super(recipient, message);
public OutgoingKeyExchangeMessage(Recipients recipients, String message) {
super(recipients, message);
}
private OutgoingKeyExchangeMessage(OutgoingKeyExchangeMessage base, String body) {

View File

@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.sms;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
public class OutgoingTextMessage {
@@ -9,10 +8,6 @@ public class OutgoingTextMessage {
private final Recipients recipients;
private final String message;
public OutgoingTextMessage(Recipient recipient, String message) {
this(new Recipients(recipient), message);
}
public OutgoingTextMessage(Recipients recipients, String message) {
this.recipients = recipients;
this.message = message;
@@ -49,13 +44,13 @@ public class OutgoingTextMessage {
public static OutgoingTextMessage from(SmsMessageRecord record) {
if (record.isSecure()) {
return new OutgoingEncryptedMessage(record.getIndividualRecipient(), record.getBody().getBody());
return new OutgoingEncryptedMessage(record.getRecipients(), record.getBody().getBody());
} else if (record.isKeyExchange()) {
return new OutgoingKeyExchangeMessage(record.getIndividualRecipient(), record.getBody().getBody());
return new OutgoingKeyExchangeMessage(record.getRecipients(), record.getBody().getBody());
} else if (record.isEndSession()) {
return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getIndividualRecipient(), record.getBody().getBody()));
return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody()));
} else {
return new OutgoingTextMessage(record.getIndividualRecipient(), record.getBody().getBody());
return new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody());
}
}

View File

@@ -0,0 +1,16 @@
package org.thoughtcrime.securesms.util;
import android.app.Activity;
import org.thoughtcrime.securesms.R;
public class DynamicNoActionBarTheme extends DynamicTheme {
@Override
protected int getSelectedTheme(Activity activity) {
String theme = TextSecurePreferences.getTheme(activity);
if (theme.equals("dark")) return R.style.TextSecure_DarkNoActionBar;
return R.style.TextSecure_LightNoActionBar;
}
}

View File

@@ -77,6 +77,17 @@ public class Util {
return result.toString();
}
public static String join(long[] list, String delimeter) {
StringBuilder sb = new StringBuilder();
for (int j=0;j<list.length;j++) {
if (j != 0) sb.append(delimeter);
sb.append(list[j]);
}
return sb.toString();
}
public static ExecutorService newSingleThreadedLifoExecutor() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingLifoQueue<Runnable>());