Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
Simple-Sms
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
zhangchengbo
Simple-Sms
Commits
7e41e05b
Commit
7e41e05b
authored
Nov 15, 2024
by
zhangchengbo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix:添加对系统通话记录查询类
parent
d2ab1d13
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
355 additions
and
0 deletions
+355
-0
RecentsHelper.kt
...src/main/kotlin/com/secspace/sms/helpers/RecentsHelper.kt
+314
-0
RecentCall.kt
app/src/main/kotlin/com/secspace/sms/models/RecentCall.kt
+41
-0
No files found.
app/src/main/kotlin/com/secspace/sms/helpers/RecentsHelper.kt
0 → 100644
View file @
7e41e05b
package
com.secspace.sms.helpers
import
android.annotation.SuppressLint
import
android.content.ContentValues
import
android.content.Context
import
android.provider.CallLog.Calls
import
com.secspace.sms.activities.SimpleActivity
import
com.secspace.sms.models.RecentCall
import
com.simplemobiletools.commons.extensions.*
import
com.simplemobiletools.commons.helpers.*
import
com.simplemobiletools.commons.models.contacts.Contact
//最近通话记录
class
RecentsHelper
(
private
val
context
:
Context
)
{
private
val
COMPARABLE_PHONE_NUMBER_LENGTH
=
9
private
val
QUERY_LIMIT
=
200
private
val
contentUri
=
Calls
.
CONTENT_URI
/**
* 获取通话记录
*/
fun
getRecentCalls
(
groupSubsequentCalls
:
Boolean
,
maxSize
:
Int
=
QUERY_LIMIT
,
callback
:
(
List
<
RecentCall
>)
->
Unit
)
{
val
privateCursor
=
context
.
getMyContactsCursor
(
favoritesOnly
=
false
,
withPhoneNumbersOnly
=
true
)
ensureBackgroundThread
{
if
(!
context
.
hasPermission
(
PERMISSION_READ_CALL_LOG
))
{
callback
(
ArrayList
())
return
@ensureBackgroundThread
}
ContactsHelper
(
context
).
getContacts
(
showOnlyContactsWithNumbers
=
true
)
{
contacts
:
java
.
util
.
ArrayList
<
Contact
>
->
val
privateContacts
=
MyContactsContentProvider
.
getContacts
(
context
,
privateCursor
)
if
(
privateContacts
.
isNotEmpty
())
{
contacts
.
addAll
(
privateContacts
)
}
getRecents
(
contacts
,
groupSubsequentCalls
,
maxSize
,
callback
=
callback
)
}
}
}
@SuppressLint
(
"NewApi"
)
private
fun
getRecents
(
contacts
:
List
<
Contact
>,
groupSubsequentCalls
:
Boolean
,
maxSize
:
Int
,
callback
:
(
List
<
RecentCall
>)
->
Unit
)
{
val
recentCalls
=
mutableListOf
<
RecentCall
>()
var
previousRecentCallFrom
=
""
var
previousStartTS
=
0
val
contactsNumbersMap
=
HashMap
<
String
,
String
>()
val
contactPhotosMap
=
HashMap
<
String
,
String
>()
val
projection
=
arrayOf
(
Calls
.
_ID
,
Calls
.
NUMBER
,
Calls
.
CACHED_NAME
,
Calls
.
CACHED_PHOTO_URI
,
Calls
.
DATE
,
Calls
.
DURATION
,
Calls
.
TYPE
,
Calls
.
PHONE_ACCOUNT_ID
,
Calls
.
COUNTRY_ISO
,
Calls
.
IS_READ
,
Calls
.
CACHED_MATCHED_NUMBER
)
/*val accountIdToSimIDMap = HashMap<String, Int>()
context.getAvailableSIMCardLabels().forEach {
accountIdToSimIDMap[it.handle.id] = it.id
}*/
val
cursor
=
if
(
isNougatPlus
())
{
// https://issuetracker.google.com/issues/175198972?pli=1#comment6
val
limitedUri
=
contentUri
.
buildUpon
()
.
appendQueryParameter
(
Calls
.
LIMIT_PARAM_KEY
,
QUERY_LIMIT
.
toString
())
.
build
()
val
sortOrder
=
"${Calls.DATE} DESC"
context
.
contentResolver
.
query
(
limitedUri
,
projection
,
null
,
null
,
sortOrder
)
}
else
{
val
sortOrder
=
"${Calls.DATE} DESC LIMIT $QUERY_LIMIT"
context
.
contentResolver
.
query
(
contentUri
,
projection
,
null
,
null
,
sortOrder
)
}
val
contactsWithMultipleNumbers
=
contacts
.
filter
{
it
.
phoneNumbers
.
size
>
1
}
val
numbersToContactIDMap
=
HashMap
<
String
,
Int
>()
contactsWithMultipleNumbers
.
forEach
{
contact
->
contact
.
phoneNumbers
.
forEach
{
phoneNumber
->
numbersToContactIDMap
[
phoneNumber
.
value
]
=
contact
.
contactId
numbersToContactIDMap
[
phoneNumber
.
normalizedNumber
]
=
contact
.
contactId
}
}
cursor
?.
use
{
if
(!
cursor
.
moveToFirst
())
{
return
@use
}
do
{
val
id
=
cursor
.
getIntValue
(
Calls
.
_ID
)
var
isUnknownNumber
=
false
val
number
=
cursor
.
getStringValueOrNull
(
Calls
.
NUMBER
)
if
(
number
==
null
||
number
==
"-1"
)
{
isUnknownNumber
=
true
}
var
name
=
cursor
.
getStringValueOrNull
(
Calls
.
CACHED_NAME
)
if
(
name
.
isNullOrEmpty
()
||
name
==
"-1"
)
{
name
=
number
.
orEmpty
()
}
if
(
name
==
number
&&
!
isUnknownNumber
)
{
if
(
contactsNumbersMap
.
containsKey
(
number
))
{
name
=
contactsNumbersMap
[
number
]
!!
}
else
{
val
normalizedNumber
=
number
.
normalizePhoneNumber
()
if
(
normalizedNumber
!!
.
length
>=
COMPARABLE_PHONE_NUMBER_LENGTH
)
{
name
=
contacts
.
filter
{
it
.
phoneNumbers
.
isNotEmpty
()
}.
firstOrNull
{
contact
->
val
curNumber
=
contact
.
phoneNumbers
.
first
().
normalizedNumber
if
(
curNumber
.
length
>=
COMPARABLE_PHONE_NUMBER_LENGTH
)
{
if
(
curNumber
.
substring
(
curNumber
.
length
-
COMPARABLE_PHONE_NUMBER_LENGTH
)
==
normalizedNumber
.
substring
(
normalizedNumber
.
length
-
COMPARABLE_PHONE_NUMBER_LENGTH
)
)
{
contactsNumbersMap
[
number
]
=
contact
.
getNameToDisplay
()
return
@firstOrNull
true
}
}
false
}
?.
name
?:
number
}
}
}
if
(
name
.
isEmpty
()
||
name
==
"-1"
)
{
name
=
/*context.getString(R.string.unknown)*/
""
}
var
photoUri
=
cursor
.
getStringValue
(
Calls
.
CACHED_PHOTO_URI
)
?:
""
if
(
photoUri
.
isEmpty
()
&&
!
number
.
isNullOrEmpty
())
{
if
(
contactPhotosMap
.
containsKey
(
number
))
{
photoUri
=
contactPhotosMap
[
number
]
!!
}
else
{
val
contact
=
contacts
.
firstOrNull
{
it
.
doesContainPhoneNumber
(
number
)
}
if
(
contact
!=
null
)
{
photoUri
=
contact
.
photoUri
contactPhotosMap
[
number
]
=
contact
.
photoUri
}
}
}
val
startTS
=
(
cursor
.
getLongValue
(
Calls
.
DATE
)
/
1000L
).
toInt
()
if
(
previousStartTS
==
startTS
)
{
continue
}
else
{
previousStartTS
=
startTS
}
val
duration
=
cursor
.
getIntValue
(
Calls
.
DURATION
)
val
type
=
cursor
.
getIntValue
(
Calls
.
TYPE
)
// val accountId = cursor.getStringValue(Calls.PHONE_ACCOUNT_ID)
val
simID
=
/*accountIdToSimIDMap[accountId] ?:*/
-
1
val
neighbourIDs
=
mutableListOf
<
Int
>()
var
specificNumber
=
""
var
specificType
=
""
val
sfFlag
=
cursor
.
getStringValue
(
Calls
.
COUNTRY_ISO
)
val
export
=
cursor
.
getStringValue
(
Calls
.
IS_READ
)
val
syncHideNumber
=
cursor
.
getStringValue
(
Calls
.
CACHED_MATCHED_NUMBER
)
val
contactIdWithMultipleNumbers
=
numbersToContactIDMap
[
number
]
if
(
contactIdWithMultipleNumbers
!=
null
)
{
val
specificPhoneNumber
=
contacts
.
firstOrNull
{
it
.
contactId
==
contactIdWithMultipleNumbers
}
?.
phoneNumbers
?.
firstOrNull
{
it
.
value
==
number
}
if
(
specificPhoneNumber
!=
null
)
{
specificNumber
=
specificPhoneNumber
.
value
specificType
=
context
.
getPhoneNumberTypeText
(
specificPhoneNumber
.
type
,
specificPhoneNumber
.
label
)
}
}
var
hideNumber
=
""
/* number?.let {
PhoneUtils.phoneNumberFormat(it) { number ->
hideNumber = if (sfFlag == Constant.SFSY) {
number
} else {
""
}
}
}*/
var
exportStr
=
false
export
?.
let
{
exportStr
=
it
==
Constant
.
EXPORT
}
var
syncNumber
=
false
syncHideNumber
?.
let
{
syncNumber
=
it
==
Constant
.
HIDE
}
val
recentCall
=
RecentCall
(
id
=
id
,
phoneNumber
=
number
.
orEmpty
(),
name
=
name
,
photoUri
=
photoUri
,
startTS
=
startTS
,
duration
=
duration
,
type
=
type
,
neighbourIDs
=
neighbourIDs
,
simID
=
simID
,
specificNumber
=
specificNumber
,
specificType
=
specificType
,
isUnknownNumber
=
isUnknownNumber
,
isShunFeng
=
sfFlag
==
Constant
.
SFSY
,
sfNumber
=
hideNumber
,
isExport
=
exportStr
,
isHidePhoneNumber
=
syncNumber
)
// if we have multiple missed calls from the same number, show it just once
if
(!
groupSubsequentCalls
||
"$number$name$simID"
!=
previousRecentCallFrom
)
{
recentCalls
.
add
(
recentCall
)
}
else
{
recentCalls
.
lastOrNull
()
?.
neighbourIDs
?.
add
(
id
)
}
// previousRecentCallFrom = "$number$name$simID"
}
while
(
cursor
.
moveToNext
()
&&
recentCalls
.
size
<
maxSize
)
}
val
blockedNumbers
=
context
.
getBlockedNumbers
()
val
recentResult
=
recentCalls
.
filter
{
!
context
.
isNumberBlocked
(
it
.
phoneNumber
,
blockedNumbers
)
}
callback
(
recentResult
)
}
/***
* 更改顺丰标志位
* 根据手机号将所有的改为掩码
*/
fun
updateRecent
(
id
:
Int
,
callback
:
()
->
Unit
=
{})
{
val
content
=
ContentValues
().
apply
{
put
(
Calls
.
COUNTRY_ISO
,
Constant
.
SFSY
)
}
val
updateResult
=
context
.
contentResolver
.
update
(
contentUri
,
content
,
"_id = $id"
,
null
)
if
(
updateResult
==
1
)
{
callback
.
invoke
()
}
}
/***
* 更改导出标志位
*/
fun
updateRecentIsExport
(
id
:
Int
)
{
val
content
=
ContentValues
().
apply
{
put
(
Calls
.
IS_READ
,
Constant
.
EXPORT
)
}
context
.
contentResolver
.
update
(
contentUri
,
content
,
"_id = $id"
,
null
)
}
/**
* 相同的号码进行同步掩码操作
*/
fun
updateSyncHideNumber
(
id
:
Int
)
{
val
content
=
ContentValues
().
apply
{
put
(
Calls
.
CACHED_MATCHED_NUMBER
,
Constant
.
HIDE
)
}
context
.
contentResolver
.
update
(
contentUri
,
content
,
"_id = $id"
,
null
)
}
fun
removeRecentCalls
(
ids
:
List
<
Int
>,
callback
:
()
->
Unit
)
{
ensureBackgroundThread
{
ids
.
chunked
(
30
).
forEach
{
chunk
->
val
selection
=
"${Calls._ID} IN (${getQuestionMarks(chunk.size)})"
val
selectionArgs
:
Array
<
String
>
=
chunk
.
map
{
it
.
toString
()
}.
toTypedArray
()
context
.
contentResolver
.
delete
(
contentUri
,
selection
,
selectionArgs
)
}
callback
.
invoke
()
}
}
@SuppressLint
(
"MissingPermission"
)
fun
removeAllRecentCalls
(
activity
:
SimpleActivity
,
callback
:
()
->
Unit
)
{
activity
.
handlePermission
(
PERMISSION_WRITE_CALL_LOG
)
{
if
(
it
)
{
ensureBackgroundThread
{
context
.
contentResolver
.
delete
(
contentUri
,
null
,
null
)
callback
()
}
}
}
}
fun
restoreRecentCalls
(
activity
:
SimpleActivity
,
objects
:
List
<
RecentCall
>,
callback
:
()
->
Unit
)
{
activity
.
handlePermission
(
PERMISSION_WRITE_CALL_LOG
)
{
if
(
it
)
{
ensureBackgroundThread
{
val
values
=
objects
.
sortedBy
{
it
.
startTS
}
.
map
{
ContentValues
().
apply
{
put
(
Calls
.
NUMBER
,
it
.
phoneNumber
)
put
(
Calls
.
TYPE
,
it
.
type
)
put
(
Calls
.
DATE
,
it
.
startTS
.
toLong
()
*
1000L
)
put
(
Calls
.
DURATION
,
it
.
duration
)
put
(
Calls
.
CACHED_NAME
,
it
.
name
)
}
}.
toTypedArray
()
context
.
contentResolver
.
bulkInsert
(
contentUri
,
values
)
callback
()
}
}
}
}
}
app/src/main/kotlin/com/secspace/sms/models/RecentCall.kt
0 → 100644
View file @
7e41e05b
package
com.secspace.sms.models
import
android.os.Parcelable
import
android.telephony.PhoneNumberUtils
import
androidx.annotation.Keep
import
com.simplemobiletools.commons.extensions.normalizePhoneNumber
import
kotlinx.parcelize.Parcelize
/**
* Used at displaying recent calls.
* For contacts with multiple numbers specify the number and type
*/
@Keep
@kotlinx
.
serialization
.
Serializable
@Parcelize
data class
RecentCall
(
val
id
:
Int
,
val
phoneNumber
:
String
,
val
name
:
String
,
val
photoUri
:
String
,
val
startTS
:
Int
,
val
duration
:
Int
,
val
type
:
Int
,
val
neighbourIDs
:
MutableList
<
Int
>,
val
simID
:
Int
,
val
specificNumber
:
String
,
val
specificType
:
String
,
val
isUnknownNumber
:
Boolean
,
var
isShunFeng
:
Boolean
,
var
sfNumber
:
String
,
var
isExport
:
Boolean
=
false
,
//是否已经导出过该条记录
var
isHidePhoneNumber
:
Boolean
=
false
//相同号码是否同步掩码
)
:
Parcelable
{
fun
doesContainPhoneNumber
(
text
:
String
):
Boolean
{
val
normalizedText
=
text
.
normalizePhoneNumber
()
return
PhoneNumberUtils
.
compare
(
phoneNumber
.
normalizePhoneNumber
(),
normalizedText
)
||
phoneNumber
.
contains
(
text
)
||
phoneNumber
.
normalizePhoneNumber
().
contains
(
normalizedText
)
||
phoneNumber
.
contains
(
normalizedText
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment