diff --git a/res/drawable-hdpi/ic_call_made_grey600_24dp.png b/res/drawable-hdpi/ic_call_made_grey600_24dp.png new file mode 100644 index 0000000000..7d0807b0bf Binary files /dev/null and b/res/drawable-hdpi/ic_call_made_grey600_24dp.png differ diff --git a/res/drawable-hdpi/ic_call_missed_grey600_24dp.png b/res/drawable-hdpi/ic_call_missed_grey600_24dp.png new file mode 100644 index 0000000000..0241747c68 Binary files /dev/null and b/res/drawable-hdpi/ic_call_missed_grey600_24dp.png differ diff --git a/res/drawable-hdpi/ic_call_received_grey600_24dp.png b/res/drawable-hdpi/ic_call_received_grey600_24dp.png new file mode 100644 index 0000000000..5c9a88d126 Binary files /dev/null and b/res/drawable-hdpi/ic_call_received_grey600_24dp.png differ diff --git a/res/drawable-mdpi/ic_call_made_grey600_24dp.png b/res/drawable-mdpi/ic_call_made_grey600_24dp.png new file mode 100644 index 0000000000..f275722ffe Binary files /dev/null and b/res/drawable-mdpi/ic_call_made_grey600_24dp.png differ diff --git a/res/drawable-mdpi/ic_call_missed_grey600_24dp.png b/res/drawable-mdpi/ic_call_missed_grey600_24dp.png new file mode 100644 index 0000000000..609ef52617 Binary files /dev/null and b/res/drawable-mdpi/ic_call_missed_grey600_24dp.png differ diff --git a/res/drawable-mdpi/ic_call_received_grey600_24dp.png b/res/drawable-mdpi/ic_call_received_grey600_24dp.png new file mode 100644 index 0000000000..685982d8e2 Binary files /dev/null and b/res/drawable-mdpi/ic_call_received_grey600_24dp.png differ diff --git a/res/drawable-xhdpi/ic_call_made_grey600_24dp.png b/res/drawable-xhdpi/ic_call_made_grey600_24dp.png new file mode 100644 index 0000000000..1e609bb5e6 Binary files /dev/null and b/res/drawable-xhdpi/ic_call_made_grey600_24dp.png differ diff --git a/res/drawable-xhdpi/ic_call_missed_grey600_24dp.png b/res/drawable-xhdpi/ic_call_missed_grey600_24dp.png new file mode 100644 index 0000000000..fb5e2794f0 Binary files /dev/null and b/res/drawable-xhdpi/ic_call_missed_grey600_24dp.png differ diff --git a/res/drawable-xhdpi/ic_call_received_grey600_24dp.png b/res/drawable-xhdpi/ic_call_received_grey600_24dp.png new file mode 100644 index 0000000000..91b5587a8a Binary files /dev/null and b/res/drawable-xhdpi/ic_call_received_grey600_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_call_made_grey600_24dp.png b/res/drawable-xxhdpi/ic_call_made_grey600_24dp.png new file mode 100644 index 0000000000..66a9ff46e8 Binary files /dev/null and b/res/drawable-xxhdpi/ic_call_made_grey600_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_call_missed_grey600_24dp.png b/res/drawable-xxhdpi/ic_call_missed_grey600_24dp.png new file mode 100644 index 0000000000..84c13861cb Binary files /dev/null and b/res/drawable-xxhdpi/ic_call_missed_grey600_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_call_received_grey600_24dp.png b/res/drawable-xxhdpi/ic_call_received_grey600_24dp.png new file mode 100644 index 0000000000..f4be04c671 Binary files /dev/null and b/res/drawable-xxhdpi/ic_call_received_grey600_24dp.png differ diff --git a/res/layout/conversation_item_update.xml b/res/layout/conversation_item_update.xml index a3c927e65b..29557e17a1 100644 --- a/res/layout/conversation_item_update.xml +++ b/res/layout/conversation_item_update.xml @@ -1,21 +1,25 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/conversation_update_item" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + android:padding="20dp"> - + + - - + android:textColor="?attr/conversation_group_member_name" + tools:text="Sasha called you"/> + + + diff --git a/src/org/thoughtcrime/redphone/RedPhoneService.java b/src/org/thoughtcrime/redphone/RedPhoneService.java index 7a8a3cb5a4..90732766dd 100644 --- a/src/org/thoughtcrime/redphone/RedPhoneService.java +++ b/src/org/thoughtcrime/redphone/RedPhoneService.java @@ -48,6 +48,7 @@ import org.thoughtcrime.redphone.signaling.SignalingSocket; import org.thoughtcrime.redphone.ui.NotificationBarManager; import org.thoughtcrime.redphone.util.Base64; import org.thoughtcrime.redphone.util.UncaughtExceptionHandlerManager; +import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -92,15 +93,12 @@ public class RedPhoneService extends Service implements CallStateListener, CallS private int state; private byte[] zid; -// private String localNumber; private String remoteNumber; -// private String password; private CallManager currentCallManager; private LockManager lockManager; private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager; private Handler handler; -// private CallLogger.CallRecord currentCallRecord; private IncomingPstnCallListener pstnCallListener; @Override @@ -163,17 +161,9 @@ public class RedPhoneService extends Service implements CallStateListener, CallS registerReceiver(pstnCallListener, new IntentFilter("android.intent.action.PHONE_STATE")); } -// private void initializeApplicationContext() { -// ApplicationContext context = ApplicationContext.getInstance(); -// context.setContext(this); -// context.setCallStateListener(this); -// } - private void initializeResources() { this.state = RedPhone.STATE_IDLE; this.zid = getZID(); -// this.localNumber = TextSecurePreferences.getLocalNumber(this); -// this.password = TextSecurePreferences.getPushServerPassword(this); this.lockManager = new LockManager(this); } @@ -216,8 +206,8 @@ public class RedPhoneService extends Service implements CallStateListener, CallS this.currentCallManager.start(); NotificationBarManager.setCallInProgress(this); -// -// currentCallRecord = CallLogger.logOutgoingCall(this, remoteNumber); + DatabaseFactory.getSmsDatabase(this).insertOutgoingCall(remoteNumber); + } private void handleBusyCall(Intent intent) { @@ -246,14 +236,14 @@ public class RedPhoneService extends Service implements CallStateListener, CallS } private void handleMissedCall(String remoteNumber) { -// CallLogger.logMissedCall(this, remoteNumber, System.currentTimeMillis()); + DatabaseFactory.getSmsDatabase(this).insertMissedCall(remoteNumber); NotificationBarManager.notifyMissedCall(this, remoteNumber); } private void handleAnswerCall(Intent intent) { state = RedPhone.STATE_ANSWERING; incomingRinger.stop(); -// currentCallRecord = CallLogger.logIncomingCall(this, remoteNumber); + DatabaseFactory.getSmsDatabase(this).insertReceivedCall(remoteNumber); if (currentCallManager != null) { ((ResponderCallManager)this.currentCallManager).answer(true); } @@ -262,7 +252,7 @@ public class RedPhoneService extends Service implements CallStateListener, CallS private void handleDenyCall(Intent intent) { state = RedPhone.STATE_IDLE; incomingRinger.stop(); -// CallLogger.logMissedCall(this, remoteNumber, System.currentTimeMillis()); + DatabaseFactory.getSmsDatabase(this).insertMissedCall(remoteNumber); if(currentCallManager != null) { ((ResponderCallManager)this.currentCallManager).answer(false); } diff --git a/src/org/thoughtcrime/redphone/ui/NotificationBarManager.java b/src/org/thoughtcrime/redphone/ui/NotificationBarManager.java index 675f60fa6f..47c2c4d19a 100644 --- a/src/org/thoughtcrime/redphone/ui/NotificationBarManager.java +++ b/src/org/thoughtcrime/redphone/ui/NotificationBarManager.java @@ -22,9 +22,15 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.net.Uri; +import android.support.v4.app.NotificationCompat; import org.thoughtcrime.redphone.RedPhone; +import org.thoughtcrime.securesms.ConversationActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.Recipients; /** * Manages the state of the RedPhone items in the Android notification bar. @@ -35,7 +41,8 @@ import org.thoughtcrime.securesms.R; public class NotificationBarManager { - private static final int RED_PHONE_NOTIFICATION = 313388; + private static final int RED_PHONE_NOTIFICATION = 313388; + private static final int MISSED_CALL_NOTIFICATION = 313389; public static void setCallEnded(Context context) { NotificationManager notificationManager = (NotificationManager)context @@ -61,26 +68,27 @@ public class NotificationBarManager { } public static void notifyMissedCall(Context context, String remoteNumber) { -// Intent intent = new Intent(DialerActivity.CALL_LOG_ACTION, null, -// context, DialerActivity.class); -// PendingIntent launchIntent = PendingIntent.getActivity(context, 0, intent, 0); -// PersonInfo remoteInfo = PersonInfo.getInstance(context, remoteNumber); + Intent intent = new Intent(context, ConversationActivity.class); + Recipients notifyRecipients = RecipientFactory.getRecipientsFromString(context, remoteNumber, false); + intent.putExtra("recipients", notifyRecipients.getIds()); + intent.putExtra("thread_id", DatabaseFactory.getThreadDatabase(context).getThreadIdFor(notifyRecipients)); + intent.setData((Uri.parse("custom://" + System.currentTimeMillis()))); -// NotificationCompat.Builder builder = new NotificationCompat.Builder(context); -// builder.setSmallIcon(R.drawable.stat_notify_missed_call); -// builder.setWhen(System.currentTimeMillis()); -// builder.setTicker(context -// .getString(R.string.NotificationBarManager_missed_redphone_call_from_s, -// remoteInfo.getName())); -// builder.setContentTitle(context.getString(R.string.NotificationBarManager_missed_redphone_call)); -// builder.setContentText(remoteInfo.getName()); -// builder.setContentIntent(launchIntent); -// builder.setDefaults(Notification.DEFAULT_VIBRATE); -// builder.setAutoCancel(true); -// -// NotificationManager manager = (NotificationManager)context -// .getSystemService(Context.NOTIFICATION_SERVICE); -// -// manager.notify(DialerActivity.MISSED_CALL, builder.build()); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context); + builder.setSmallIcon(R.drawable.ic_call_missed_grey600_24dp); + builder.setWhen(System.currentTimeMillis()); + builder.setTicker(String.format("Missed call from %s", notifyRecipients.toShortString())); + builder.setContentTitle("Missed Signal call"); + builder.setContentText(String.format("Missed call from %s", notifyRecipients.toShortString())); + builder.setContentIntent(pendingIntent); + builder.setDefaults(Notification.DEFAULT_VIBRATE); + builder.setAutoCancel(true); + + NotificationManager manager = (NotificationManager)context + .getSystemService(Context.NOTIFICATION_SERVICE); + + manager.notify(MISSED_CALL_NOTIFICATION, builder.build()); } } diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index ed636731e8..370cfc723d 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -164,9 +164,9 @@ public class ConversationAdapter String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT)); MessageRecord messageRecord = getMessageRecord(id, cursor, type); - if (messageRecord.isGroupAction()) return MESSAGE_TYPE_UPDATE; - else if (messageRecord.isOutgoing()) return MESSAGE_TYPE_OUTGOING; - else return MESSAGE_TYPE_INCOMING; + if (messageRecord.isGroupAction() || messageRecord.isCallLog()) return MESSAGE_TYPE_UPDATE; + else if (messageRecord.isOutgoing()) return MESSAGE_TYPE_OUTGOING; + else return MESSAGE_TYPE_INCOMING; } private MessageRecord getMessageRecord(long messageId, Cursor cursor, String type) { diff --git a/src/org/thoughtcrime/securesms/ConversationUpdateItem.java b/src/org/thoughtcrime/securesms/ConversationUpdateItem.java index a8322aa188..b41d36b5c8 100644 --- a/src/org/thoughtcrime/securesms/ConversationUpdateItem.java +++ b/src/org/thoughtcrime/securesms/ConversationUpdateItem.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipients; +import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; @@ -26,8 +27,10 @@ public class ConversationUpdateItem extends LinearLayout private ImageView icon; private TextView body; + private TextView date; private Recipient sender; private MessageRecord messageRecord; + private Locale locale; public ConversationUpdateItem(Context context) { super(context); @@ -43,6 +46,7 @@ public class ConversationUpdateItem extends LinearLayout this.icon = (ImageView)findViewById(R.id.conversation_update_icon); this.body = (TextView)findViewById(R.id.conversation_update_body); + this.date = (TextView)findViewById(R.id.conversation_update_date); setOnClickListener(this); } @@ -54,28 +58,45 @@ public class ConversationUpdateItem extends LinearLayout @NonNull Set batchSelected, boolean groupThread, boolean pushDestination) { - bind(messageRecord); + bind(messageRecord, locale); } - private void bind(@NonNull MessageRecord messageRecord) { + private void bind(@NonNull MessageRecord messageRecord, @NonNull Locale locale) { this.messageRecord = messageRecord; this.sender = messageRecord.getIndividualRecipient(); + this.locale = locale; this.sender.addListener(this); - if (messageRecord.isGroupAction()) { - icon.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_group_grey600_24dp)); + if (messageRecord.isGroupAction()) setGroupRecord(messageRecord); + else if (messageRecord.isCallLog()) setCallRecord(messageRecord); + else throw new AssertionError("Neither group no log."); + } - if (messageRecord.isGroupQuit() && messageRecord.isOutgoing()) { - body.setText(R.string.MessageRecord_left_group); - } else if (messageRecord.isGroupQuit()) { - body.setText(getContext().getString(R.string.ConversationItem_group_action_left, sender.toShortString())); - } else { - GroupUtil.GroupDescription description = GroupUtil.getDescription(getContext(), messageRecord.getBody().getBody()); - description.addListener(this); - body.setText(description.toString()); - } + private void setCallRecord(MessageRecord messageRecord) { + if (messageRecord.isIncomingCall()) icon.setImageResource(R.drawable.ic_call_received_grey600_24dp); + else if (messageRecord.isOutgoingCall()) icon.setImageResource(R.drawable.ic_call_made_grey600_24dp); + else icon.setImageResource(R.drawable.ic_call_missed_grey600_24dp); + + body.setText(messageRecord.getDisplayBody()); + date.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, messageRecord.getDateReceived())); + date.setVisibility(View.VISIBLE); + } + + private void setGroupRecord(MessageRecord messageRecord) { + icon.setImageResource(R.drawable.ic_group_grey600_24dp); + + if (messageRecord.isGroupQuit() && messageRecord.isOutgoing()) { + body.setText(R.string.MessageRecord_left_group); + } else if (messageRecord.isGroupQuit()) { + body.setText(getContext().getString(R.string.ConversationItem_group_action_left, sender.toShortString())); + } else { + GroupUtil.GroupDescription description = GroupUtil.getDescription(getContext(), messageRecord.getBody().getBody()); + description.addListener(this); + body.setText(description.toString()); } + + date.setVisibility(View.GONE); } @Override @@ -88,7 +109,7 @@ public class ConversationUpdateItem extends LinearLayout Util.runOnMain(new Runnable() { @Override public void run() { - bind(messageRecord); + bind(messageRecord, locale); } }); } diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java index 9eaceb0bac..fcb24c6660 100644 --- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -19,6 +19,10 @@ public interface MmsSmsColumns { // Base Types protected static final long BASE_TYPE_MASK = 0x1F; + protected static final long INCOMING_CALL_TYPE = 1; + protected static final long OUTGOING_CALL_TYPE = 2; + protected static final long MISSED_CALL_TYPE = 3; + protected static final long BASE_INBOX_TYPE = 20; protected static final long BASE_OUTBOX_TYPE = 21; protected static final long BASE_SENDING_TYPE = 22; @@ -150,6 +154,22 @@ public interface MmsSmsColumns { return (type & KEY_EXCHANGE_IDENTITY_UPDATE_BIT) != 0; } + public static boolean isCallLog(long type) { + return type == INCOMING_CALL_TYPE || type == OUTGOING_CALL_TYPE || type == MISSED_CALL_TYPE; + } + + public static boolean isIncomingCall(long type) { + return type == INCOMING_CALL_TYPE; + } + + public static boolean isOutgoingCall(long type) { + return type == OUTGOING_CALL_TYPE; + } + + public static boolean isMissedCall(long type) { + return type == MISSED_CALL_TYPE; + } + public static boolean isGroupUpdate(long type) { return (type & GROUP_UPDATE_BIT) != 0; } diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java index a5d4175859..784d09655f 100644 --- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -22,6 +22,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; +import android.support.annotation.NonNull; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.util.Log; @@ -343,6 +344,45 @@ public class SmsDatabase extends MessagingDatabase { return new Pair<>(newMessageId, record.getThreadId()); } + public @NonNull Pair insertReceivedCall(@NonNull String number) { + return insertCallLog(number, Types.INCOMING_CALL_TYPE, false); + } + + public @NonNull Pair insertOutgoingCall(@NonNull String number) { + return insertCallLog(number, Types.OUTGOING_CALL_TYPE, false); + } + + public @NonNull Pair insertMissedCall(@NonNull String number) { + return insertCallLog(number, Types.MISSED_CALL_TYPE, true); + } + + private @NonNull Pair insertCallLog(@NonNull String number, long type, boolean unread) { + Recipients recipients = RecipientFactory.getRecipientsFromString(context, number, true); + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); + + ContentValues values = new ContentValues(6); + values.put(ADDRESS, number); + values.put(ADDRESS_DEVICE_ID, 1); + values.put(DATE_RECEIVED, System.currentTimeMillis()); + values.put(DATE_SENT, System.currentTimeMillis()); + values.put(READ, unread ? 0 : 1); + values.put(TYPE, type); + values.put(THREAD_ID, threadId); + + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + long messageId = db.insert(TABLE_NAME, null, values); + + DatabaseFactory.getThreadDatabase(context).update(threadId); + notifyConversationListeners(threadId); + jobManager.add(new TrimThreadJob(context, threadId)); + + if (unread) { + DatabaseFactory.getThreadDatabase(context).setUnread(threadId); + } + + return new Pair<>(messageId, threadId); + } + protected Pair insertMessageInbox(IncomingTextMessage message, long type) { if (message.isPreKeyBundle()) { type |= Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT; diff --git a/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 3c0bc3ed44..67c13ff8a4 100644 --- a/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -95,6 +95,22 @@ public abstract class DisplayRecord { return isGroupUpdate() || isGroupQuit(); } + public boolean isCallLog() { + return SmsDatabase.Types.isCallLog(type); + } + + public boolean isIncomingCall() { + return SmsDatabase.Types.isIncomingCall(type); + } + + public boolean isOutgoingCall() { + return SmsDatabase.Types.isOutgoingCall(type); + } + + public boolean isMissedCall() { + return SmsDatabase.Types.isMissedCall(type); + } + public static class Body { private final String body; private final boolean plaintext; diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java index c58a7ab870..e1f9afa7d2 100644 --- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -115,6 +115,12 @@ public abstract class MessageRecord extends DisplayRecord { return emphasisAdded(context.getString(R.string.MessageRecord_left_group)); } else if (isGroupQuit()) { return emphasisAdded(context.getString(R.string.ConversationItem_group_action_left, getIndividualRecipient().toShortString())); + } else if (isIncomingCall()) { + return emphasisAdded(String.format("%s called you", getIndividualRecipient().toShortString())); + } else if (isOutgoingCall()) { + return emphasisAdded(String.format("Called %s", getIndividualRecipient().toShortString())); + } else if (isMissedCall()) { + return emphasisAdded(String.format("Missed call from %s", getIndividualRecipient().toShortString())); } else if (getBody().getBody().length() > MAX_DISPLAY_LENGTH) { return new SpannableString(getBody().getBody().substring(0, MAX_DISPLAY_LENGTH)); }