diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 20f2e1ce..e712a516 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,7 +40,7 @@ android:label="@string/app_name" android:theme="@style/Theme.LunarLauncher" android:excludeFromRecents="true" - android:clearTaskOnLaunch="true" + android:clearTaskOnLaunch="true" android:stateNotNeeded="true" android:enableOnBackInvokedCallback="true" android:largeHeap="true" @@ -52,7 +52,7 @@ @@ -113,6 +113,14 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> - + + + + + \ No newline at end of file diff --git a/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt b/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt index 8868d79e..f4c09aff 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt @@ -19,7 +19,9 @@ package rasel.lunar.launcher.home import android.annotation.SuppressLint +import android.content.BroadcastReceiver import android.content.ComponentName +import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.SharedPreferences @@ -31,15 +33,12 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.biometric.BiometricPrompt +import androidx.core.content.ContextCompat.RECEIVER_EXPORTED +import androidx.core.content.ContextCompat.registerReceiver import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager -import androidx.lifecycle.LiveData -import androidx.recyclerview.widget.LinearLayoutManager import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.OneTimeWorkRequest -import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkInfo import androidx.work.WorkManager import com.google.gson.Gson import rasel.lunar.launcher.LauncherActivity.Companion.lActivity @@ -84,6 +83,17 @@ internal class LauncherHome : Fragment() { var smsList = arrayListOf() } + private val nReceiver: NotificationReceiver? = null + + class NotificationReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent) { +// val temp = """ +// ${intent.getStringExtra("notification_event")} +//// ${txtView.getText()} +// """.trimIndent() +// txtView.setText(temp) + } + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = LauncherHomeBinding.inflate(inflater, container, false) @@ -100,16 +110,26 @@ internal class LauncherHome : Fragment() { mWorkManager?.enqueue(PeriodicWorkRequestBuilder(30, TimeUnit.SECONDS).addTag("RecentSmsGetter").build()) mWorkManager?.enqueueUniquePeriodicWork("MissedCallGetter",ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,PeriodicWorkRequestBuilder(30, TimeUnit.SECONDS).addTag("MissedCallGetter").build()) mWorkManager?.getWorkInfosByTagLiveData("RecentSmsGetter")?.observeForever { + binding.recentSms.text = "최근 문자 [${smsList.size}]" if (binding.recentSms.isChecked) chooseAdpater() + else if (missedCalls.size == 0) { + binding.recentSms.isChecked = true + chooseAdpater() + } BLog.LOGE("smsList.size >>> ${smsList.size}") - it.clear() + it.clear() } mWorkManager?.getWorkInfosByTagLiveData("MissedCallGetter")?.observeForever { + binding.missedCalls.text = "최근 통화 [${missedCalls.size}]" if (binding.missedCalls.isChecked) chooseAdpater() it.clear() } + val filter = IntentFilter() + filter.addAction("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE") + registerReceiver(requireContext(),nReceiver, filter,RECEIVER_EXPORTED) + BLog.LOGE("onCreateView()") return binding.root } @@ -178,6 +198,7 @@ internal class LauncherHome : Fragment() { try { BLog.LOGE("chooseAdpater smsList >>> ${smsList.size}") binding.mainList.adapter = mSmsLogsAdapter + smsList.sortByDescending { it.rcvDate } mSmsLogsAdapter.updateData(smsList) } catch (e : Exception) { diff --git a/app/src/main/kotlin/rasel/lunar/launcher/todos/SmsLogsAdapter.kt b/app/src/main/kotlin/rasel/lunar/launcher/todos/SmsLogsAdapter.kt index 2724c436..d33df81b 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/todos/SmsLogsAdapter.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/todos/SmsLogsAdapter.kt @@ -38,6 +38,7 @@ import rasel.lunar.launcher.helpers.UniUtils.Companion.copyToClipboard import rasel.lunar.launcher.home.MissedCall import rasel.lunar.launcher.utils.BLog import rasel.lunar.launcher.utils.RecentSmsLog +import java.text.SimpleDateFormat import java.util.* import kotlin.collections.ArrayList import kotlin.collections.List @@ -63,7 +64,8 @@ internal class SmsLogsAdapter( override fun onBindViewHolder(holder: SmsLogHolder, position: Int) { val todo = smsList[position] BLog.LOGE("SmsLogsAdapter smsList >>> ${smsList[position]}") - holder.view.itemText.text = "\u25CF ${todo.person} ${todo.addr} , ${todo.body} : ${todo.pstDate} : ${todo.type}" + + holder.view.itemText.text = "\u25CF ${todo.person} ${todo.addr} , ${todo.body} : ${SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Date(Math.max(todo.pstDate.toLong(),todo.rcvDate.toLong())))} : ${todo.type}" /* multiline texts are enabled for TodoManager */ // holder.view.itemText.isSingleLine = false diff --git a/app/src/main/kotlin/rasel/lunar/launcher/utils/DataManager.kt b/app/src/main/kotlin/rasel/lunar/launcher/utils/DataManager.kt index 693b2d60..6cac3a10 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/utils/DataManager.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/utils/DataManager.kt @@ -1,10 +1,12 @@ package rasel.lunar.launcher.utils import android.annotation.SuppressLint +import android.content.ContentResolver import android.content.Context +import android.database.Cursor +import android.net.Uri import android.provider.CallLog import android.provider.Telephony -import androidx.work.Data import androidx.work.Worker import androidx.work.WorkerParameters import androidx.work.workDataOf @@ -13,8 +15,12 @@ import rasel.lunar.launcher.LauncherActivity.Companion.lActivity import rasel.lunar.launcher.home.LauncherHome.Companion.missedCalls import rasel.lunar.launcher.home.LauncherHome.Companion.smsList import rasel.lunar.launcher.home.MissedCall +import java.io.BufferedReader +import java.io.InputStreamReader import java.text.SimpleDateFormat +import java.util.Calendar import java.util.Date +import kotlin.properties.Delegates class MissedCallGetter : Worker { @@ -24,7 +30,7 @@ class MissedCallGetter : Worker { @SuppressLint("RestrictedApi") override fun doWork(): Result { - var dateParam = Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24)).time.toString() + var dateParam = beforeDay(Date(),3).toString() var managedCursor =lActivity?.contentResolver?.query( CallLog.Calls.CONTENT_URI, arrayOf( CallLog.Calls.NUMBER, @@ -32,7 +38,9 @@ class MissedCallGetter : Worker { CallLog.Calls.DATE, CallLog.Calls.DURATION, CallLog.Calls.CACHED_NAME, - ), CallLog.Calls.DATE + "> ? AND " + CallLog.Calls.TYPE + " > ?", arrayOf(dateParam, "2"), CallLog.Calls.DATE + " desc") + ), CallLog.Calls.DATE + " >= ? " , arrayOf(dateParam), CallLog.Calls.DATE + " desc") + //+ CallLog.Calls.TYPE + " > ?" + //, "2" if(managedCursor != null && managedCursor.isClosed == false) { try { val number = managedCursor.getColumnIndex(CallLog.Calls.NUMBER) @@ -94,6 +102,7 @@ class MissedCallGetter : Worker { SimpleDateFormat("yyy/MM/dd-HH:mm:ss").format(callDayTime) ) } + BLog.LOGE("missed put >>> ${missed.toJson()}") missedCalls.put(phNumber, missed) } @@ -111,6 +120,12 @@ class MissedCallGetter : Worker { } +fun beforeDay(date: Date?, day: Int): Long { + val cal: Calendar = Calendar.getInstance() + cal.setTime(date) + cal.add(Calendar.DAY_OF_YEAR, Math.abs(day) * -1) + return cal.timeInMillis +} class RecentSmsGetter : Worker { @@ -120,11 +135,11 @@ class RecentSmsGetter : Worker { + @SuppressLint("RestrictedApi") override fun doWork(): Result { BLog.LOGE("phNumber == onStart") - var resultData = workDataOf("" to "") - var dateParam = Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24 * 3)).time.toString() + var dateParam = beforeDay(Date(),3).toString() val managedCursor = lActivity?.contentResolver?.query( Telephony.Sms.CONTENT_URI, arrayOf( Telephony.Sms.ADDRESS, @@ -133,9 +148,10 @@ class RecentSmsGetter : Worker { Telephony.Sms.DATE_SENT, Telephony.Sms.BODY, Telephony.Sms.PERSON, - ), Telephony.Sms.DATE + ">= ? OR " + Telephony.Sms.DATE_SENT + " >= ?", arrayOf(dateParam, dateParam), Telephony.Sms.DEFAULT_SORT_ORDER) + ), Telephony.Sms.DATE + "> ${dateParam}", null, Telephony.Sms.DEFAULT_SORT_ORDER) if (managedCursor != null && managedCursor.isClosed == false) { try { + smsList.clear() val address = managedCursor.getColumnIndex(Telephony.Sms.ADDRESS) val type = managedCursor.getColumnIndex(Telephony.Sms.TYPE) val date = managedCursor.getColumnIndex(Telephony.Sms.DATE) @@ -179,16 +195,20 @@ class RecentSmsGetter : Worker { managedCursor.close() } } + if (lActivity?.contentResolver != null) { + TestQueryHelper(lActivity?.contentResolver!!).query() + } return Result.success() } } class RecentSmsLog { + var id : String = "" var addr : String = "" var type : String = "" - var rcvDate : String = "" - var pstDate : String = "" + var rcvDate : String = "0" + var pstDate : String = "0" var body : String = "" var person : String = "" @@ -208,7 +228,118 @@ class RecentSmsLog { this.person = person } + constructor(id: String, sender: String, date: String, body: String) { + this.id = id + this.rcvDate = date + this.body = body + this.addr = sender + } + fun toJson() : String { return Gson().toJson(this) } } + +class TestQueryHelper( + private val _contentResolver: ContentResolver +) { + + private val mmsUri: Uri = Telephony.Mms.CONTENT_URI + private val cursor: Cursor? + get() = + _contentResolver.query( + mmsUri, + null, + null, + null, + null + ) + + fun dataMapper(cursor: Cursor): RecentSmsLog { + val id = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Mms._ID)) + val date = cursor.getLong(cursor.getColumnIndexOrThrow(Telephony.Mms.DATE)) * 1000 + val sender : String = getSender(id) ?: throw NullPointerException("NotFound Sender For Mms") + val body: String = getBody(id) ?: throw NullPointerException("NotFound Body by Mms") + + return RecentSmsLog( + id = id.toString(), + date = date.toString(), + body = body, + sender = sender + ) + } + + private fun getSender(id: Int): String? { + _contentResolver.query( + Uri.parse("content://mms/$id/addr"), + null, + "${Telephony.Mms.Addr.MSG_ID} = ?", + arrayOf(id.toString()), + null + )?.use { senderAddressCursor -> + if (senderAddressCursor.moveToFirst()) { + do { + // 137 수신, 151 발신 타입 값이다. Telephony.Mms.Addr.Type 의 주석 참고. + val isSender = senderAddressCursor.getInt( + senderAddressCursor.getColumnIndexOrThrow(Telephony.Mms.Addr.TYPE) + ) == 137 + var sender = senderAddressCursor.getString( + senderAddressCursor.getColumnIndexOrThrow(Telephony.Mms.Addr.ADDRESS) + ) + BLog.LOGE("sender >> ${sender}") + if (isSender) { + return sender + } + } while (senderAddressCursor.moveToNext()) + } + } + return null + } + + private fun getBody(id: Int): String? { + _contentResolver.query( + Uri.parse("content://mms/part"), + null, + "${Telephony.Mms.Part.MSG_ID} = ?", + arrayOf(id.toString()), + null + )?.use { partsCursor -> + if (partsCursor.moveToFirst()) { + do { + val partContentType = + partsCursor.getString(partsCursor.getColumnIndexOrThrow(Telephony.Mms.Part.CONTENT_TYPE)) + if (partContentType == "text/plain") { + var textBody = + partsCursor.getString(partsCursor.getColumnIndexOrThrow(Telephony.Mms.Part.TEXT)) + .replace("\n\n", "\n").replace("\n", " ") + if (textBody.length > 60) { + textBody = textBody.substring(0, Math.min(textBody.length, 60)).plus(" ... ") + } + BLog.LOGE("textBody >>> ${textBody}") + + return textBody + } + } while (partsCursor.moveToNext()) + } + } + return null + } + + fun convertData(cursor: Cursor?) { + cursor ?: return + cursor.use { + if (cursor.moveToFirst()) { + do { + val data = kotlin.runCatching { + dataMapper(cursor) + }.getOrNull() + data?.let { smsList.add(it) } + } while (cursor.moveToNext()) + } + } + } + fun query() { + return convertData(cursor) + } + +} \ No newline at end of file diff --git a/app/src/main/kotlin/rasel/lunar/launcher/utils/PLService.kt b/app/src/main/kotlin/rasel/lunar/launcher/utils/PLService.kt new file mode 100644 index 00000000..c268491c --- /dev/null +++ b/app/src/main/kotlin/rasel/lunar/launcher/utils/PLService.kt @@ -0,0 +1,67 @@ +package rasel.lunar.launcher.utils + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.service.notification.NotificationListenerService +import android.service.notification.StatusBarNotification +import android.util.Log + + +class NLService : NotificationListenerService() { + private val TAG: String = this.javaClass.simpleName + private var nlservicereciver: NLServiceReceiver? = null + + override fun onCreate() { + super.onCreate() + nlservicereciver = NLServiceReceiver() + val filter = IntentFilter() + filter.addAction("com.kpbird.nlsexample.NOTIFICATION_LISTENER_SERVICE_EXAMPLE") + registerReceiver(nlservicereciver, filter) + } + + override fun onDestroy() { + super.onDestroy() + unregisterReceiver(nlservicereciver) + } + + override fun onNotificationPosted(sbn: StatusBarNotification) { + Log.i(TAG, "********** onNotificationPosted") + Log.i(TAG, "ID :" + sbn.id + "\t" + sbn.notification.tickerText + "\t" + sbn.packageName) + val i = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE") + i.putExtra("notification_event", "onNotificationPosted :" + sbn.packageName + "\n") + sendBroadcast(i) + } + + override fun onNotificationRemoved(sbn: StatusBarNotification) { + Log.i(TAG, "********** onNOtificationRemoved") + Log.i(TAG, "ID :" + sbn.id + "\t" + sbn.notification.tickerText + "\t" + sbn.packageName) + val i = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE") + i.putExtra("notification_event", "onNotificationRemoved :" + sbn.packageName + "\n") + + sendBroadcast(i) + } + + internal inner class NLServiceReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent) { + if (intent.getStringExtra("command") == "clearall") { + this@NLService.cancelAllNotifications() + } else if (intent.getStringExtra("command") == "list") { + val i1 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE") + i1.putExtra("notification_event", "=====================") + sendBroadcast(i1) + var i = 1 + for (sbn in this@NLService.activeNotifications) { + val i2 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE") + i2.putExtra("notification_event", i.toString() + " " + sbn.packageName + "\n") + sendBroadcast(i2) + i++ + } + val i3 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE") + i3.putExtra("notification_event", "===== Notification List ====") + sendBroadcast(i3) + } + } + } +} \ No newline at end of file