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

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:layout_marginTop="20dp"
android:background="@color/white">
<TextView android:id="@+id/unknown_sender_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="25dp"
android:textColor="?android:textColorSecondary"
android:text="@string/unknown_sender_view__the_sender_is_not_in_your_contact_list"/>
<LinearLayout android:id="@+id/block"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp"
android:clickable="true"
android:gravity="center_vertical">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_block_white_24dp"
android:tint="@color/signal_primary"/>
<TextView android:layout_marginLeft="15dp"
android:layout_marginStart="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/unknown_sender_view__block"
android:textAllCaps="true"
android:textStyle="bold"
android:textColor="@color/signal_primary"
android:textSize="14sp"/>
</LinearLayout>
<LinearLayout android:id="@+id/add_to_contacts"
android:clickable="true"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp"
android:gravity="center_vertical">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_person_add_white_24dp"
android:tint="@color/signal_primary"/>
<TextView android:layout_marginLeft="15dp"
android:layout_marginStart="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/unknown_sender_view__add_to_contacts"
android:textAllCaps="true"
android:textStyle="bold"
android:textColor="@color/signal_primary"
android:textSize="14sp"/>
</LinearLayout>
<LinearLayout android:id="@+id/share_profile"
android:clickable="true"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp"
android:gravity="center_vertical">
<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_face_white_24dp"
android:tint="@color/signal_primary"/>
<TextView android:layout_marginLeft="15dp"
android:layout_marginStart="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/unknown_sender_view__don_t_add_but_make_my_profile_visible"
android:textAllCaps="true"
android:textStyle="bold"
android:textColor="@color/signal_primary"
android:textSize="14sp"/>
</LinearLayout>
</LinearLayout>

View File

@ -617,6 +617,14 @@
<string name="UpdateApkReadyListener_Signal_update">Signal update</string>
<string name="UpdateApkReadyListener_a_new_version_of_signal_is_available_tap_to_update">A new version of Signal is available, tap to update</string>
<!-- UnknownSenderView -->
<string name="UnknownSenderView_block_s">Block %s?</string>
<string name="UnknownSenderView_blocked_contacts_will_no_longer_be_able_to_send_you_messages_or_call_you">Blocked contacts will no longer be able to send you messages or call you.</string>
<string name="UnknownSenderView_block">Block</string>
<string name="UnknownSenderView_share_profile_with_s">Share profile with %s?</string>
<string name="UnknownSenderView_the_easiest_way_to_share_your_profile_information_is_to_add_the_sender_to_your_contacts">The easiest way to share your profile information is to add the sender to your contacts. If you do not wish to, you can still share your profile information this way.</string>
<string name="UnknownSenderView_share_profile">Share profile</string>
<!-- UntrustedSendDialog -->
<string name="UntrustedSendDialog_send_message">Send message?</string>
<string name="UntrustedSendDialog_send">Send</string>
@ -1065,6 +1073,12 @@
<string name="recipients_panel__to"><small>Enter a name or number</small></string>
<string name="recipients_panel__add_members">Add members</string>
<!-- unknown_sender_view -->
<string name="unknown_sender_view__the_sender_is_not_in_your_contact_list">The sender is not in your contact list</string>
<string name="unknown_sender_view__block">BLOCK</string>
<string name="unknown_sender_view__add_to_contacts">ADD TO CONTACTS</string>
<string name="unknown_sender_view__don_t_add_but_make_my_profile_visible">DON\'T ADD, BUT MAKE MY PROFILE VISIBLE</string>
<!-- verify_display_fragment -->
<string name="verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s"><![CDATA[If you wish to verify the security of your encryption with %s, compare the number above with the number on their device. Alternatively, you can scan the code on their phone, or ask them to scan your code. <a href="https://whispersystems.org/redirect/safety-numbers">Learn more.</a>]]></string>
<string name="verify_display_fragment__tap_to_scan">Tap to scan</string>
@ -1424,7 +1438,6 @@
<string name="transport_selection_list_item__transport_icon">Transport icon</string>
<!-- EOF -->
</resources>

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