xiongzhu 1 tahun lalu
induk
melakukan
8003cefde1

+ 3 - 0
app/build.gradle

@@ -145,4 +145,7 @@ dependencies {
     implementation 'org.apache.commons:commons-lang3:3.14.0'
     implementation 'commons-io:commons-io:2.16.1'
     implementation 'org.slf4j:slf4j-android:1.7.36'
+
+    implementation("io.coil-kt.coil3:coil-compose:3.0.2")
+    implementation("io.coil-kt.coil3:coil-network-ktor2:3.0.2")
 }

+ 11 - 1
app/src/main/java/com/example/modifier/MyApplication.kt

@@ -1,7 +1,11 @@
 package com.example.modifier
 
 import android.app.Application
+import android.content.Context
 import android.util.Log
+import coil3.ImageLoader
+import coil3.SingletonImageLoader
+import coil3.request.crossfade
 import com.example.modifier.data.AppContainer
 import com.example.modifier.data.AppDataContainer
 import com.example.modifier.repo.AppPrefsRepo
@@ -10,7 +14,7 @@ import dagger.hilt.android.HiltAndroidApp
 const val baseTag = "Modifier"
 
 @HiltAndroidApp
-class MyApplication : Application() {
+class MyApplication : Application(), SingletonImageLoader.Factory {
     companion object {
         private const val TAG = "$baseTag/MyApplication"
     }
@@ -24,4 +28,10 @@ class MyApplication : Application() {
 
         Log.i(TAG, "server=${AppPrefsRepo.instance.appPrefs.value.server}")
     }
+
+    override fun newImageLoader(context: Context): ImageLoader {
+        return ImageLoader.Builder(context)
+            .crossfade(true)
+            .build()
+    }
 }

+ 38 - 0
app/src/main/java/com/example/modifier/http/KtorClient.kt

@@ -1,7 +1,9 @@
 package com.example.modifier.http
 
+import androidx.core.content.ContextCompat
 import com.example.modifier.http.response.ErrorResponse
 import com.example.modifier.repo.AppPrefsRepo
+import com.example.modifier.utils.getContext
 import io.ktor.client.HttpClient
 import io.ktor.client.call.body
 import io.ktor.client.engine.okhttp.OkHttp
@@ -18,11 +20,21 @@ import io.ktor.client.plugins.defaultRequest
 import io.ktor.client.plugins.logging.LogLevel
 import io.ktor.client.plugins.logging.Logging
 import io.ktor.client.plugins.resources.Resources
+import io.ktor.client.request.prepareGet
 import io.ktor.client.utils.unwrapCancellationException
+import io.ktor.http.contentLength
 import io.ktor.serialization.kotlinx.json.json
+import io.ktor.utils.io.ByteReadChannel
+import io.ktor.utils.io.core.isEmpty
+import io.ktor.utils.io.core.readBytes
 import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
 import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.json.Json
+import org.apache.commons.io.FileUtils
+import org.apache.commons.lang3.RandomStringUtils
+import java.io.File
 
 fun Throwable.isTimeoutException(): Boolean {
     val exception = unwrapCancellationException()
@@ -85,4 +97,30 @@ var ktorClient = HttpClient(OkHttp) {
             }
         }
     }
+}
+
+val fileCaching = mutableMapOf<String, String>()
+suspend fun downloadImage(url: String): File {
+    fileCaching[url]?.let {
+        val file = File(it)
+        if (file.exists()) {
+            return file
+        }
+    }
+    val imageDir = ContextCompat.getExternalFilesDirs(getContext(), "images")[0]
+    val file =
+        File(imageDir, RandomStringUtils.randomAlphanumeric(32) + "." + url.substringAfterLast("."))
+    ktorClient.prepareGet(url)
+        .execute { httpResponse ->
+            val channel: ByteReadChannel = httpResponse.body()
+            while (!channel.isClosedForRead) {
+                val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
+                while (!packet.isEmpty) {
+                    val bytes = packet.readBytes()
+                    file.appendBytes(bytes)
+                }
+            }
+        }
+    fileCaching[url] = file.absolutePath
+    return file
 }

