That fuck shit the fascists are using
at master 1504 lines 49 kB view raw
1package org.tm.archive.database 2 3import android.content.Context 4import android.database.Cursor 5import androidx.annotation.Discouraged 6import androidx.core.content.contentValuesOf 7import org.signal.core.util.IntSerializer 8import org.signal.core.util.Serializer 9import org.signal.core.util.SqlUtil 10import org.signal.core.util.count 11import org.signal.core.util.delete 12import org.signal.core.util.deleteAll 13import org.signal.core.util.flatten 14import org.signal.core.util.insertInto 15import org.signal.core.util.isAbsent 16import org.signal.core.util.logging.Log 17import org.signal.core.util.readToList 18import org.signal.core.util.readToMap 19import org.signal.core.util.readToSingleLong 20import org.signal.core.util.readToSingleObject 21import org.signal.core.util.requireLong 22import org.signal.core.util.requireNonNullString 23import org.signal.core.util.requireObject 24import org.signal.core.util.requireString 25import org.signal.core.util.select 26import org.signal.core.util.toSingleLine 27import org.signal.core.util.update 28import org.signal.core.util.withinTransaction 29import org.signal.ringrtc.CallId 30import org.signal.ringrtc.CallManager.RingUpdate 31import org.tm.archive.calls.log.CallLogFilter 32import org.tm.archive.calls.log.CallLogRow 33import org.tm.archive.database.model.GroupCallUpdateDetailsUtil 34import org.tm.archive.database.model.MessageId 35import org.tm.archive.dependencies.ApplicationDependencies 36import org.tm.archive.jobs.CallSyncEventJob 37import org.tm.archive.recipients.Recipient 38import org.tm.archive.recipients.RecipientId 39import org.tm.archive.service.webrtc.links.CallLinkRoomId 40import org.whispersystems.signalservice.api.push.ServiceId.ACI 41import org.whispersystems.signalservice.internal.push.SyncMessage.CallEvent 42import java.util.UUID 43import java.util.concurrent.TimeUnit 44 45/** 46 * Contains details for each 1:1 call. 47 */ 48class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference { 49 50 companion object { 51 private val TAG = Log.tag(CallTable::class.java) 52 private val TIME_WINDOW = TimeUnit.HOURS.toMillis(4) 53 54 const val TABLE_NAME = "call" 55 const val ID = "_id" 56 const val CALL_ID = "call_id" 57 const val MESSAGE_ID = "message_id" 58 const val PEER = "peer" 59 const val TYPE = "type" 60 const val DIRECTION = "direction" 61 const val EVENT = "event" 62 const val TIMESTAMP = "timestamp" 63 const val RINGER = "ringer" 64 const val DELETION_TIMESTAMP = "deletion_timestamp" 65 const val READ = "read" 66 67 //language=sql 68 const val CREATE_TABLE = """ 69 CREATE TABLE $TABLE_NAME ( 70 $ID INTEGER PRIMARY KEY, 71 $CALL_ID INTEGER NOT NULL, 72 $MESSAGE_ID INTEGER DEFAULT NULL REFERENCES ${MessageTable.TABLE_NAME} (${MessageTable.ID}) ON DELETE SET NULL, 73 $PEER INTEGER NOT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE, 74 $TYPE INTEGER NOT NULL, 75 $DIRECTION INTEGER NOT NULL, 76 $EVENT INTEGER NOT NULL, 77 $TIMESTAMP INTEGER NOT NULL, 78 $RINGER INTEGER DEFAULT NULL, 79 $DELETION_TIMESTAMP INTEGER DEFAULT 0, 80 $READ INTEGER DEFAULT 1, 81 UNIQUE ($CALL_ID, $PEER) ON CONFLICT FAIL 82 ) 83 """ 84 85 val CREATE_INDEXES = arrayOf( 86 "CREATE INDEX call_call_id_index ON $TABLE_NAME ($CALL_ID)", 87 "CREATE INDEX call_message_id_index ON $TABLE_NAME ($MESSAGE_ID)", 88 "CREATE INDEX call_peer_index ON $TABLE_NAME ($PEER)" 89 ) 90 } 91 92 fun markAllCallEventsRead(timestamp: Long = Long.MAX_VALUE) { 93 writableDatabase.update(TABLE_NAME) 94 .values(READ to ReadState.serialize(ReadState.READ)) 95 .where("$TIMESTAMP <= ?", timestamp) 96 .run() 97 98 notifyConversationListListeners() 99 } 100 101 fun getUnreadMissedCallCount(): Long { 102 return readableDatabase 103 .count() 104 .from(TABLE_NAME) 105 .where("$EVENT = ? AND $READ = ?", Event.serialize(Event.MISSED), ReadState.serialize(ReadState.UNREAD)) 106 .run() 107 .readToSingleLong() 108 } 109 110 fun insertOneToOneCall(callId: Long, timestamp: Long, peer: RecipientId, type: Type, direction: Direction, event: Event) { 111 val messageType: Long = Call.getMessageType(type, direction, event) 112 113 writableDatabase.withinTransaction { 114 val result = SignalDatabase.messages.insertCallLog(peer, messageType, timestamp, direction == Direction.OUTGOING) 115 val values = contentValuesOf( 116 CALL_ID to callId, 117 MESSAGE_ID to result.messageId, 118 PEER to peer.serialize(), 119 TYPE to Type.serialize(type), 120 DIRECTION to Direction.serialize(direction), 121 EVENT to Event.serialize(event), 122 TIMESTAMP to timestamp, 123 READ to ReadState.serialize(ReadState.UNREAD) 124 ) 125 126 writableDatabase.insert(TABLE_NAME, null, values) 127 } 128 129 ApplicationDependencies.getMessageNotifier().updateNotification(context) 130 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 131 132 Log.i(TAG, "Inserted call: $callId type: $type direction: $direction event:$event") 133 } 134 135 fun updateOneToOneCall(callId: Long, event: Event): Call? { 136 return writableDatabase.withinTransaction { 137 writableDatabase 138 .update(TABLE_NAME) 139 .values( 140 EVENT to Event.serialize(event), 141 READ to ReadState.serialize(ReadState.UNREAD) 142 ) 143 .where("$CALL_ID = ?", callId) 144 .run() 145 146 val call = readableDatabase 147 .select() 148 .from(TABLE_NAME) 149 .where("$CALL_ID = ?", callId) 150 .run() 151 .readToSingleObject(Call.Deserializer) 152 153 if (call != null) { 154 Log.i(TAG, "Updated call: $callId event: $event") 155 156 if (call.messageId == null) { 157 Log.w(TAG, "Call does not have an associated message id! No message to update.") 158 } else { 159 SignalDatabase.messages.updateCallLog(call.messageId, call.messageType) 160 } 161 162 ApplicationDependencies.getMessageNotifier().updateNotification(context) 163 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 164 } 165 166 call 167 } 168 } 169 170 fun getCallById(callId: Long, recipientId: RecipientId): Call? { 171 val query = getCallSelectionQuery(callId, recipientId) 172 173 return readableDatabase 174 .select() 175 .from(TABLE_NAME) 176 .where(query.where, query.whereArgs) 177 .run() 178 .readToSingleObject(Call.Deserializer) 179 } 180 181 fun getCallByMessageId(messageId: Long): Call? { 182 return readableDatabase 183 .select() 184 .from(TABLE_NAME) 185 .where("$MESSAGE_ID = ?", messageId) 186 .run() 187 .readToSingleObject(Call.Deserializer) 188 } 189 190 fun getCalls(messageIds: Collection<Long>): Map<Long, Call> { 191 val queries = SqlUtil.buildCollectionQuery(MESSAGE_ID, messageIds) 192 val maps = queries.map { query -> 193 readableDatabase 194 .select() 195 .from(TABLE_NAME) 196 .where("$EVENT != ${Event.serialize(Event.DELETE)} AND ${query.where}", query.whereArgs) 197 .run() 198 .readToMap { c -> c.requireLong(MESSAGE_ID) to Call.deserialize(c) } 199 } 200 201 return maps.flatten() 202 } 203 204 /** 205 * @param callRowIds The CallTable.ID collection to query 206 * 207 * @return a map of raw MessageId -> Call 208 */ 209 fun getCallsByRowIds(callRowIds: Collection<Long>): Map<Long, Call> { 210 val queries = SqlUtil.buildCollectionQuery(ID, callRowIds) 211 212 val maps = queries.map { query -> 213 readableDatabase 214 .select() 215 .from(TABLE_NAME) 216 .where("$EVENT != ${Event.serialize(Event.DELETE)} AND ${query.where}", query.whereArgs) 217 .run() 218 .readToMap { c -> c.requireLong(MESSAGE_ID) to Call.deserialize(c) } 219 } 220 221 return maps.flatten() 222 } 223 224 fun getOldestDeletionTimestamp(): Long { 225 return writableDatabase 226 .select(DELETION_TIMESTAMP) 227 .from(TABLE_NAME) 228 .where("$DELETION_TIMESTAMP > 0") 229 .orderBy("$DELETION_TIMESTAMP DESC") 230 .limit(1) 231 .run() 232 .readToSingleLong(0L) 233 } 234 235 fun deleteCallEventsDeletedBefore(threshold: Long): Int { 236 return writableDatabase 237 .delete(TABLE_NAME) 238 .where("$DELETION_TIMESTAMP > 0 AND $DELETION_TIMESTAMP <= ?", threshold) 239 .run() 240 } 241 242 fun getCallLinkRoomIdsFromCallRowIds(callRowIds: Set<Long>): Set<CallLinkRoomId> { 243 return SqlUtil.buildCollectionQuery("$TABLE_NAME.$ID", callRowIds).map { query -> 244 //language=sql 245 val statement = """ 246 SELECT ${CallLinkTable.ROOM_ID} FROM $TABLE_NAME 247 INNER JOIN ${CallLinkTable.TABLE_NAME} ON ${CallLinkTable.TABLE_NAME}.${CallLinkTable.RECIPIENT_ID} = $PEER 248 WHERE $TYPE = ${Type.serialize(Type.AD_HOC_CALL)} AND ${query.where} 249 """.toSingleLine() 250 251 readableDatabase.query(statement, query.whereArgs).readToList { 252 CallLinkRoomId.DatabaseSerializer.deserialize(it.requireNonNullString(CallLinkTable.ROOM_ID)) 253 } 254 }.flatten().toSet() 255 } 256 257 /** 258 * If a call link has been revoked, or if we do not have a CallLink table entry for an AD_HOC_CALL type 259 * event, we mark it deleted. 260 */ 261 fun updateAdHocCallEventDeletionTimestamps(skipSync: Boolean = false) { 262 //language=sql 263 val statement = """ 264 UPDATE $TABLE_NAME 265 SET $DELETION_TIMESTAMP = ${System.currentTimeMillis()}, $EVENT = ${Event.serialize(Event.DELETE)} 266 WHERE $TYPE = ${Type.serialize(Type.AD_HOC_CALL)} 267 AND ( 268 (NOT EXISTS (SELECT 1 FROM ${CallLinkTable.TABLE_NAME} WHERE ${CallLinkTable.RECIPIENT_ID} = $PEER)) 269 OR 270 (SELECT ${CallLinkTable.REVOKED} FROM ${CallLinkTable.TABLE_NAME} WHERE ${CallLinkTable.RECIPIENT_ID} = $PEER) 271 ) 272 RETURNING * 273 """.toSingleLine() 274 275 val toSync = writableDatabase.query(statement).readToList { 276 Call.deserialize(it) 277 }.toSet() 278 279 if (!skipSync) { 280 CallSyncEventJob.enqueueDeleteSyncEvents(toSync) 281 } 282 283 ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary() 284 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 285 } 286 287 /** 288 * If a non-ad-hoc call has been deleted from the message database, then we need to 289 * set its deletion_timestamp to now. 290 */ 291 fun updateCallEventDeletionTimestamps(skipSync: Boolean = false) { 292 val where = "$TYPE != ? AND $DELETION_TIMESTAMP = 0 AND $MESSAGE_ID IS NULL" 293 val type = Type.serialize(Type.AD_HOC_CALL) 294 295 val toSync = writableDatabase.withinTransaction { db -> 296 val result = db 297 .select() 298 .from(TABLE_NAME) 299 .where(where, type) 300 .run() 301 .readToList { 302 Call.deserialize(it) 303 } 304 .toSet() 305 306 db 307 .update(TABLE_NAME) 308 .values( 309 EVENT to Event.serialize(Event.DELETE), 310 DELETION_TIMESTAMP to System.currentTimeMillis() 311 ) 312 .where(where, type) 313 .run() 314 315 result 316 } 317 318 if (!skipSync) { 319 CallSyncEventJob.enqueueDeleteSyncEvents(toSync) 320 } 321 322 ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary() 323 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 324 } 325 326 // region Group / Ad-Hoc Calling 327 fun deleteGroupCall(call: Call) { 328 checkIsGroupOrAdHocCall(call) 329 330 val filter: SqlUtil.Query = getCallSelectionQuery(call.callId, call.peer) 331 332 writableDatabase.withinTransaction { db -> 333 db 334 .update(TABLE_NAME) 335 .values( 336 EVENT to Event.serialize(Event.DELETE), 337 DELETION_TIMESTAMP to System.currentTimeMillis() 338 ) 339 .where(filter.where, filter.whereArgs) 340 .run() 341 342 if (call.messageId != null) { 343 SignalDatabase.messages.deleteMessage(call.messageId) 344 } 345 } 346 347 ApplicationDependencies.getMessageNotifier().updateNotification(context) 348 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 349 Log.d(TAG, "Marked group call event for deletion: ${call.callId}") 350 } 351 352 fun insertDeletedGroupCallFromSyncEvent( 353 callId: Long, 354 recipientId: RecipientId, 355 direction: Direction, 356 timestamp: Long 357 ) { 358 val recipient = Recipient.resolved(recipientId) 359 val type = if (recipient.isCallLink) Type.AD_HOC_CALL else Type.GROUP_CALL 360 361 writableDatabase 362 .insertInto(TABLE_NAME) 363 .values( 364 CALL_ID to callId, 365 MESSAGE_ID to null, 366 PEER to recipientId.toLong(), 367 EVENT to Event.serialize(Event.DELETE), 368 TYPE to Type.serialize(type), 369 DIRECTION to Direction.serialize(direction), 370 TIMESTAMP to timestamp, 371 DELETION_TIMESTAMP to System.currentTimeMillis() 372 ) 373 .run(SQLiteDatabase.CONFLICT_ABORT) 374 375 ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary() 376 } 377 378 fun acceptIncomingGroupCall(call: Call) { 379 checkIsGroupOrAdHocCall(call) 380 381 val newEvent = when (call.event) { 382 Event.RINGING, Event.MISSED, Event.MISSED_NOTIFICATION_PROFILE, Event.DECLINED -> Event.ACCEPTED 383 Event.GENERIC_GROUP_CALL -> Event.JOINED 384 else -> { 385 Log.d(TAG, "[acceptIncomingGroupCall] Call in state ${call.event} cannot be transitioned by ACCEPTED") 386 return 387 } 388 } 389 390 writableDatabase 391 .update(TABLE_NAME) 392 .values(EVENT to Event.serialize(newEvent)) 393 .where("$CALL_ID = ?", call.callId) 394 .run() 395 396 ApplicationDependencies.getMessageNotifier().updateNotification(context) 397 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 398 Log.d(TAG, "[acceptIncomingGroupCall] Transitioned group call ${call.callId} from ${call.event} to $newEvent") 399 } 400 401 fun acceptOutgoingGroupCall(call: Call) { 402 checkIsGroupOrAdHocCall(call) 403 404 val newEvent = when (call.event) { 405 Event.GENERIC_GROUP_CALL, Event.JOINED -> Event.OUTGOING_RING 406 Event.RINGING, Event.MISSED, Event.MISSED_NOTIFICATION_PROFILE, Event.DECLINED, Event.ACCEPTED -> { 407 Log.w(TAG, "[acceptOutgoingGroupCall] This shouldn't have been an outgoing ring because the call already existed!") 408 Event.ACCEPTED 409 } 410 411 else -> { 412 Log.d(TAG, "[acceptOutgoingGroupCall] Call in state ${call.event} cannot be transitioned by ACCEPTED") 413 return 414 } 415 } 416 417 writableDatabase 418 .update(TABLE_NAME) 419 .values(EVENT to Event.serialize(newEvent), DIRECTION to Direction.serialize(Direction.OUTGOING)) 420 .where("$CALL_ID = ?", call.callId) 421 .run() 422 423 ApplicationDependencies.getMessageNotifier().updateNotification(context) 424 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 425 Log.d(TAG, "[acceptOutgoingGroupCall] Transitioned group call ${call.callId} from ${call.event} to $newEvent") 426 } 427 428 fun declineIncomingGroupCall(call: Call) { 429 checkIsGroupOrAdHocCall(call) 430 check(call.direction == Direction.INCOMING) 431 432 val newEvent = when (call.event) { 433 Event.GENERIC_GROUP_CALL, Event.RINGING, Event.MISSED, Event.MISSED_NOTIFICATION_PROFILE -> Event.DECLINED 434 Event.JOINED -> Event.ACCEPTED 435 else -> { 436 Log.d(TAG, "Call in state ${call.event} cannot be transitioned by DECLINED") 437 return 438 } 439 } 440 441 writableDatabase 442 .update(TABLE_NAME) 443 .values(EVENT to Event.serialize(newEvent)) 444 .where("$CALL_ID = ?", call.callId) 445 .run() 446 447 ApplicationDependencies.getMessageNotifier().updateNotification(context) 448 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 449 Log.d(TAG, "Transitioned group call ${call.callId} from ${call.event} to $newEvent") 450 } 451 452 fun insertAcceptedGroupCall( 453 callId: Long, 454 recipientId: RecipientId, 455 direction: Direction, 456 timestamp: Long 457 ) { 458 val recipient = Recipient.resolved(recipientId) 459 val type = if (recipient.isCallLink) Type.AD_HOC_CALL else Type.GROUP_CALL 460 val event = if (direction == Direction.OUTGOING) Event.OUTGOING_RING else Event.JOINED 461 val ringer = if (direction == Direction.OUTGOING) Recipient.self().id.toLong() else null 462 463 writableDatabase.withinTransaction { db -> 464 val messageId: MessageId? = if (type == Type.GROUP_CALL) { 465 SignalDatabase.messages.insertGroupCall( 466 groupRecipientId = recipientId, 467 sender = Recipient.self().id, 468 timestamp, 469 "", 470 emptyList(), 471 false 472 ) 473 } else { 474 null 475 } 476 477 db 478 .insertInto(TABLE_NAME) 479 .values( 480 CALL_ID to callId, 481 MESSAGE_ID to messageId?.id, 482 PEER to recipientId.toLong(), 483 EVENT to Event.serialize(event), 484 TYPE to Type.serialize(type), 485 DIRECTION to Direction.serialize(direction), 486 TIMESTAMP to timestamp, 487 RINGER to ringer 488 ) 489 .run(SQLiteDatabase.CONFLICT_ABORT) 490 } 491 492 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 493 } 494 495 fun insertDeclinedGroupCall( 496 callId: Long, 497 recipientId: RecipientId, 498 timestamp: Long 499 ) { 500 val recipient = Recipient.resolved(recipientId) 501 val type = if (recipient.isCallLink) Type.AD_HOC_CALL else Type.GROUP_CALL 502 503 writableDatabase.withinTransaction { db -> 504 val messageId: MessageId? = if (type == Type.GROUP_CALL) { 505 SignalDatabase.messages.insertGroupCall( 506 groupRecipientId = recipientId, 507 sender = Recipient.self().id, 508 timestamp, 509 "", 510 emptyList(), 511 false 512 ) 513 } else { 514 null 515 } 516 517 db 518 .insertInto(TABLE_NAME) 519 .values( 520 CALL_ID to callId, 521 MESSAGE_ID to messageId?.id, 522 PEER to recipientId.toLong(), 523 EVENT to Event.serialize(Event.DECLINED), 524 TYPE to Type.serialize(type), 525 DIRECTION to Direction.serialize(Direction.INCOMING), 526 TIMESTAMP to timestamp, 527 RINGER to null 528 ) 529 .run(SQLiteDatabase.CONFLICT_ABORT) 530 } 531 532 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 533 } 534 535 fun insertOrUpdateGroupCallFromExternalEvent( 536 groupRecipientId: RecipientId, 537 sender: RecipientId, 538 timestamp: Long, 539 messageGroupCallEraId: String? 540 ) { 541 insertOrUpdateGroupCallFromLocalEvent( 542 groupRecipientId, 543 sender, 544 timestamp, 545 messageGroupCallEraId, 546 emptyList(), 547 false 548 ) 549 } 550 551 fun insertOrUpdateGroupCallFromLocalEvent( 552 groupRecipientId: RecipientId, 553 sender: RecipientId, 554 timestamp: Long, 555 peekGroupCallEraId: String?, 556 peekJoinedUuids: Collection<UUID>, 557 isCallFull: Boolean 558 ) { 559 val recipient = Recipient.resolved(groupRecipientId) 560 if (recipient.isCallLink) { 561 handleCallLinkUpdate(recipient, timestamp, peekGroupCallEraId) 562 } else { 563 handleGroupUpdate(recipient, sender, timestamp, peekGroupCallEraId, peekJoinedUuids, isCallFull) 564 } 565 } 566 567 private fun handleGroupUpdate( 568 groupRecipient: Recipient, 569 sender: RecipientId, 570 timestamp: Long, 571 peekGroupCallEraId: String?, 572 peekJoinedUuids: Collection<UUID>, 573 isCallFull: Boolean 574 ) { 575 check(groupRecipient.isPushV2Group) 576 writableDatabase.withinTransaction { 577 if (peekGroupCallEraId.isNullOrEmpty()) { 578 Log.w(TAG, "Dropping local call event with null era id.") 579 return@withinTransaction 580 } 581 582 val callId = CallId.fromEra(peekGroupCallEraId).longValue() 583 val call = getCallById(callId, groupRecipient.id) 584 val messageId: MessageId = if (call != null) { 585 if (call.event == Event.DELETE) { 586 Log.d(TAG, "Dropping group call update for deleted call.") 587 return@withinTransaction 588 } 589 590 if (call.type != Type.GROUP_CALL) { 591 Log.d(TAG, "Dropping unsupported update message for non-group-call call.") 592 return@withinTransaction 593 } 594 595 if (call.messageId == null) { 596 Log.d(TAG, "Dropping group call update for call without an attached message.") 597 return@withinTransaction 598 } 599 600 SignalDatabase.messages.updateGroupCall( 601 call.messageId, 602 peekGroupCallEraId, 603 peekJoinedUuids, 604 isCallFull 605 ) 606 } else { 607 SignalDatabase.messages.insertGroupCall( 608 groupRecipient.id, 609 sender, 610 timestamp, 611 peekGroupCallEraId, 612 peekJoinedUuids, 613 isCallFull 614 ) 615 } 616 617 insertCallEventFromGroupUpdate( 618 callId, 619 messageId, 620 sender, 621 groupRecipient.id, 622 timestamp 623 ) 624 } 625 } 626 627 private fun handleCallLinkUpdate( 628 callLinkRecipient: Recipient, 629 timestamp: Long, 630 peekGroupCallEraId: String? 631 ) { 632 check(callLinkRecipient.isCallLink) 633 634 val callId = peekGroupCallEraId?.let { CallId.fromEra(it).longValue() } ?: return 635 636 writableDatabase.withinTransaction { db -> 637 db.delete(TABLE_NAME) 638 .where("$PEER = ?", callLinkRecipient.id.serialize()) 639 .run() 640 641 db.insertInto(TABLE_NAME) 642 .values( 643 CALL_ID to callId, 644 MESSAGE_ID to null, 645 PEER to callLinkRecipient.id.toLong(), 646 EVENT to Event.serialize(Event.GENERIC_GROUP_CALL), 647 TYPE to Type.serialize(Type.AD_HOC_CALL), 648 DIRECTION to Direction.serialize(Direction.OUTGOING), 649 TIMESTAMP to timestamp, 650 RINGER to null 651 ).run(SQLiteDatabase.CONFLICT_ABORT) 652 } 653 654 Log.d(TAG, "Inserted new call event for call link. Call Id: $callId") 655 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 656 } 657 658 private fun insertCallEventFromGroupUpdate( 659 callId: Long, 660 messageId: MessageId?, 661 sender: RecipientId, 662 groupRecipientId: RecipientId, 663 timestamp: Long 664 ) { 665 if (messageId != null) { 666 val call = getCallById(callId, groupRecipientId) 667 if (call == null) { 668 val direction = if (sender == Recipient.self().id) Direction.OUTGOING else Direction.INCOMING 669 670 writableDatabase 671 .insertInto(TABLE_NAME) 672 .values( 673 CALL_ID to callId, 674 MESSAGE_ID to messageId.id, 675 PEER to groupRecipientId.toLong(), 676 EVENT to Event.serialize(Event.GENERIC_GROUP_CALL), 677 TYPE to Type.serialize(Type.GROUP_CALL), 678 DIRECTION to Direction.serialize(direction), 679 TIMESTAMP to timestamp, 680 RINGER to null 681 ) 682 .run(SQLiteDatabase.CONFLICT_ABORT) 683 684 Log.d(TAG, "Inserted new call event from group call update message. Call Id: $callId") 685 } else { 686 if (timestamp < call.timestamp) { 687 setTimestamp(callId, groupRecipientId, timestamp) 688 Log.d(TAG, "Updated call event timestamp for call id $callId") 689 } 690 691 if (call.messageId == null) { 692 setMessageId(callId, messageId) 693 Log.d(TAG, "Updated call event message id for newly inserted group call state: $callId") 694 } 695 } 696 } else { 697 Log.d(TAG, "Skipping call event processing for null era id.") 698 } 699 700 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 701 } 702 703 /** 704 * Since this does not alter the call table, we can simply pass this directly through to the old handler. 705 */ 706 fun updateGroupCallFromPeek( 707 threadId: Long, 708 peekGroupCallEraId: String?, 709 peekJoinedUuids: Collection<UUID>, 710 isCallFull: Boolean 711 ): Boolean { 712 val sameEraId = SignalDatabase.messages.updatePreviousGroupCall(threadId, peekGroupCallEraId, peekJoinedUuids, isCallFull) 713 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 714 return sameEraId 715 } 716 717 fun insertOrUpdateGroupCallFromRingState( 718 ringId: Long, 719 groupRecipientId: RecipientId, 720 ringerRecipient: RecipientId, 721 dateReceived: Long, 722 ringState: RingUpdate 723 ) { 724 handleGroupRingState(ringId, groupRecipientId, ringerRecipient, dateReceived, ringState) 725 } 726 727 @JvmOverloads 728 fun insertOrUpdateGroupCallFromRingState( 729 ringId: Long, 730 groupRecipientId: RecipientId, 731 ringerAci: ACI, 732 dateReceived: Long, 733 ringState: RingUpdate, 734 dueToNotificationProfile: Boolean = false 735 ) { 736 val ringerRecipient = Recipient.externalPush(ringerAci) 737 handleGroupRingState(ringId, groupRecipientId, ringerRecipient.id, dateReceived, ringState, dueToNotificationProfile) 738 } 739 740 fun isRingCancelled(ringId: Long, groupRecipientId: RecipientId): Boolean { 741 val call = getCallById(ringId, groupRecipientId) ?: return false 742 return call.event != Event.RINGING && call.event != Event.GENERIC_GROUP_CALL 743 } 744 745 private fun handleGroupRingState( 746 ringId: Long, 747 groupRecipientId: RecipientId, 748 ringerRecipient: RecipientId, 749 dateReceived: Long, 750 ringState: RingUpdate, 751 dueToNotificationProfile: Boolean = false 752 ) { 753 writableDatabase.withinTransaction { 754 Log.d(TAG, "Processing group ring state update for $ringId in state $ringState") 755 756 val call = getCallById(ringId, groupRecipientId) 757 if (call != null) { 758 if (call.event == Event.DELETE) { 759 Log.d(TAG, "Ignoring ring request for $ringId since its event has been deleted.") 760 return@withinTransaction 761 } 762 763 when (ringState) { 764 RingUpdate.REQUESTED -> { 765 when (call.event) { 766 Event.GENERIC_GROUP_CALL -> updateEventFromRingState(ringId, Event.RINGING, ringerRecipient) 767 Event.JOINED -> updateEventFromRingState(ringId, Event.ACCEPTED, ringerRecipient) 768 else -> Log.w(TAG, "Received a REQUESTED ring event while in ${call.event}. Ignoring.") 769 } 770 } 771 772 RingUpdate.EXPIRED_REQUEST, RingUpdate.CANCELLED_BY_RINGER -> { 773 when (call.event) { 774 Event.GENERIC_GROUP_CALL, Event.RINGING -> updateEventFromRingState(ringId, if (dueToNotificationProfile) Event.MISSED_NOTIFICATION_PROFILE else Event.MISSED, ringerRecipient) 775 Event.JOINED -> updateEventFromRingState(ringId, Event.ACCEPTED, ringerRecipient) 776 Event.OUTGOING_RING -> Log.w(TAG, "Received an expiration or cancellation while in OUTGOING_RING state. Ignoring.") 777 else -> Unit 778 } 779 } 780 781 RingUpdate.BUSY_LOCALLY -> { 782 when (call.event) { 783 Event.JOINED -> updateEventFromRingState(ringId, Event.ACCEPTED) 784 Event.GENERIC_GROUP_CALL, Event.RINGING -> updateEventFromRingState(ringId, Event.MISSED) 785 else -> { 786 updateEventFromRingState(ringId, call.event, ringerRecipient) 787 Log.w(TAG, "Received a busy event we can't process. Updating ringer only.") 788 } 789 } 790 } 791 792 RingUpdate.BUSY_ON_ANOTHER_DEVICE -> { 793 when (call.event) { 794 Event.JOINED -> updateEventFromRingState(ringId, Event.ACCEPTED) 795 Event.GENERIC_GROUP_CALL, Event.RINGING -> updateEventFromRingState(ringId, Event.MISSED) 796 else -> Log.w(TAG, "Received a busy event we can't process. Ignoring.") 797 } 798 } 799 800 RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE -> { 801 updateEventFromRingState(ringId, Event.ACCEPTED) 802 } 803 804 RingUpdate.DECLINED_ON_ANOTHER_DEVICE -> { 805 when (call.event) { 806 Event.RINGING, Event.MISSED, Event.MISSED_NOTIFICATION_PROFILE, Event.GENERIC_GROUP_CALL -> updateEventFromRingState(ringId, Event.DECLINED) 807 Event.JOINED -> updateEventFromRingState(ringId, Event.ACCEPTED) 808 Event.OUTGOING_RING -> Log.w(TAG, "Received DECLINED_ON_ANOTHER_DEVICE while in OUTGOING_RING state.") 809 else -> Unit 810 } 811 } 812 } 813 } else { 814 val event: Event = when (ringState) { 815 RingUpdate.REQUESTED -> Event.RINGING 816 RingUpdate.EXPIRED_REQUEST -> if (dueToNotificationProfile) Event.MISSED_NOTIFICATION_PROFILE else Event.MISSED 817 RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE -> { 818 Log.w(TAG, "Missed original ring request for $ringId") 819 Event.ACCEPTED 820 } 821 822 RingUpdate.DECLINED_ON_ANOTHER_DEVICE -> { 823 Log.w(TAG, "Missed original ring request for $ringId") 824 Event.DECLINED 825 } 826 827 RingUpdate.BUSY_LOCALLY, RingUpdate.BUSY_ON_ANOTHER_DEVICE -> { 828 Log.w(TAG, "Missed original ring request for $ringId") 829 Event.MISSED 830 } 831 832 RingUpdate.CANCELLED_BY_RINGER -> { 833 Log.w(TAG, "Missed original ring request for $ringId") 834 Event.MISSED 835 } 836 } 837 838 createEventFromRingState(ringId, groupRecipientId, ringerRecipient, event, dateReceived) 839 } 840 } 841 842 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 843 } 844 845 private fun updateEventFromRingState( 846 callId: Long, 847 event: Event, 848 ringerRecipient: RecipientId 849 ) { 850 writableDatabase 851 .update(TABLE_NAME) 852 .values( 853 EVENT to Event.serialize(event), 854 RINGER to ringerRecipient.serialize() 855 ) 856 .where("$CALL_ID = ?", callId) 857 .run() 858 859 Log.d(TAG, "Updated ring state to $event") 860 } 861 862 private fun updateEventFromRingState( 863 callId: Long, 864 event: Event 865 ) { 866 writableDatabase 867 .update(TABLE_NAME) 868 .values( 869 EVENT to Event.serialize(event) 870 ) 871 .where("$CALL_ID = ?", callId) 872 .run() 873 874 Log.d(TAG, "Updated ring state to $event") 875 } 876 877 private fun createEventFromRingState( 878 callId: Long, 879 groupRecipientId: RecipientId, 880 ringerRecipient: RecipientId, 881 event: Event, 882 timestamp: Long 883 ) { 884 val direction = if (ringerRecipient == Recipient.self().id) Direction.OUTGOING else Direction.INCOMING 885 886 val recipient = Recipient.resolved(groupRecipientId) 887 check(recipient.isPushV2Group) 888 889 writableDatabase.withinTransaction { db -> 890 val messageId = SignalDatabase.messages.insertGroupCall( 891 groupRecipientId = groupRecipientId, 892 sender = ringerRecipient, 893 timestamp = timestamp, 894 eraId = "", 895 joinedUuids = emptyList(), 896 isCallFull = false 897 ) 898 899 db 900 .insertInto(TABLE_NAME) 901 .values( 902 CALL_ID to callId, 903 MESSAGE_ID to messageId.id, 904 PEER to groupRecipientId.toLong(), 905 EVENT to Event.serialize(event), 906 TYPE to Type.serialize(Type.GROUP_CALL), 907 DIRECTION to Direction.serialize(direction), 908 TIMESTAMP to timestamp, 909 RINGER to ringerRecipient.toLong() 910 ) 911 .run(SQLiteDatabase.CONFLICT_ABORT) 912 } 913 914 Log.d(TAG, "Inserted a new group ring event for $callId with event $event") 915 } 916 917 fun setTimestamp(callId: Long, recipientId: RecipientId, timestamp: Long) { 918 writableDatabase.withinTransaction { db -> 919 val call = getCallById(callId, recipientId) 920 if (call == null || call.event == Event.DELETE) { 921 Log.d(TAG, "Refusing to update deleted call event.") 922 return@withinTransaction 923 } 924 925 db 926 .update(TABLE_NAME) 927 .values(TIMESTAMP to timestamp) 928 .where("$CALL_ID = ?", callId) 929 .run() 930 931 if (call.messageId != null) { 932 SignalDatabase.messages.updateCallTimestamps(call.messageId, timestamp) 933 } 934 } 935 936 ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers() 937 } 938 939 private fun setMessageId(callId: Long, messageId: MessageId) { 940 writableDatabase 941 .update(TABLE_NAME) 942 .values(MESSAGE_ID to messageId.id) 943 .where("$CALL_ID = ?", callId) 944 .run() 945 } 946 947 /** 948 * Gets the most recent timestamp from the [TIMESTAMP] column 949 */ 950 fun getLatestCall(): Call? { 951 val statement = """ 952 SELECT * FROM $TABLE_NAME ORDER BY $TIMESTAMP DESC LIMIT 1 953 """.trimIndent() 954 955 return readableDatabase.query(statement).readToSingleObject { Call.deserialize(it) } 956 } 957 958 fun deleteNonAdHocCallEventsOnOrBefore(timestamp: Long) { 959 val messageIdsOnOrBeforeTimestamp = """ 960 SELECT $MESSAGE_ID FROM $TABLE_NAME WHERE $TIMESTAMP <= $timestamp AND $MESSAGE_ID IS NOT NULL 961 """.trimIndent() 962 963 writableDatabase.withinTransaction { db -> 964 db.delete(MessageTable.TABLE_NAME) 965 .where("${MessageTable.ID} IN ($messageIdsOnOrBeforeTimestamp)") 966 .run() 967 968 updateCallEventDeletionTimestamps(skipSync = true) 969 } 970 } 971 972 fun deleteNonAdHocCallEvents(callRowIds: Set<Long>) { 973 val messageIds = getMessageIds(callRowIds) 974 SignalDatabase.messages.deleteCallUpdates(messageIds) 975 updateCallEventDeletionTimestamps() 976 } 977 978 fun deleteAllNonAdHocCallEventsExcept(callRowIds: Set<Long>, missedOnly: Boolean) { 979 val callFilter = if (missedOnly) { 980 "($EVENT = ${Event.serialize(Event.MISSED)} OR $EVENT = ${Event.serialize(Event.MISSED_NOTIFICATION_PROFILE)}) AND $DELETION_TIMESTAMP = 0" 981 } else { 982 "$DELETION_TIMESTAMP = 0" 983 } 984 985 if (callRowIds.isEmpty()) { 986 val threadIds = writableDatabase.withinTransaction { db -> 987 val ids = db.select(MessageTable.THREAD_ID) 988 .from(MessageTable.TABLE_NAME) 989 .where( 990 """ 991 ${MessageTable.ID} IN ( 992 SELECT $MESSAGE_ID FROM $TABLE_NAME 993 WHERE $callFilter 994 ) 995 """.toSingleLine() 996 ) 997 .run() 998 .readToList { it.requireLong(MessageTable.THREAD_ID) } 999 1000 db.delete(MessageTable.TABLE_NAME) 1001 .where( 1002 """ 1003 ${MessageTable.ID} IN ( 1004 SELECT $MESSAGE_ID FROM $TABLE_NAME 1005 WHERE $callFilter 1006 ) 1007 """.toSingleLine() 1008 ) 1009 .run() 1010 1011 ids.toSet() 1012 } 1013 1014 threadIds.forEach { 1015 SignalDatabase.threads.update( 1016 threadId = it, 1017 unarchive = false, 1018 allowDeletion = true 1019 ) 1020 } 1021 1022 notifyConversationListeners(threadIds) 1023 notifyConversationListListeners() 1024 updateCallEventDeletionTimestamps() 1025 } else { 1026 writableDatabase.withinTransaction { db -> 1027 SqlUtil.buildCollectionQuery( 1028 column = ID, 1029 values = callRowIds, 1030 prefix = "$callFilter AND", 1031 collectionOperator = SqlUtil.CollectionOperator.NOT_IN 1032 ).forEach { query -> 1033 val messageIds = db.select(MESSAGE_ID) 1034 .from(TABLE_NAME) 1035 .where(query.where, query.whereArgs) 1036 .run() 1037 .readToList { it.requireLong(MESSAGE_ID) } 1038 .toSet() 1039 SignalDatabase.messages.deleteCallUpdates(messageIds) 1040 updateCallEventDeletionTimestamps() 1041 } 1042 } 1043 } 1044 } 1045 1046 @Discouraged("Using this method is generally considered an error. Utilize other deletion methods instead of this.") 1047 fun deleteAllCalls() { 1048 Log.w(TAG, "Deleting all calls from the local database.") 1049 writableDatabase.deleteAll(TABLE_NAME) 1050 } 1051 1052 private fun getCallSelectionQuery(callId: Long, recipientId: RecipientId): SqlUtil.Query { 1053 return SqlUtil.Query("$CALL_ID = ? AND $PEER = ?", SqlUtil.buildArgs(callId, recipientId)) 1054 } 1055 1056 private fun getMessageIds(callRowIds: Set<Long>): Set<Long> { 1057 val queries = SqlUtil.buildCollectionQuery( 1058 ID, 1059 callRowIds, 1060 "$MESSAGE_ID NOT NULL AND" 1061 ) 1062 1063 return queries.map { query -> 1064 readableDatabase.select(MESSAGE_ID).from(TABLE_NAME).where(query.where, query.whereArgs).run().readToList { 1065 it.requireLong(MESSAGE_ID) 1066 } 1067 }.flatten().toSet() 1068 } 1069 1070 private fun checkIsGroupOrAdHocCall(call: Call) { 1071 check(call.type == Type.GROUP_CALL || call.type == Type.AD_HOC_CALL) 1072 } 1073 1074 // endregion 1075 1076 private fun getCallsCursor(isCount: Boolean, offset: Int, limit: Int, searchTerm: String?, filter: CallLogFilter): Cursor { 1077 val filterClause: SqlUtil.Query = when (filter) { 1078 CallLogFilter.ALL -> SqlUtil.buildQuery("$DELETION_TIMESTAMP = 0") 1079 CallLogFilter.MISSED -> SqlUtil.buildQuery("($EVENT = ${Event.serialize(Event.MISSED)} OR $EVENT = ${Event.serialize(Event.MISSED_NOTIFICATION_PROFILE)}) AND $DELETION_TIMESTAMP = 0") 1080 CallLogFilter.AD_HOC -> SqlUtil.buildQuery("$TYPE = ${Type.serialize(Type.AD_HOC_CALL)} AND $DELETION_TIMESTAMP = 0") 1081 } 1082 1083 val queryClause: SqlUtil.Query = if (!searchTerm.isNullOrEmpty()) { 1084 val glob = SqlUtil.buildCaseInsensitiveGlobPattern(searchTerm) 1085 val selection = 1086 """ 1087 ${RecipientTable.TABLE_NAME}.${RecipientTable.BLOCKED} = ? AND ${RecipientTable.TABLE_NAME}.${RecipientTable.HIDDEN} = ? AND 1088 ( 1089 sort_name GLOB ? OR 1090 ${RecipientTable.TABLE_NAME}.${RecipientTable.USERNAME} GLOB ? OR 1091 ${RecipientTable.TABLE_NAME}.${RecipientTable.E164} GLOB ? OR 1092 ${RecipientTable.TABLE_NAME}.${RecipientTable.EMAIL} GLOB ? 1093 ) 1094 """ 1095 SqlUtil.buildQuery(selection, 0, 0, glob, glob, glob, glob) 1096 } else { 1097 SqlUtil.buildQuery( 1098 """ 1099 ${RecipientTable.TABLE_NAME}.${RecipientTable.BLOCKED} = ? AND ${RecipientTable.TABLE_NAME}.${RecipientTable.HIDDEN} = ? 1100 """, 1101 0, 1102 0 1103 ) 1104 } 1105 1106 val offsetLimit = if (limit > 0) { 1107 "LIMIT $offset,$limit" 1108 } else { 1109 "" 1110 } 1111 1112 val projection = if (isCount) { 1113 "COUNT(*)," 1114 } else { 1115 "p.$ID, p.$TIMESTAMP, $EVENT, $DIRECTION, $PEER, p.$TYPE, $CALL_ID, $MESSAGE_ID, $RINGER, children, in_period, ${MessageTable.BODY}," 1116 } 1117 1118 val eventTypeSubQuery = """ 1119 ($TABLE_NAME.$EVENT = c.$EVENT AND ($TABLE_NAME.$EVENT = ${Event.serialize(Event.MISSED)} OR $TABLE_NAME.$EVENT = ${Event.serialize(Event.MISSED_NOTIFICATION_PROFILE)})) OR 1120 ( 1121 $TABLE_NAME.$EVENT != ${Event.serialize(Event.MISSED)} AND 1122 c.$EVENT != ${Event.serialize(Event.MISSED)} AND 1123 $TABLE_NAME.$EVENT != ${Event.serialize(Event.MISSED_NOTIFICATION_PROFILE)} AND 1124 c.$EVENT != ${Event.serialize(Event.MISSED_NOTIFICATION_PROFILE)} 1125 ) 1126 """ 1127 1128 //language=sql 1129 val statement = """ 1130 SELECT $projection 1131 LOWER( 1132 COALESCE( 1133 NULLIF(${GroupTable.TABLE_NAME}.${GroupTable.TITLE}, ''), 1134 NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.SYSTEM_JOINED_NAME}, ''), 1135 NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.SYSTEM_GIVEN_NAME}, ''), 1136 NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.PROFILE_JOINED_NAME}, ''), 1137 NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.PROFILE_GIVEN_NAME}, ''), 1138 NULLIF(${RecipientTable.TABLE_NAME}.${RecipientTable.USERNAME}, '') 1139 ) 1140 ) AS sort_name 1141 FROM ( 1142 WITH cte AS ( 1143 SELECT 1144 $ID, $TIMESTAMP, $EVENT, $DIRECTION, $PEER, $TYPE, $CALL_ID, $MESSAGE_ID, $RINGER, 1145 ( 1146 SELECT 1147 $ID 1148 FROM 1149 $TABLE_NAME 1150 WHERE 1151 $TABLE_NAME.$DIRECTION = c.$DIRECTION 1152 AND $TABLE_NAME.$PEER = c.$PEER 1153 AND $TABLE_NAME.$TIMESTAMP - $TIME_WINDOW <= c.$TIMESTAMP 1154 AND $TABLE_NAME.$TIMESTAMP >= c.$TIMESTAMP 1155 AND ($eventTypeSubQuery) 1156 AND ${filterClause.where} 1157 ORDER BY 1158 $TIMESTAMP DESC 1159 ) as parent, 1160 ( 1161 SELECT 1162 group_concat($ID) 1163 FROM 1164 $TABLE_NAME 1165 WHERE 1166 $TABLE_NAME.$DIRECTION = c.$DIRECTION 1167 AND $TABLE_NAME.$PEER = c.$PEER 1168 AND c.$TIMESTAMP - $TIME_WINDOW <= $TABLE_NAME.$TIMESTAMP 1169 AND c.$TIMESTAMP >= $TABLE_NAME.$TIMESTAMP 1170 AND ($eventTypeSubQuery) 1171 AND ${filterClause.where} 1172 ) as children, 1173 ( 1174 SELECT 1175 group_concat($ID) 1176 FROM 1177 $TABLE_NAME 1178 WHERE 1179 c.$TIMESTAMP - $TIME_WINDOW <= $TABLE_NAME.$TIMESTAMP 1180 AND c.$TIMESTAMP >= $TABLE_NAME.$TIMESTAMP 1181 AND ${filterClause.where} 1182 ) as in_period 1183 FROM 1184 $TABLE_NAME c 1185 WHERE ${filterClause.where} 1186 ORDER BY 1187 $TIMESTAMP DESC 1188 ) 1189 SELECT 1190 *, 1191 CASE 1192 WHEN LAG (parent, 1, 0) OVER ( 1193 ORDER BY 1194 $TIMESTAMP DESC 1195 ) != parent THEN $ID 1196 ELSE parent 1197 END true_parent 1198 FROM 1199 cte 1200 ) p 1201 INNER JOIN ${RecipientTable.TABLE_NAME} ON ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} = $PEER 1202 LEFT JOIN ${MessageTable.TABLE_NAME} ON ${MessageTable.TABLE_NAME}.${MessageTable.ID} = $MESSAGE_ID 1203 LEFT JOIN ${GroupTable.TABLE_NAME} ON ${GroupTable.TABLE_NAME}.${GroupTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} 1204 WHERE true_parent = p.$ID ${if (queryClause.where.isNotEmpty()) "AND ${queryClause.where}" else ""} 1205 ORDER BY p.$TIMESTAMP DESC 1206 $offsetLimit 1207 """ 1208 1209 return readableDatabase.query( 1210 statement, 1211 queryClause.whereArgs 1212 ) 1213 } 1214 1215 fun getLatestRingingCalls(): List<Call> { 1216 return readableDatabase.select() 1217 .from(TABLE_NAME) 1218 .where("$EVENT = ?", Event.serialize(Event.RINGING)) 1219 .limit(10) 1220 .orderBy(TIMESTAMP) 1221 .run() 1222 .readToList { 1223 Call.deserialize(it) 1224 } 1225 } 1226 1227 fun markRingingCallsAsMissed() { 1228 writableDatabase.update(TABLE_NAME) 1229 .values(EVENT to Event.serialize(Event.MISSED)) 1230 .where("$EVENT = ?", Event.serialize(Event.RINGING)) 1231 .run() 1232 } 1233 1234 fun getCallsCount(searchTerm: String?, filter: CallLogFilter): Int { 1235 return getCallsCursor(true, 0, 0, searchTerm, filter).use { 1236 it.moveToFirst() 1237 it.getInt(0) 1238 } 1239 } 1240 1241 fun getCalls(offset: Int, limit: Int, searchTerm: String?, filter: CallLogFilter): List<CallLogRow.Call> { 1242 return getCallsCursor(false, offset, limit, searchTerm, filter).readToList { cursor -> 1243 val call = Call.deserialize(cursor) 1244 val groupCallDetails = GroupCallUpdateDetailsUtil.parse(cursor.requireString(MessageTable.BODY)) 1245 1246 val children = cursor.requireNonNullString("children") 1247 .split(',') 1248 .map { it.toLong() } 1249 .toSet() 1250 1251 val inPeriod = cursor.requireNonNullString("in_period") 1252 .split(',') 1253 .map { it.toLong() } 1254 .sortedDescending() 1255 .toSet() 1256 1257 val actualChildren = inPeriod.takeWhile { children.contains(it) } 1258 val peer = Recipient.resolved(call.peer) 1259 1260 val canUserBeginCall = if (peer.isGroup) { 1261 val record = SignalDatabase.groups.getGroup(peer.id) 1262 1263 !record.isAbsent() && 1264 record.get().isActive && 1265 (!record.get().isAnnouncementGroup || record.get().memberLevel(Recipient.self()) == GroupTable.MemberLevel.ADMINISTRATOR) 1266 } else { 1267 true 1268 } 1269 1270 CallLogRow.Call( 1271 record = call, 1272 date = call.timestamp, 1273 peer = peer, 1274 groupCallState = CallLogRow.GroupCallState.fromDetails(groupCallDetails), 1275 children = actualChildren.toSet(), 1276 searchQuery = searchTerm, 1277 callLinkPeekInfo = ApplicationDependencies.getSignalCallManager().peekInfoSnapshot[peer.id], 1278 canUserBeginCall = canUserBeginCall 1279 ) 1280 } 1281 } 1282 1283 override fun remapRecipient(fromId: RecipientId, toId: RecipientId) { 1284 writableDatabase 1285 .update(TABLE_NAME) 1286 .values(PEER to toId.serialize()) 1287 .where("$PEER = ?", fromId) 1288 .run() 1289 } 1290 1291 data class Call( 1292 val callId: Long, 1293 val peer: RecipientId, 1294 val type: Type, 1295 val direction: Direction, 1296 val event: Event, 1297 val messageId: Long?, 1298 val timestamp: Long, 1299 val ringerRecipient: RecipientId? 1300 ) { 1301 val messageType: Long = getMessageType(type, direction, event) 1302 1303 companion object Deserializer : Serializer<Call, Cursor> { 1304 fun getMessageType(type: Type, direction: Direction, event: Event): Long { 1305 if (type == Type.GROUP_CALL || type == Type.AD_HOC_CALL) { 1306 return MessageTypes.GROUP_CALL_TYPE 1307 } 1308 1309 return if (direction == Direction.INCOMING && event.isMissedCall()) { 1310 if (type == Type.VIDEO_CALL) MessageTypes.MISSED_VIDEO_CALL_TYPE else MessageTypes.MISSED_AUDIO_CALL_TYPE 1311 } else if (direction == Direction.INCOMING) { 1312 if (type == Type.VIDEO_CALL) MessageTypes.INCOMING_VIDEO_CALL_TYPE else MessageTypes.INCOMING_AUDIO_CALL_TYPE 1313 } else { 1314 if (type == Type.VIDEO_CALL) MessageTypes.OUTGOING_VIDEO_CALL_TYPE else MessageTypes.OUTGOING_AUDIO_CALL_TYPE 1315 } 1316 } 1317 1318 override fun serialize(data: Call): Cursor { 1319 throw UnsupportedOperationException() 1320 } 1321 1322 override fun deserialize(data: Cursor): Call { 1323 return Call( 1324 callId = data.requireLong(CALL_ID), 1325 peer = RecipientId.from(data.requireLong(PEER)), 1326 type = data.requireObject(TYPE, Type.Serializer), 1327 direction = data.requireObject(DIRECTION, Direction.Serializer), 1328 event = data.requireObject(EVENT, Event.Serializer), 1329 messageId = data.requireLong(MESSAGE_ID).takeIf { it > 0L }, 1330 timestamp = data.requireLong(TIMESTAMP), 1331 ringerRecipient = data.requireLong(RINGER).let { 1332 if (it > 0) { 1333 RecipientId.from(it) 1334 } else { 1335 null 1336 } 1337 } 1338 ) 1339 } 1340 } 1341 } 1342 1343 enum class Type(private val code: Int) { 1344 AUDIO_CALL(0), 1345 VIDEO_CALL(1), 1346 GROUP_CALL(3), 1347 AD_HOC_CALL(4); 1348 1349 companion object Serializer : IntSerializer<Type> { 1350 override fun serialize(data: Type): Int = data.code 1351 1352 override fun deserialize(data: Int): Type { 1353 return when (data) { 1354 AUDIO_CALL.code -> AUDIO_CALL 1355 VIDEO_CALL.code -> VIDEO_CALL 1356 GROUP_CALL.code -> GROUP_CALL 1357 AD_HOC_CALL.code -> AD_HOC_CALL 1358 else -> throw IllegalArgumentException("Unknown type $data") 1359 } 1360 } 1361 1362 @JvmStatic 1363 fun from(type: CallEvent.Type?): Type? { 1364 return when (type) { 1365 null, CallEvent.Type.UNKNOWN_TYPE -> null 1366 CallEvent.Type.AUDIO_CALL -> AUDIO_CALL 1367 CallEvent.Type.VIDEO_CALL -> VIDEO_CALL 1368 CallEvent.Type.GROUP_CALL -> GROUP_CALL 1369 CallEvent.Type.AD_HOC_CALL -> AD_HOC_CALL 1370 } 1371 } 1372 } 1373 } 1374 1375 enum class Direction(private val code: Int) { 1376 INCOMING(0), 1377 OUTGOING(1); 1378 1379 companion object Serializer : IntSerializer<Direction> { 1380 override fun serialize(data: Direction): Int = data.code 1381 1382 override fun deserialize(data: Int): Direction { 1383 return when (data) { 1384 INCOMING.code -> INCOMING 1385 OUTGOING.code -> OUTGOING 1386 else -> throw IllegalArgumentException("Unknown type $data") 1387 } 1388 } 1389 1390 @JvmStatic 1391 fun from(direction: CallEvent.Direction?): Direction? { 1392 return when (direction) { 1393 null, CallEvent.Direction.UNKNOWN_DIRECTION -> null 1394 CallEvent.Direction.INCOMING -> INCOMING 1395 CallEvent.Direction.OUTGOING -> OUTGOING 1396 } 1397 } 1398 } 1399 } 1400 1401 enum class ReadState(private val code: Int) { 1402 UNREAD(0), 1403 READ(1); 1404 1405 companion object Serializer : IntSerializer<ReadState> { 1406 override fun serialize(data: ReadState): Int { 1407 return data.code 1408 } 1409 1410 override fun deserialize(data: Int): ReadState { 1411 return ReadState.values().first { it.code == data } 1412 } 1413 } 1414 } 1415 1416 enum class Event(private val code: Int) { 1417 /** 1418 * 1:1 Calls only. 1419 */ 1420 ONGOING(0), 1421 1422 /** 1423 * 1:1 and Group Calls. 1424 * 1425 * Group calls: You accepted a ring. 1426 */ 1427 ACCEPTED(1), 1428 1429 /** 1430 * 1:1 Calls only. 1431 */ 1432 NOT_ACCEPTED(2), 1433 1434 /** 1435 * 1:1 and Group/Ad-Hoc Calls. 1436 * 1437 * Group calls: The remote ring has expired or was cancelled by the ringer. 1438 */ 1439 MISSED(3), 1440 1441 /** 1442 * 1:1 and Group/Ad-Hoc Calls. 1443 * 1444 * Call was auto-declined due to a notification profile. 1445 */ 1446 MISSED_NOTIFICATION_PROFILE(10), 1447 1448 /** 1449 * 1:1 and Group/Ad-Hoc Calls. 1450 */ 1451 DELETE(4), 1452 1453 /** 1454 * Group/Ad-Hoc Calls only. 1455 * 1456 * Initial state. 1457 */ 1458 GENERIC_GROUP_CALL(5), 1459 1460 /** 1461 * Group Calls: User has joined the group call. 1462 */ 1463 JOINED(6), 1464 1465 /** 1466 * Group Calls: If a ring was requested by another user. 1467 */ 1468 RINGING(7), 1469 1470 /** 1471 * Group Calls: If you declined a ring. 1472 */ 1473 DECLINED(8), 1474 1475 /** 1476 * Group Calls: If you are ringing a group. 1477 */ 1478 OUTGOING_RING(9); // Next is 11 1479 1480 fun isMissedCall(): Boolean { 1481 return this == MISSED || this == MISSED_NOTIFICATION_PROFILE 1482 } 1483 1484 companion object Serializer : IntSerializer<Event> { 1485 override fun serialize(data: Event): Int = data.code 1486 1487 override fun deserialize(data: Int): Event { 1488 return values().firstOrNull { 1489 it.code == data 1490 } ?: throw IllegalArgumentException("Unknown event $data") 1491 } 1492 1493 @JvmStatic 1494 fun from(event: CallEvent.Event?): Event? { 1495 return when (event) { 1496 null, CallEvent.Event.UNKNOWN_ACTION -> null 1497 CallEvent.Event.ACCEPTED -> ACCEPTED 1498 CallEvent.Event.NOT_ACCEPTED -> NOT_ACCEPTED 1499 CallEvent.Event.DELETE -> DELETE 1500 } 1501 } 1502 } 1503 } 1504}