...
This commit is contained in:
@@ -10,6 +10,7 @@ import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
@@ -35,6 +36,7 @@ import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
@@ -54,13 +56,11 @@ import bums.lunatic.launcher.home.NeoRssActivity
|
||||
import bums.lunatic.launcher.home.RssHome
|
||||
import bums.lunatic.launcher.model.WidgetData
|
||||
import bums.lunatic.launcher.receiver.NLService
|
||||
import bums.lunatic.launcher.receiver.SmsReceiver
|
||||
import bums.lunatic.launcher.settings.SettingsActivity
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import bums.lunatic.launcher.workers.WorkersDb.syncSystemUsageStats
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.yausername.ffmpeg.FFmpeg
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import com.yausername.youtubedl_android.YoutubeDLException
|
||||
import io.realm.kotlin.UpdatePolicy
|
||||
import io.realm.kotlin.ext.query
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -135,10 +135,37 @@ open class LauncherActivity : CommonActivity() {
|
||||
|
||||
// 4. 롱프레스 감지 (빈 공간)
|
||||
override fun onLongPress(e: MotionEvent) {
|
||||
// 위젯 추가 메뉴 등을 띄우려면 여기서 처리
|
||||
// 주의: 위젯 위에서 롱프레스하면 위젯 드래그가 먼저 작동하도록 설계해야 함
|
||||
// showToast("바탕화면 롱프레스: 위젯 추가")
|
||||
selectWidget() // 기존에 만든 위젯 추가 함수 호출
|
||||
// 1. 기기 화면의 전체 높이 가져오기 (Resources 사용)
|
||||
val screenHeight = resources.displayMetrics.heightPixels
|
||||
|
||||
// 2. 롱프레스가 발생한 절대 Y 좌표
|
||||
val touchY = e.rawY
|
||||
|
||||
// 3. 높이에 따른 분기 처리
|
||||
when {
|
||||
touchY < screenHeight / 3 -> {
|
||||
// 상단 롱프레스 (0 ~ 33%)
|
||||
handleTopLongPress()
|
||||
}
|
||||
touchY < (screenHeight / 3) * 2 -> {
|
||||
// 중단 롱프레스 (33% ~ 66%)
|
||||
selectWidget() // 기존 함수
|
||||
}
|
||||
else -> {
|
||||
// 하단 롱프레스 (66% ~ 100%)
|
||||
handleBottomLongPress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 각 구간별로 실행할 함수들 (예시)
|
||||
private fun handleTopLongPress() {
|
||||
// 상단 전용 기능
|
||||
}
|
||||
|
||||
private fun handleBottomLongPress() {
|
||||
// 하단 전용 기능
|
||||
startActivity(Intent(this@LauncherActivity, SettingsActivity::class.java))
|
||||
}
|
||||
|
||||
// onDown은 true를 반환해야 다른 제스처들이 시작됨
|
||||
@@ -406,10 +433,10 @@ open class LauncherActivity : CommonActivity() {
|
||||
restoreWidgets()
|
||||
|
||||
// 3. 바탕화면 롱클릭 시 위젯 추가 메뉴 띄우기 (예시)
|
||||
binding.widgetContainer.setOnLongClickListener {
|
||||
selectWidget()
|
||||
true
|
||||
}
|
||||
// binding.widgetContainer.setOnLongClickListener {
|
||||
// selectWidget()
|
||||
// true
|
||||
// }
|
||||
scaleDetector = android.view.ScaleGestureDetector(this, object : android.view.ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
override fun onScale(detector: android.view.ScaleGestureDetector): Boolean {
|
||||
currentDragView?.let { view ->
|
||||
@@ -433,6 +460,41 @@ open class LauncherActivity : CommonActivity() {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
requestSmsPermissionLauncher.launch(arrayOf(
|
||||
android.Manifest.permission.RECEIVE_SMS,
|
||||
android.Manifest.permission.READ_SMS
|
||||
))
|
||||
}
|
||||
|
||||
private var smsReceiver: SmsReceiver? = null
|
||||
|
||||
// 권한 요청 결과 처리기
|
||||
private val requestSmsPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions ->
|
||||
val granted = permissions[android.Manifest.permission.RECEIVE_SMS] ?: false
|
||||
if (granted) {
|
||||
Blog.LOGE("SMS 권한 승인됨 -> 리시버 동적 등록 실행")
|
||||
registerSmsDynamicReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerSmsDynamicReceiver() {
|
||||
if (smsReceiver == null) {
|
||||
smsReceiver = SmsReceiver()
|
||||
val filter = IntentFilter("android.provider.Telephony.SMS_RECEIVED").apply {
|
||||
priority = 2147483647 // 시스템 최우선 순위
|
||||
}
|
||||
|
||||
// Android 14 이상(안드 16 포함) 필수 플래그: RECEIVER_EXPORTED
|
||||
// 외부 앱(시스템 타워)으로부터 브로드캐스트를 받으려면 필수입니다.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(smsReceiver, filter, Context.RECEIVER_EXPORTED)
|
||||
} else {
|
||||
registerReceiver(smsReceiver, filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWidgetOptions(appWidgetId: Int, hostView: AppWidgetHostView) {
|
||||
@@ -732,10 +794,14 @@ open class LauncherActivity : CommonActivity() {
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
appWidgetHost?.stopListening() // [필수] 여기서 리스닝 중지 (onDestroy 대신 여기 추천)
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
smsReceiver?.let {
|
||||
unregisterReceiver(it)
|
||||
smsReceiver = null
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
|
||||
val pm = requireContext().packageManager
|
||||
|
||||
// 1. [추천 로직] 에러가 나도 앱 목록 로딩은 진행되도록 try-catch 분리
|
||||
val scoredItems = WorkersDb.getContextualRecommendations(limit = 8)
|
||||
val scoredItems = WorkersDb.getContextualRecommendations(limit = 18)
|
||||
|
||||
val unifiedList = mutableListOf<RecommendationItem>()
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package bums.lunatic.launcher.apps;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64; // Java 기본 Base64 사용
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
@@ -505,9 +505,7 @@ open class NeoRssActivity : CommonActivity() {
|
||||
.replace(R.id.fragment_container, BookmarkPagerFragment())
|
||||
.commit()
|
||||
}
|
||||
R.id.setting ->{
|
||||
startActivity(Intent(this, SettingsActivity::class.java))
|
||||
}
|
||||
|
||||
R.id.close ->{
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container)?.let {
|
||||
supportFragmentManager.beginTransaction()
|
||||
|
||||
@@ -27,4 +27,16 @@ class SimpleContact : RealmObject {
|
||||
|
||||
constructor()
|
||||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
id : $id
|
||||
name : $name
|
||||
chosung : $chosung
|
||||
phoneNumber : $phoneNumber
|
||||
touchCount : $touchCount
|
||||
lastedTouchDateTime : $lastedTouchDateTime
|
||||
visibilityMode : $visibilityMode
|
||||
blockRecommend : $blockRecommend
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package bums.lunatic.launcher.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.telephony.SmsMessage
|
||||
import android.telephony.TelephonyManager
|
||||
import android.widget.Toast
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.workers.UsageUpdateType
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
|
||||
// 1. 전화 감지 리시버
|
||||
class CallReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == TelephonyManager.ACTION_PHONE_STATE_CHANGED) {
|
||||
val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
|
||||
val number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)
|
||||
|
||||
// 전화 벨이 울릴 때 (수신)
|
||||
if (state == TelephonyManager.EXTRA_STATE_RINGING && number != null) {
|
||||
WorkersDb.logContactInteraction(number, UsageUpdateType.CALL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 문자 감지 리시버
|
||||
class SmsReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
// 1. 액션 수신 확인 로그
|
||||
Blog.LOGE("SMS_RECEIVED 브로드캐스트 수신됨: ${intent.action}")
|
||||
Toast.makeText(context, "문자 수신 신호 감지!", Toast.LENGTH_SHORT).show()
|
||||
Blog.LOGE("Action: ${intent.action}")
|
||||
|
||||
if (intent.action == "android.provider.Telephony.SMS_RECEIVED") {
|
||||
val bundle = intent.extras
|
||||
val pdus = bundle?.get("pdus") as? Array<*>
|
||||
// format이 null일 경우를 대비해 기본값 "3gpp"(GSM) 또는 "3gpp2"(CDMA) 처리
|
||||
val format = bundle?.getString("format") ?: "3gpp"
|
||||
|
||||
if (pdus != null) {
|
||||
for (pdu in pdus) {
|
||||
try {
|
||||
val sms = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
SmsMessage.createFromPdu(pdu as ByteArray, format)
|
||||
} else {
|
||||
SmsMessage.createFromPdu(pdu as ByteArray)
|
||||
}
|
||||
|
||||
val senderNumber = sms.originatingAddress
|
||||
Blog.LOGE("수신된 번호: $senderNumber")
|
||||
|
||||
if (senderNumber != null) {
|
||||
WorkersDb.logContactInteraction(senderNumber, UsageUpdateType.SMS)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Blog.LOGE("SMS 파싱 오류: ${e.message}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Blog.LOGE("SMS 데이터(pdus)가 null입니다.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import android.content.IntentFilter
|
||||
import android.location.Geocoder
|
||||
import android.location.Location
|
||||
import android.os.Build
|
||||
import android.provider.ContactsContract
|
||||
import android.service.notification.NotificationListenerService
|
||||
import android.service.notification.StatusBarNotification
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -21,6 +22,8 @@ import bums.lunatic.launcher.helpers.ForeGroundService.Companion.EXTRA_MSGKEY
|
||||
import bums.lunatic.launcher.helpers.PrefBoolean
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.utils.KakaoPublicTransfer
|
||||
import bums.lunatic.launcher.workers.UsageUpdateType
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
@@ -69,10 +72,33 @@ class NLService : NotificationListenerService() {
|
||||
stringBuffer.append(conversationTitle).append("\n")
|
||||
stringBuffer.append(summaryText).append("\n")
|
||||
stringBuffer.append(verificationText).append("\n")
|
||||
// Blog.LOGE("title >> ${title} text >> ${text} bigText >> ${bigText} extraInfo >> ${extraInfo} subText >> ${subText} conversationTitle >> ${conversationTitle} summaryText >> ${summaryText} verificationText >> ${verificationText}")
|
||||
mHourlyLogWriter?.writeLog("${sbn.packageName}\n${stringBuffer.toString()}")
|
||||
|
||||
if (sbn.packageName == "com.samsung.android.messaging" || sbn.packageName == "com.google.android.apps.messaging") {
|
||||
val extras = sbn.notification.extras
|
||||
val title = extras.getString(Notification.EXTRA_TITLE) ?: return
|
||||
val cleanTitle = sanitizeIdentifier(title)
|
||||
if (cleanTitle.isEmpty()) return
|
||||
// 1. 제목이 숫자인지 확인 (정규식: 숫자, +, - 만 포함된 경우)
|
||||
val isNumber = cleanTitle.matches(Regex("^[0-9+\\- ]+$"))
|
||||
|
||||
if (isNumber) {
|
||||
// 번호가 바로 왔을 때
|
||||
WorkersDb.logContactInteraction(cleanTitle, UsageUpdateType.SMS)
|
||||
} else {
|
||||
// 이름이 왔을 때 -> 주소록에서 번호 조회
|
||||
val foundNumber = getPhoneNumberByName(applicationContext, cleanTitle)
|
||||
if (foundNumber != null) {
|
||||
Blog.LOGE("이름($title)으로 번호($foundNumber) 조회 성공")
|
||||
WorkersDb.logContactInteraction(foundNumber, UsageUpdateType.SMS)
|
||||
} else {
|
||||
// 주소록에도 없는 이름일 경우 (이름 자체로 로그를 남기려면 WorkersDb 수정 필요)
|
||||
Blog.LOGE("주소록에서 찾을 수 없는 이름: $title")
|
||||
}
|
||||
}
|
||||
}
|
||||
when (sbn.packageName) {
|
||||
"com.kakao.taxi" -> {
|
||||
Blog.LOGE("packageName ${sbn.packageName} :::: title >> ${title} text >> ${text} bigText >> ${bigText} extraInfo >> ${extraInfo} subText >> ${subText} conversationTitle >> ${conversationTitle} summaryText >> ${summaryText} verificationText >> ${verificationText}")
|
||||
var defaultMsg: StringBuffer? = StringBuffer("돼지 택시 ")
|
||||
if (stringBuffer.contains("택시") && stringBuffer.contains("탑승") && stringBuffer.contains(
|
||||
"완료"
|
||||
@@ -128,6 +154,35 @@ class NLService : NotificationListenerService() {
|
||||
}
|
||||
}
|
||||
|
||||
fun sanitizeIdentifier(input: String): String {
|
||||
// 1. 한글(가-힣), 초성(ㄱ-ㅎ), 숫자(0-9), 영문(a-zA-Z)만 남기고 모두 제거
|
||||
val regex = Regex("[^가-힣ㄱ-ㅎ0-9a-zA-Z]")
|
||||
val cleaned = input.replace(regex, "").trim()
|
||||
|
||||
Blog.LOGE("정제 전: [$input] -> 정제 후: [$cleaned]")
|
||||
return cleaned
|
||||
}
|
||||
|
||||
fun getPhoneNumberByName(context: Context, name: String): String? {
|
||||
val contentResolver = context.contentResolver
|
||||
|
||||
// 이름의 일부라도 포함되어 있는지 검색 (%name%)
|
||||
val cursor = contentResolver.query(
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
||||
arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER),
|
||||
"${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} LIKE ?",
|
||||
arrayOf("%$name%"),
|
||||
null
|
||||
)
|
||||
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
return it.getString(0).replace(Regex("[^0-9]"), "")
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
fun makeMsgByTransferInfomation(stringBuffer : StringBuffer) {
|
||||
val actionIntent = Intent(this, ForeGroundService::class.java).apply {
|
||||
|
||||
@@ -62,7 +62,7 @@ class CustMigration : AutomaticSchemaMigration {
|
||||
|
||||
data class ScoredItem(val key: String, val type: String, val score: Double)
|
||||
enum class UsageLogType { APP, CONTACT };
|
||||
enum class UsageUpdateType { JC, COUNT, DATETIME };
|
||||
enum class UsageUpdateType { JC, COUNT, DATETIME,CALL, SMS };
|
||||
object WorkersDb {
|
||||
|
||||
//RecentCall::class, RecentSms::class,
|
||||
@@ -141,8 +141,51 @@ object WorkersDb {
|
||||
}
|
||||
}
|
||||
|
||||
fun logContactInteraction(phoneNumber: String, updateType: UsageUpdateType) {
|
||||
val realm = getRealm()
|
||||
val calendar = Calendar.getInstance()
|
||||
|
||||
// 전화번호에서 하이픈 제거 등 포맷 정리 (DB 저장 형식에 맞춤)
|
||||
val normalizedNumber = phoneNumber.replace("-", "").replace(" ", "")
|
||||
|
||||
realm.writeBlocking {
|
||||
// 1. 전화번호로 해당 연락처 찾기 (SimpleContact에 phone 필드가 있다고 가정)
|
||||
val contact = query<SimpleContact>("phoneNumber == $0", normalizedNumber).first().find()
|
||||
|
||||
if (contact != null) {
|
||||
val key = contact.id // 추천 시스템에서 사용하는 키값
|
||||
Blog.LOGE("contact >>> ${contact}")
|
||||
when (updateType) {
|
||||
UsageUpdateType.CALL -> {
|
||||
contact.touchCount += 20 // 통화는 가중치를 더 높게 설정
|
||||
contact.lastedTouchDateTime = System.currentTimeMillis()
|
||||
}
|
||||
UsageUpdateType.SMS -> {
|
||||
contact.touchCount += 10 // 문자는 중간 정도
|
||||
contact.lastedTouchDateTime = System.currentTimeMillis()
|
||||
}
|
||||
else -> {
|
||||
contact.touchCount += 1
|
||||
}
|
||||
}
|
||||
key?.let { key ->
|
||||
// 2. 추천 시스템이 사용하는 AppUsageLog에도 기록 추가
|
||||
copyToRealm(AppUsageLog().apply {
|
||||
itemKey = key
|
||||
itemType = UsageLogType.CONTACT.name
|
||||
timestamp = System.currentTimeMillis()
|
||||
month = calendar.get(Calendar.MONTH)
|
||||
dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH)
|
||||
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)
|
||||
hour = calendar.get(Calendar.HOUR_OF_DAY)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [핵심] 현재 시간 상황에 맞는 추천 리스트 가져오기
|
||||
fun getContextualRecommendations(limit: Int = 10): List<ScoredItem> {
|
||||
fun getContextualRecommendations(limit: Int = 18): List<ScoredItem> {
|
||||
val realm = getRealm()
|
||||
val calendar = Calendar.getInstance()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user