+ 1 - 0
app/src/main/java/com/example/modifier/model/TaskAction.kt

@@ -41,6 +41,7 @@ class TaskItem(
     val taskId: Int,
     val number: String,
     val message: String,
+    val img: String?,
     val status: String,
     @Serializable(with = LocalDateTimeSerializer::class)
     val sendAt: LocalDateTime?

+ 19 - 4
app/src/main/java/com/example/modifier/service/TaskRunner.kt

@@ -3,6 +3,10 @@ package com.example.modifier.service
 import android.content.Context
 import android.util.Log
 import android.view.accessibility.AccessibilityNodeInfo
+import coil3.ImageLoader
+import coil3.SingletonImageLoader
+import coil3.request.ImageRequest
+import coil3.request.crossfade
 import com.example.modifier.baseTag
 import com.example.modifier.TraverseResult
 import com.example.modifier.constants.CMD_BACK
@@ -20,6 +24,7 @@ import com.example.modifier.extension.kill
 import com.example.modifier.http.api.DeviceApi
 import com.example.modifier.http.api.RcsNumberApi
 import com.example.modifier.http.api.SysConfigApi
+import com.example.modifier.http.downloadImage
 import com.example.modifier.http.ktorClient
 import com.example.modifier.http.response.SysConfigResponse
 import com.example.modifier.model.InstallApkAction
@@ -94,13 +99,23 @@ class TaskRunner(
     private var requestMode = 1
     private var storeNumberJob: Job? = null
 
+    private val imageLoader = ImageLoader.Builder(context)
+        .crossfade(true)
+        .build()
+
+
     suspend fun send(
         to: String,
-        body: String,
+        body: String?,
+        img: String?,
         taskConfig: TaskConfig
     ): Boolean {
         Log.i(TAG, "Sending SMS to $to: $body")
-        context.startActivity(smsIntent(to, body))
+        var imgFile: File? = null
+        if (img?.isNotBlank() == true) {
+            imgFile = downloadImage(img)
+        }
+        context.startActivity(smsIntent(to, body, imgFile))
         try {
             Log.i(TAG, "Command executed successfully, waiting for app to open...")
             delay(1000)
@@ -195,7 +210,7 @@ class TaskRunner(
             for (i in 0 until taskAction.data.tasks.size) {
                 val taskItem = taskAction.data.tasks[i]
                 try {
-                    if (send(taskItem.number, taskItem.message, taskConfig)) {
+                    if (send(taskItem.number, taskItem.message, taskItem.img, taskConfig)) {
                         success.add(taskItem.id)
                     } else {
                         fail.add(taskItem.id)
@@ -598,7 +613,7 @@ class TaskRunner(
                     }
 
                     checkRcsA10yNumbers.forEach {
-                        context.startActivity(smsIntent(it, ""))
+                        context.startActivity(smsIntent(it, "", null))
                         repeat(timeout / 200) {
                             delay(200)
                             val traverseResult = TraverseResult()

+ 38 - 18
app/src/main/java/com/example/modifier/ui/utils/UtilsFragment.kt

@@ -13,6 +13,10 @@ import androidx.core.content.ContextCompat
 import androidx.core.content.FileProvider.getUriForFile
 import androidx.fragment.app.Fragment
 import androidx.lifecycle.lifecycleScope
+import coil3.ImageLoader
+import coil3.request.ImageRequest
+import coil3.request.crossfade
+import coil3.toBitmap
 import com.example.modifier.BuildConfig
 import com.example.modifier.Frida
 import com.example.modifier.R
@@ -27,7 +31,10 @@ import com.example.modifier.databinding.FragmentUtilsBinding
 import com.example.modifier.extension.kill
 import com.example.modifier.http.ktorClient
 import com.example.modifier.http.api.SysConfigApi
+import com.example.modifier.http.downloadImage
 import com.example.modifier.http.response.SysConfigResponse
+import com.example.modifier.service.TaskRunner
+import com.example.modifier.service.TaskRunner.Companion
 import com.example.modifier.utils.clear
 import com.example.modifier.utils.clearConv
 import com.example.modifier.utils.killPhoneProcess
@@ -60,6 +67,11 @@ class UtilsFragment : Fragment() {
     }
 
     private lateinit var binding: FragmentUtilsBinding
+    private val imageLoader by lazy {
+        ImageLoader.Builder(requireContext())
+            .crossfade(true)
+            .build()
+    }
 
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
@@ -271,25 +283,33 @@ class UtilsFragment : Fragment() {
         }
 
         binding.btnSendImg.setOnClickListener {
-            val intent = Intent(Intent.ACTION_SENDTO)
-            intent.data = Uri.parse("sms:+18583199738")
-            intent.putExtra("exit_on_sent", true)
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            intent.setPackage(PACKAGE_MESSAGING)
+            lifecycleScope.launch {
+                withContext(Dispatchers.IO) {
+                    val file =
+                        downloadImage("https://nebuai.oss-cn-hangzhou.aliyuncs.com/image/20241112/l57qnhga.jpg")
+                    Log.i(TAG, "file: ${file.path}")
+                    val intent = Intent(Intent.ACTION_SENDTO)
+                    intent.data = Uri.parse("sms:+18583199738")
+                    intent.putExtra("exit_on_sent", true)
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    intent.setPackage(PACKAGE_MESSAGING)
 
-            val context = com.example.modifier.utils.getContext()
-            val imagePath: File = ContextCompat.getExternalFilesDirs(context, "images")[0]
-            val newFile = File(imagePath, "1.jpg")
-            val contentUri: Uri =
-                getUriForFile(com.example.modifier.utils.getContext(), "${BuildConfig.APPLICATION_ID}.fileprovider", newFile)
-            intent.putExtra(Intent.EXTRA_STREAM, contentUri)
-            Log.i(TAG, "contentUri: ${contentUri}")
-            requireContext().grantUriPermission(
-                PACKAGE_MESSAGING,
-                contentUri,
-                Intent.FLAG_GRANT_READ_URI_PERMISSION
-            )
-            startActivity(intent)
+                    val contentUri: Uri =
+                        getUriForFile(
+                            com.example.modifier.utils.getContext(),
+                            "${BuildConfig.APPLICATION_ID}.fileprovider",
+                            file
+                        )
+                    intent.putExtra(Intent.EXTRA_STREAM, contentUri)
+                    Log.i(TAG, "contentUri: ${contentUri}")
+                    requireContext().grantUriPermission(
+                        PACKAGE_MESSAGING,
+                        contentUri,
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    )
+                    startActivity(intent)
+                }
+            }
         }
 
         return binding.root

+ 21 - 2
app/src/main/java/com/example/modifier/utils/GoogleMessage.kt

@@ -6,6 +6,8 @@ import android.content.Intent
 import android.net.Uri
 import android.os.Build
 import androidx.core.content.ContextCompat
+import androidx.core.content.FileProvider.getUriForFile
+import com.example.modifier.BuildConfig
 import com.example.modifier.Utils
 import com.example.modifier.constants.PACKAGE_MESSAGING
 import com.example.modifier.constants.PACKAGE_TELEPHONY
@@ -23,13 +25,30 @@ fun injectOTP(otp: String) {
     context.sendBroadcast(intent)
 }
 
-fun smsIntent(to: String, body: String): Intent {
+fun smsIntent(to: String, body: String?, img: File?): Intent {
     val intent = Intent(Intent.ACTION_SENDTO)
     intent.data = Uri.parse("sms:$to")
-    intent.putExtra("sms_body", body)
+    if (body?.isNotBlank() == true) {
+        intent.putExtra("sms_body", body)
+    }
     intent.putExtra("exit_on_sent", true)
     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
     intent.setPackage(PACKAGE_MESSAGING)
+    if (img != null) {
+        val context = getContext()
+        val contentUri: Uri =
+            getUriForFile(
+                context,
+                "${BuildConfig.APPLICATION_ID}.fileprovider",
+                img
+            )
+        intent.putExtra(Intent.EXTRA_STREAM, contentUri)
+        context.grantUriPermission(
+            PACKAGE_MESSAGING,
+            contentUri,
+            Intent.FLAG_GRANT_READ_URI_PERMISSION
+        )
+    }
     return intent
 }