Display unknown sender footer for unknown senders

// FREEBIE
This commit is contained in:
Moxie Marlinspike
2017-08-18 17:28:56 -07:00
parent 5942e93a33
commit 1b2f52209d
25 changed files with 279 additions and 24 deletions

View File

@@ -61,6 +61,7 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.profiles.UnknownSenderView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.sms.MessageSender;
@@ -100,6 +101,7 @@ public class ConversationFragment extends Fragment
private RecyclerView list;
private RecyclerView.ItemDecoration lastSeenDecoration;
private View loadMoreView;
private UnknownSenderView unknownSenderView;
private View composeDivider;
private View scrollToBottomButton;
private TextView scrollDateHeader;
@@ -132,14 +134,12 @@ public class ConversationFragment extends Fragment
list.setItemAnimator(null);
loadMoreView = inflater.inflate(R.layout.load_more_header, container, false);
loadMoreView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Bundle args = new Bundle();
args.putLong("limit", 0);
getLoaderManager().restartLoader(0, args, ConversationFragment.this);
}
loadMoreView.setOnClickListener(v -> {
Bundle args = new Bundle();
args.putLong("limit", 0);
getLoaderManager().restartLoader(0, args, ConversationFragment.this);
});
return view;
}
@@ -184,10 +184,11 @@ public class ConversationFragment extends Fragment
}
private void initializeResources() {
this.recipient = RecipientFactory.getRecipientFor(getActivity(), (Address)getActivity().getIntent().getParcelableExtra(ConversationActivity.ADDRESS_EXTRA), true);
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1);
this.firstLoad = true;
this.recipient = RecipientFactory.getRecipientFor(getActivity(), (Address) getActivity().getIntent().getParcelableExtra(ConversationActivity.ADDRESS_EXTRA), true);
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1);
this.firstLoad = true;
this.unknownSenderView = new UnknownSenderView(getActivity(), recipient, threadId);
OnScrollListener scrollListener = new ConversationScrollListener(getActivity());
list.addOnScrollListener(scrollListener);
@@ -430,6 +431,12 @@ public class ConversationFragment extends Fragment
setLastSeen(loader.getLastSeen());
}
if (!loader.hasSent() && recipient.getName() == null) {
getListAdapter().setHeaderView(unknownSenderView);
} else {
getListAdapter().setHeaderView(null);
}
getListAdapter().changeCursor(cursor);
int lastSeenPosition = getListAdapter().findLastSeenPosition(lastSeen);
@@ -456,6 +463,8 @@ public class ConversationFragment extends Fragment
MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(getContext()).readerFor(message, threadId).getCurrent();
if (getListAdapter() != null) {
getListAdapter().setHeaderView(null);
setLastSeen(0);
getListAdapter().addFastRecord(messageRecord);
}
@@ -466,6 +475,8 @@ public class ConversationFragment extends Fragment
MessageRecord messageRecord = DatabaseFactory.getSmsDatabase(getContext()).readerFor(message, threadId).getCurrent();
if (getListAdapter() != null) {
getListAdapter().setHeaderView(null);
setLastSeen(0);
getListAdapter().addFastRecord(messageRecord);
}

View File

@@ -105,7 +105,8 @@ public class DatabaseFactory {
private static final int INTERNAL_SYSTEM_DISPLAY_NAME = 40;
private static final int PROFILES = 41;
private static final int PROFILE_SHARING_APPROVAL = 42;
private static final int DATABASE_VERSION = 42;
private static final int UNSEEN_NUMBER_OFFER = 43;
private static final int DATABASE_VERSION = 43;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@@ -1312,6 +1313,10 @@ public class DatabaseFactory {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_sharing_approval INTEGER DEFAULT 0");
}
if (oldVersion < UNSEEN_NUMBER_OFFER) {
db.execSQL("ALTER TABLE thread ADD COLUMN has_sent INTEGER DEFAULT 0");
}
db.setTransactionSuccessful();
db.endTransaction();
}

View File

@@ -808,6 +808,7 @@ public class MmsDatabase extends MessagingDatabase {
long messageId = insertMediaMessage(masterSecret, message.getBody(), message.getAttachments(), contentValues, insertListener);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
jobManager.add(new TrimThreadJob(context, threadId));
return messageId;

View File

@@ -615,6 +615,8 @@ public class SmsDatabase extends MessagingDatabase {
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
}
DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
notifyConversationListeners(threadId);
if (!message.isIdentityVerified() && !message.isIdentityDefault()) {

View File

@@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.util.DelimiterUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.LinkedList;
@@ -73,6 +74,7 @@ public class ThreadDatabase extends Database {
public static final String RECEIPT_COUNT = "delivery_receipt_count";
public static final String EXPIRES_IN = "expires_in";
public static final String LAST_SEEN = "last_seen";
private static final String HAS_SENT = "has_sent";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" +
ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " +
@@ -82,7 +84,7 @@ public class ThreadDatabase extends Database {
SNIPPET_TYPE + " INTEGER DEFAULT 0, " + SNIPPET_URI + " TEXT DEFAULT NULL, " +
ARCHIVED + " INTEGER DEFAULT 0, " + STATUS + " INTEGER DEFAULT 0, " +
RECEIPT_COUNT + " INTEGER DEFAULT 0, " + EXPIRES_IN + " INTEGER DEFAULT 0, " +
LAST_SEEN + " INTEGER DEFAULT 0);";
LAST_SEEN + " INTEGER DEFAULT 0, " + HAS_SENT + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + ADDRESS + ");",
@@ -417,20 +419,19 @@ public class ThreadDatabase extends Database {
notifyConversationListListeners();
}
public long getLastSeen(long threadId) {
public Pair<Long, Boolean> getLastSeenAndHasSent(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN, HAS_SENT}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(0);
return new Pair<>(cursor.getLong(0), cursor.getLong(1) == 1);
}
return -1;
return new Pair<>(-1L, false);
} finally {
if (cursor != null) cursor.close();
}
}
public void deleteConversation(long threadId) {
@@ -520,6 +521,16 @@ public class ThreadDatabase extends Database {
return null;
}
public void setHasSent(long threadId, boolean hasSent) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(HAS_SENT, hasSent ? 1 : 0);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, ID_WHERE,
new String[] {String.valueOf(threadId)});
notifyConversationListeners(threadId);
}
public void updateReadState(long threadId) {
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);

View File

@@ -5,17 +5,20 @@ import android.database.Cursor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
import org.whispersystems.libsignal.util.Pair;
public class ConversationLoader extends AbstractCursorLoader {
private final long threadId;
private long limit;
private long lastSeen;
private final long threadId;
private long limit;
private long lastSeen;
private boolean hasSent;
public ConversationLoader(Context context, long threadId, long limit, long lastSeen) {
super(context);
this.threadId = threadId;
this.limit = limit;
this.lastSeen = lastSeen;
this.hasSent = true;
}
public boolean hasLimit() {
@@ -26,10 +29,18 @@ public class ConversationLoader extends AbstractCursorLoader {
return lastSeen;
}
public boolean hasSent() {
return hasSent;
}
@Override
public Cursor getCursor() {
Pair<Long, Boolean> lastSeenAndHasSent = DatabaseFactory.getThreadDatabase(context).getLastSeenAndHasSent(threadId);
this.hasSent = lastSeenAndHasSent.second();
if (lastSeen == -1) {
this.lastSeen = DatabaseFactory.getThreadDatabase(context).getLastSeen(threadId);
this.lastSeen = lastSeenAndHasSent.first();
}
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId, limit);

View File

@@ -0,0 +1,106 @@
package org.thoughtcrime.securesms.profiles;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.view.View;
import android.widget.FrameLayout;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ViewUtil;
public class UnknownSenderView extends FrameLayout {
private final @NonNull Recipient recipient;
private final long threadId;
public UnknownSenderView(@NonNull Context context, @NonNull Recipient recipient, long threadId) {
super(context);
this.recipient = recipient;
this.threadId = threadId;
inflate(context, R.layout.unknown_sender_view, this);
View block = ViewUtil.findById(this, R.id.block);
View add = ViewUtil.findById(this, R.id.add_to_contacts);
View profileAccess = ViewUtil.findById(this, R.id.share_profile);
block.setOnClickListener(v -> handleBlock());
add.setOnClickListener(v -> handleAdd());
profileAccess.setOnClickListener(v -> handleProfileAccess());
}
private void handleBlock() {
final Context context = getContext();
new AlertDialog.Builder(getContext())
.setIconAttribute(R.attr.dialog_alert_icon)
.setTitle(getContext().getString(R.string.UnknownSenderView_block_s, recipient.toShortString()))
.setMessage(R.string.UnknownSenderView_blocked_contacts_will_no_longer_be_able_to_send_you_messages_or_call_you)
.setPositiveButton(R.string.UnknownSenderView_block, (dialog, which) -> {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(context).setBlocked(recipient, true);
if (threadId != -1) DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
return null;
}
@Override
protected void onPostExecute(Void result) {
recipient.setBlocked(true);
}
}.execute();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void handleAdd() {
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
if (!TextUtils.isEmpty(recipient.getProfileName())) {
intent.putExtra(ContactsContract.Intents.Insert.NAME, recipient.getProfileName());
}
if (recipient.getAddress().isEmail()) {
intent.putExtra(ContactsContract.Intents.Insert.EMAIL, recipient.getAddress().toEmailString());
}
if (recipient.getAddress().isPhone()) {
intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getAddress().toPhoneString());
}
getContext().startActivity(intent);
if (threadId != -1) DatabaseFactory.getThreadDatabase(getContext()).setHasSent(threadId, true);
}
private void handleProfileAccess() {
final Context context = getContext();
new AlertDialog.Builder(getContext())
.setIconAttribute(R.attr.dialog_info_icon)
.setTitle(getContext().getString(R.string.UnknownSenderView_share_profile_with_s, recipient.toShortString()))
.setMessage(R.string.UnknownSenderView_the_easiest_way_to_share_your_profile_information_is_to_add_the_sender_to_your_contacts)
.setPositiveButton(R.string.UnknownSenderView_share_profile, (dialog, which) -> {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(context).setProfileSharing(recipient.getAddress(), true);
if (threadId != -1) DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
return null;
}
}.execute();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}

View File

@@ -132,7 +132,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
{
int headerHeight = getHeaderHeightForLayout(header);
int top = getChildY(parent, child) - headerHeight;
if (layoutPos == 0) {
if (sticky && layoutPos == 0) {
final int count = parent.getChildCount();
final long currentId = adapter.getHeaderId(adapterPos);
// find next view with header and compute the offscreen push if needed