feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل
قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ. الخريطة: backend · payment_server · loction_server · ride_server · passenger_server · docker · dashboard · stress_test → الجذر siro_rider → apps/rider siro_driver → apps/driver siro_admin → dashboards/admin siro_service → dashboards/service android_bot → apps/android_bot socialBot → apps/socialBot نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب) لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً: كل ما يلي يصير فرقاً مقروءاً مقابل المصدر. لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز، سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh (ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و dashboards/transit-web). ⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة: 1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر): كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner. 2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist) يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً. 3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع → يجب ضمّ الحزم داخله أسوة بـ apps/rider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@@ -0,0 +1,2 @@
|
|||||||
|
#Sun Jun 21 02:56:57 EET 2026
|
||||||
|
gradle.version=8.13
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#Sun Jun 21 02:56:49 EET 2026
|
||||||
|
java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.siro.android_bot"
|
||||||
|
compileSdk = 36
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.siro.android_bot"
|
||||||
|
minSdk = 24
|
||||||
|
targetSdk = 36
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
buildConfigField("String", "BOT_SECRET_KEY", "\"SIRO_BOT_SUPER_SECRET_123\"")
|
||||||
|
// مضيف الباك إند في مكان واحد: تبديله عند نسخ البوت لعلامة أخرى
|
||||||
|
// (مثل انطلق على api.intaleqapp.com) يصير سطراً واحداً لا بحثاً في الكود.
|
||||||
|
buildConfigField("String", "BACKEND_HOST", "\"https://jordan-siro.intaleqapp.com\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
implementation(libs.androidx.compose.ui)
|
||||||
|
implementation(libs.androidx.compose.ui.graphics)
|
||||||
|
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||||
|
implementation(libs.androidx.compose.material3)
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
|
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||||
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.siro.android_bot
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("com.siro.android_bot", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<package android:name="ae.com.yalla.go.dubai.client" />
|
||||||
|
<package android:name="com.zakinn.app" />
|
||||||
|
<package android:name="com.bis.taxi" />
|
||||||
|
<package android:name="com.careem.acma" />
|
||||||
|
<package android:name="com.ubercab" />
|
||||||
|
<package android:name="com.taxif.passenger" />
|
||||||
|
<package android:name="me.com.easytaxi" />
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.Android_bot"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.Android_bot">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.ScraperAccessibilityService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.accessibilityservice"
|
||||||
|
android:resource="@xml/accessibility_service_config" />
|
||||||
|
</service>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.siro.android_bot
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.siro.android_bot.ui.theme.Android_botTheme
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) ?: "UNKNOWN"
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
setContent {
|
||||||
|
Android_botTheme {
|
||||||
|
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||||
|
BotSetupScreen(
|
||||||
|
deviceId = androidId,
|
||||||
|
modifier = Modifier.padding(innerPadding)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BotSetupScreen(deviceId: String, modifier: Modifier = Modifier) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Siro Scraper Bot 🤖",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
Text(
|
||||||
|
text = "Your Device ID (Android ID):",
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = deviceId,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
Button(onClick = {
|
||||||
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip = ClipData.newPlainText("Device ID", deviceId)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
Toast.makeText(context, "Copied to clipboard!", Toast.LENGTH_SHORT).show()
|
||||||
|
}) {
|
||||||
|
Text("Copy ID")
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(48.dp))
|
||||||
|
Text(
|
||||||
|
text = "Make sure to add this ID to your Siro Server.\nAlso, remember to enable this app in Settings > Accessibility.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(horizontal = 32.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun GreetingPreview() {
|
||||||
|
Android_botTheme {
|
||||||
|
BotSetupScreen("a1b2c3d4e5f6g7h8")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package com.siro.android_bot.network
|
||||||
|
|
||||||
|
import com.siro.android_bot.BuildConfig
|
||||||
|
import android.content.Context
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
class WorkerClient(private val context: Context) {
|
||||||
|
|
||||||
|
private val TAG = "WorkerClient"
|
||||||
|
// TODO: Fetch this from server during first handshake instead of hardcoding
|
||||||
|
// For now, must match the BOT_SECRET_KEY env var on the server
|
||||||
|
private val SECRET_KEY = BuildConfig.BOT_SECRET_KEY
|
||||||
|
|
||||||
|
// Read the real hardware Android ID
|
||||||
|
val deviceId: String by lazy {
|
||||||
|
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) ?: "UNKNOWN_DEVICE"
|
||||||
|
}
|
||||||
|
|
||||||
|
// المضيف من BuildConfig.BACKEND_HOST (app/build.gradle.kts) — لا يُشفَّر هنا،
|
||||||
|
// حتى يكون تبديله عند نسخ البوت لعلامة أخرى سطراً واحداً في gradle.
|
||||||
|
// للتجريب المحلي: اضبط BACKEND_HOST على "http://10.0.2.2:8000" مع تقديم
|
||||||
|
// نفس المسار /backend/bot/ من جذر السيرفر المحلي.
|
||||||
|
private val BASE_URL = "${BuildConfig.BACKEND_HOST}/backend/bot/standalone_worker.php"
|
||||||
|
|
||||||
|
private fun generateSignature(deviceId: String, ts: Long): String {
|
||||||
|
val message = "$deviceId$ts"
|
||||||
|
val algorithm = "HmacSHA256"
|
||||||
|
val mac = Mac.getInstance(algorithm)
|
||||||
|
val secretKeySpec = SecretKeySpec(SECRET_KEY.toByteArray(), algorithm)
|
||||||
|
mac.init(secretKeySpec)
|
||||||
|
val hashBytes = mac.doFinal(message.toByteArray())
|
||||||
|
return hashBytes.joinToString("") { "%02x".format(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchTask(): JSONObject? {
|
||||||
|
try {
|
||||||
|
val ts = System.currentTimeMillis() / 1000
|
||||||
|
val sig = generateSignature(deviceId, ts)
|
||||||
|
|
||||||
|
val urlString = "$BASE_URL?device_id=$deviceId&ts=$ts&sig=$sig"
|
||||||
|
val url = URL(urlString)
|
||||||
|
|
||||||
|
Log.d(TAG, "Fetching task from: $urlString")
|
||||||
|
val connection = url.openConnection() as HttpURLConnection
|
||||||
|
connection.requestMethod = "GET"
|
||||||
|
connection.connectTimeout = 10000
|
||||||
|
connection.readTimeout = 10000
|
||||||
|
|
||||||
|
val responseCode = connection.responseCode
|
||||||
|
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||||
|
val reader = BufferedReader(InputStreamReader(connection.inputStream))
|
||||||
|
val response = reader.readText()
|
||||||
|
reader.close()
|
||||||
|
return JSONObject(response)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Fetch Error: HTTP $responseCode")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Exception during fetchTask: ${e.message}")
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitPrice(taskId: String, appName: String, startLat: Double, startLng: Double, endLat: Double, endLng: Double, distanceKm: Double, price: Double): Boolean {
|
||||||
|
try {
|
||||||
|
val ts = System.currentTimeMillis() / 1000
|
||||||
|
val sig = generateSignature(deviceId, ts)
|
||||||
|
|
||||||
|
val payload = JSONObject().apply {
|
||||||
|
put("device_id", deviceId)
|
||||||
|
put("ts", ts)
|
||||||
|
put("sig", sig)
|
||||||
|
put("task_id", taskId)
|
||||||
|
put("type", "price_check")
|
||||||
|
put("status", "success")
|
||||||
|
|
||||||
|
val resultData = JSONObject().apply {
|
||||||
|
put("app", appName)
|
||||||
|
put("distance_km", distanceKm)
|
||||||
|
put("price", price)
|
||||||
|
put("start_lat", startLat)
|
||||||
|
put("start_lng", startLng)
|
||||||
|
put("end_lat", endLat)
|
||||||
|
put("end_lng", endLng)
|
||||||
|
}
|
||||||
|
put("result_data", resultData)
|
||||||
|
}
|
||||||
|
|
||||||
|
val url = URL(BASE_URL)
|
||||||
|
val connection = url.openConnection() as HttpURLConnection
|
||||||
|
connection.requestMethod = "POST"
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
connection.doOutput = true
|
||||||
|
connection.connectTimeout = 10000
|
||||||
|
connection.readTimeout = 10000
|
||||||
|
|
||||||
|
val writer = OutputStreamWriter(connection.outputStream)
|
||||||
|
writer.write(payload.toString())
|
||||||
|
writer.flush()
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
val responseCode = connection.responseCode
|
||||||
|
return if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||||
|
val reader = BufferedReader(InputStreamReader(connection.inputStream))
|
||||||
|
Log.d(TAG, "Submit Success: ${reader.readText()}")
|
||||||
|
reader.close()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Submit Error: HTTP $responseCode")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Exception during submitPrice: ${e.message}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.siro.android_bot.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
object AppLauncher {
|
||||||
|
private const val TAG = "AppLauncher"
|
||||||
|
|
||||||
|
// Maps the server app name to the actual Android package name
|
||||||
|
// Important: We will verify these exact package names tomorrow once you install them.
|
||||||
|
private val APP_PACKAGES = mapOf(
|
||||||
|
"yallago" to "ae.com.yalla.go.dubai.client",
|
||||||
|
"ae.com.yalla.go.dubai.client" to "ae.com.yalla.go.dubai.client",
|
||||||
|
"zaken" to "com.zakinn.app",
|
||||||
|
"com.zakinn.app" to "com.zakinn.app",
|
||||||
|
"tufaddal" to "com.bis.taxi",
|
||||||
|
"com.bis.taxi" to "com.bis.taxi",
|
||||||
|
"careem" to "com.careem.acma",
|
||||||
|
"com.careem.acma" to "com.careem.acma",
|
||||||
|
"uber" to "com.ubercab",
|
||||||
|
"com.ubercab" to "com.ubercab",
|
||||||
|
"taxif" to "com.taxif.passenger",
|
||||||
|
"com.taxif.passenger" to "com.taxif.passenger",
|
||||||
|
"jeeny" to "me.com.easytaxi",
|
||||||
|
"me.com.easytaxi" to "me.com.easytaxi"
|
||||||
|
)
|
||||||
|
|
||||||
|
fun launchApp(context: Context, appName: String): Boolean {
|
||||||
|
val packageName = APP_PACKAGES[appName.lowercase()]
|
||||||
|
if (packageName == null) {
|
||||||
|
Log.e(TAG, "Unknown app name from server: $appName")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
|
||||||
|
if (launchIntent != null) {
|
||||||
|
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
|
context.startActivity(launchIntent)
|
||||||
|
Log.i(TAG, "Successfully launched $appName ($packageName)")
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "App $appName ($packageName) is not installed on this device!")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.siro.android_bot.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val Purple80 = Color(0xFFD0BCFF)
|
||||||
|
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
|
val Pink80 = Color(0xFFEFB8C8)
|
||||||
|
|
||||||
|
val Purple40 = Color(0xFF6650a4)
|
||||||
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
|
val Pink40 = Color(0xFF7D5260)
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.siro.android_bot.ui.theme
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
private val DarkColorScheme = darkColorScheme(
|
||||||
|
primary = Purple80,
|
||||||
|
secondary = PurpleGrey80,
|
||||||
|
tertiary = Pink80
|
||||||
|
)
|
||||||
|
|
||||||
|
private val LightColorScheme = lightColorScheme(
|
||||||
|
primary = Purple40,
|
||||||
|
secondary = PurpleGrey40,
|
||||||
|
tertiary = Pink40
|
||||||
|
|
||||||
|
/* Other default colors to override
|
||||||
|
background = Color(0xFFFFFBFE),
|
||||||
|
surface = Color(0xFFFFFBFE),
|
||||||
|
onPrimary = Color.White,
|
||||||
|
onSecondary = Color.White,
|
||||||
|
onTertiary = Color.White,
|
||||||
|
onBackground = Color(0xFF1C1B1F),
|
||||||
|
onSurface = Color(0xFF1C1B1F),
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Android_botTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
// Dynamic color is available on Android 12+
|
||||||
|
dynamicColor: Boolean = true,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
darkTheme -> DarkColorScheme
|
||||||
|
else -> LightColorScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.siro.android_bot.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
// Set of Material typography styles to start with
|
||||||
|
val Typography = Typography(
|
||||||
|
bodyLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
/* Other default text styles to override
|
||||||
|
titleLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
lineHeight = 28.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
labelSmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
)
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">android_bot</string>
|
||||||
|
<string name="accessibility_service_description">Siro Scraper Accessibility Service helps automate competitor price checks.</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Theme.Android_bot" parent="android:Theme.Material.Light.NoActionBar" />
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
|
||||||
|
android:accessibilityFeedbackType="feedbackGeneric"
|
||||||
|
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews"
|
||||||
|
android:canRetrieveWindowContent="true"
|
||||||
|
android:canPerformGestures="true"
|
||||||
|
android:description="@string/accessibility_service_description"
|
||||||
|
android:notificationTimeout="100" />
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample backup rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/guide/topics/data/autobackup
|
||||||
|
for details.
|
||||||
|
Note: This file is ignored for devices older than API 31
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore
|
||||||
|
-->
|
||||||
|
<full-backup-content>
|
||||||
|
<!--
|
||||||
|
<include domain="sharedpref" path="."/>
|
||||||
|
<exclude domain="sharedpref" path="device.xml"/>
|
||||||
|
-->
|
||||||
|
</full-backup-content>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample data extraction rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||||
|
for details.
|
||||||
|
-->
|
||||||
|
<data-extraction-rules>
|
||||||
|
<cloud-backup>
|
||||||
|
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
-->
|
||||||
|
</cloud-backup>
|
||||||
|
<!--
|
||||||
|
<device-transfer>
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
</device-transfer>
|
||||||
|
-->
|
||||||
|
</data-extraction-rules>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<domain includeSubdomains="true">location.intaleq.xyz</domain>
|
||||||
|
<domain includeSubdomains="true">intaleq.xyz</domain>
|
||||||
|
<domain includeSubdomains="true">siromove.com</domain>
|
||||||
|
</domain-config>
|
||||||
|
</network-security-config>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.siro.android_bot
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
fun addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application) apply false
|
||||||
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. For more details, visit
|
||||||
|
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
|
kotlin.code.style=official
|
||||||
|
# Enables namespacing of each library's R class so that its R class includes only the
|
||||||
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
|
# thereby reducing the size of the R class for that library
|
||||||
|
android.nonTransitiveRClass=true
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
[versions]
|
||||||
|
agp = "8.13.2"
|
||||||
|
kotlin = "2.0.21"
|
||||||
|
coreKtx = "1.18.0"
|
||||||
|
junit = "4.13.2"
|
||||||
|
junitVersion = "1.3.0"
|
||||||
|
espressoCore = "3.7.0"
|
||||||
|
lifecycleRuntimeKtx = "2.10.0"
|
||||||
|
activityCompose = "1.8.0"
|
||||||
|
composeBom = "2024.09.00"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
|
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
|
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
|
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||||
|
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
|
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||||
|
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
|
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#Sun Jun 21 02:56:41 EET 2026
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google {
|
||||||
|
content {
|
||||||
|
includeGroupByRegex("com\\.android.*")
|
||||||
|
includeGroupByRegex("com\\.google.*")
|
||||||
|
includeGroupByRegex("androidx.*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "android_bot"
|
||||||
|
include(":app")
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
/coverage/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
|
|
||||||
|
# Sensitive keys & environment files
|
||||||
|
key.properties
|
||||||
|
*.env
|
||||||
|
**/lib/env/env.g.dart
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "90673a4eef275d1a6692c26ac80d6d746d41a73a"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
- platform: android
|
||||||
|
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
- platform: linux
|
||||||
|
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
- platform: macos
|
||||||
|
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
- platform: web
|
||||||
|
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
- platform: windows
|
||||||
|
create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# siro_driver
|
||||||
|
|
||||||
|
A new Flutter project.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
|
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
|
||||||
|
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
|
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
|
||||||
|
|
||||||
|
For help getting started with Flutter development, view the
|
||||||
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
|
samples, guidance on mobile development, and a full API reference.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
// START: FlutterFire Configuration
|
||||||
|
id 'com.google.gms.google-services'
|
||||||
|
// END: FlutterFire Configuration
|
||||||
|
id "kotlin-android"
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
|
def keystoreProperties = new Properties()
|
||||||
|
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||||
|
// Load keystore properties if the file exists
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.siro.siro_driver"
|
||||||
|
compileSdk = 36
|
||||||
|
ndkVersion "29.0.14033849"
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path "src/main/cpp/CMakeLists.txt"
|
||||||
|
version "3.22.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// تمت الإضافة: توجيه مسار المكتبات الأصلية لمطابقة تطبيق الراكب
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
jniLibs.srcDirs = ['src/main/jniLibs']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.siro.siro_driver"
|
||||||
|
minSdkVersion = 30
|
||||||
|
targetSdk = 36
|
||||||
|
versionCode = flutterVersionCode.toInteger()
|
||||||
|
versionName = flutterVersionName
|
||||||
|
multiDexEnabled = true
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters "armeabi-v7a", "arm64-v8a"
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestPlaceholders += [mapsApiKey: keystoreProperties['mapsApiKey'] ?: ""]
|
||||||
|
|
||||||
|
// تمت الإضافة: تمرير تعليمات 16 KB لمترجم C++
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// تمت الإضافة: منع ضغط المكتبات لتتوافق مع متطلبات نظام التشغيل
|
||||||
|
packaging {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
keyAlias keystoreProperties['keyAlias']
|
||||||
|
keyPassword keystoreProperties['keyPassword']
|
||||||
|
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||||
|
storePassword keystoreProperties['storePassword']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "androidx.car.app:app:1.4.0"
|
||||||
|
implementation "org.maplibre.gl:android-sdk:11.0.0"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
// تمت الترقية لتطابق تطبيق الراكب
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||||
|
|
||||||
|
|
||||||
|
implementation 'com.scottyab:rootbeer-lib:0.1.2'
|
||||||
|
|
||||||
|
// تمت الترقية لتطابق تطبيق الراكب
|
||||||
|
implementation 'com.google.android.gms:play-services-safetynet:18.1.0'
|
||||||
|
|
||||||
|
implementation "androidx.concurrent:concurrent-futures:1.2.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Suppress warnings for specific Google ML Kit classes
|
||||||
|
-dontwarn com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions$Builder
|
||||||
|
-dontwarn com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions
|
||||||
|
-dontwarn com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions$Builder
|
||||||
|
-dontwarn com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions
|
||||||
|
-dontwarn com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions$Builder
|
||||||
|
-dontwarn com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
|
||||||
|
-dontwarn com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions$Builder
|
||||||
|
-dontwarn com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
|
||||||
|
|
||||||
|
# Keep rules for Google ML Kit
|
||||||
|
-keep class com.google.mlkit.vision.** { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.** { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions$Builder { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions$Builder { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions$Builder { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions$Builder { *; }
|
||||||
|
|
||||||
|
|
||||||
|
-keep class com.yalantis.ucrop.** { *; }
|
||||||
|
-keep class com.yalantis.ucrop.util.** { *; }
|
||||||
|
|
||||||
|
# منع قراءة كود RootDetection
|
||||||
|
#-keep class com.sefer_driver.RootDetection { *; }
|
||||||
|
-keep class com.sefer_driver.RootDetection {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Android Auto Car App Library
|
||||||
|
-keep class androidx.car.app.** { *; }
|
||||||
|
-keep class com.siro.siro_driver.MyCarAppService { *; }
|
||||||
|
-keep class com.siro.siro_driver.MyCarSession { *; }
|
||||||
|
-keep class com.siro.siro_driver.MyCarScreen { *; }
|
||||||
|
-keep class com.siro.siro_driver.CarNavigationData { *; }
|
||||||
|
-keep class com.siro.siro_driver.MapPresentation { *; }
|
||||||
|
|
||||||
|
# MapLibre Native SDK
|
||||||
|
-keep class org.maplibre.android.** { *; }
|
||||||
|
-dontwarn org.maplibre.android.**
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- ===== Permissions ===== -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING" />
|
||||||
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||||
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||||
|
<uses-permission android:name="android.permission.QUICKBOOT_POWERON" />
|
||||||
|
<uses-permission android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
<uses-permission android:name="android.permission.PICTURE_IN_PICTURE" />
|
||||||
|
<uses-feature android:name="android.hardware.camera" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||||
|
|
||||||
|
<!-- Package visibility declarations for external apps like maps and WhatsApp -->
|
||||||
|
<queries>
|
||||||
|
<package android:name="com.whatsapp" />
|
||||||
|
<package android:name="com.google.android.apps.maps" />
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<data android:scheme="geo" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<application android:name="${applicationName}" android:icon="@mipmap/launcher_icon"
|
||||||
|
android:label="@string/label" android:enableOnBackInvokedCallback="true"
|
||||||
|
android:allowBackup="false" android:fullBackupContent="false"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:usesCleartextTraffic="false" android:theme="@style/LaunchTheme">
|
||||||
|
<!-- Flutter embedding v2 -->
|
||||||
|
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||||
|
<!-- تحديد نقطة دخول خلفية (للـ overlay / background executor) -->
|
||||||
|
<!-- <meta-data
|
||||||
|
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_ENTRYPOINT"
|
||||||
|
android:value="overlayMain" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.BackgroundExecutor.DART_LIBRARY_URI"
|
||||||
|
android:value="main.dart" /> -->
|
||||||
|
<!-- خرائط + إشعارات فFirebase (قناة افتراضية) -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.geo.API_KEY"
|
||||||
|
android:value="${mapsApiKey}" />
|
||||||
|
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
|
android:value="@string/default_notification_channel_id" />
|
||||||
|
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false" />
|
||||||
|
|
||||||
|
<!-- Main Activity -->
|
||||||
|
<activity android:name=".MainActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:exported="true" android:hardwareAccelerated="true" android:launchMode="singleTask"
|
||||||
|
android:theme="@style/LaunchTheme" android:windowSoftInputMode="adjustResize">
|
||||||
|
<meta-data android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme" />
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Deep Link: siromove://... -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="siromove" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- App Links for siromove.com -->
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="https" android:host="siromove.com" android:pathPrefix="/" />
|
||||||
|
<data android:scheme="https" android:host="www.siromove.com" android:pathPrefix="/" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Navigation Intents -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="geo" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="google.navigation" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="vnd.android.cursor.item/map" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- أنشطة ومكوّنات إضافية -->
|
||||||
|
<activity android:name="com.yalantis.ucrop.UCropActivity" android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||||
|
<!-- خدماتك الخاصة -->
|
||||||
|
<service
|
||||||
|
android:name="id.flutter.flutter_background_service.BackgroundService"
|
||||||
|
android:foregroundServiceType="location"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
/>
|
||||||
|
<service
|
||||||
|
android:name="com.siro.siro_driver.trip_overlay_plugin.TripOverlayService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="specialUse" />
|
||||||
|
<service android:name=".MyFirebaseMessagingService" android:exported="false" />
|
||||||
|
<service android:name=".LocationUpdatesService" android:exported="false"
|
||||||
|
android:foregroundServiceType="location" />
|
||||||
|
|
||||||
|
<!-- خدمة Firebase الرسمية لاستقبال رسائل FCM -->
|
||||||
|
<service android:name="com.google.firebase.messaging.FirebaseMessagingService"
|
||||||
|
android:exported="false" tools:replace="android:exported">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<!-- خدمة overlay للمكتبة الأولى (إن كنت تستخدمها) -->
|
||||||
|
<!-- <service
|
||||||
|
android:name="com.phan_tech.flutter_overlay_apps.OverlayService"
|
||||||
|
android:exported="false" /> -->
|
||||||
|
<service
|
||||||
|
android:name="com.trip_overlay.TripOverlayService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="specialUse"
|
||||||
|
android:stopWithTask="false" />
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="com.trip_overlay.TripOverlayReceiver"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.trip_overlay.SHOW_OVERLAY" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<service android:name="flutter.overlay.window.flutter_overlay_window.OverlayService"
|
||||||
|
android:exported="false" android:foregroundServiceType="specialUse" />
|
||||||
|
<!-- خدمة overlay الخاصة بمكتبة flutter_overlay_window -->
|
||||||
|
<!-- استقبال توكن/رسائل قديمة (توافقية) -->
|
||||||
|
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
|
||||||
|
android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||||
|
<category android:name="com.siro.siro_driver" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<!-- خدمة الفقاعة الخاصة بك -->
|
||||||
|
<service android:name="com.dsaved.bubblehead.bubble.BubbleHeadService" android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="intent.bring.app.to.foreground" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<!-- Notif schedulers -->
|
||||||
|
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"
|
||||||
|
android:exported="false" />
|
||||||
|
<receiver
|
||||||
|
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||||
|
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<!-- مستقبل برودكاست خاص بك -->
|
||||||
|
<receiver android:name=".YourBroadcastReceiver" android:exported="false" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10.2) # 3.10.2 is fine, but no need to go as high as 3.31.5
|
||||||
|
project(siro_driver) # Good
|
||||||
|
|
||||||
|
# Add your C++ source file(s) to create a SHARED library.
|
||||||
|
add_library(native-lib SHARED native-lib.cpp)
|
||||||
|
|
||||||
|
# Find the Android log library.
|
||||||
|
find_library(log-lib log)
|
||||||
|
|
||||||
|
# Link your library against the log library. This is essential for debugging.
|
||||||
|
target_link_libraries(native-lib ${log-lib})
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <link.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/ptrace.h> // Add this line
|
||||||
|
|
||||||
|
#define LOG_TAG "NativeLib"
|
||||||
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
|
||||||
|
// Function to check for common root binaries (including Magisk, KernelSU, APatch)
|
||||||
|
bool isRooted()
|
||||||
|
{
|
||||||
|
std::string paths[] = {
|
||||||
|
"/system/app/Superuser.apk",
|
||||||
|
"/system/xbin/su",
|
||||||
|
"/system/bin/su",
|
||||||
|
"/system/bin/magisk",
|
||||||
|
"/system/xbin/magisk",
|
||||||
|
"/sbin/magisk",
|
||||||
|
"/data/adb/magisk/magiskinit",
|
||||||
|
"/data/adb/magisk/magisk",
|
||||||
|
"/data/adb/magisk.db",
|
||||||
|
"/data/adb/ksu",
|
||||||
|
"/data/adb/apatch",
|
||||||
|
"/data/adb/ap/single"};
|
||||||
|
|
||||||
|
for (const auto &path : paths)
|
||||||
|
{
|
||||||
|
std::ifstream file(path);
|
||||||
|
if (file.good())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check for the presence of files or directories commonly associated with Frida.
|
||||||
|
bool checkFridaFiles()
|
||||||
|
{
|
||||||
|
std::string fridaFiles[] = {
|
||||||
|
"/data/local/tmp/re.frida.server", // Common Frida server path
|
||||||
|
"/data/local/tmp/frida-server",
|
||||||
|
"/usr/lib/libfrida-gadget.so", // Frida gadget (injected library)
|
||||||
|
"/usr/lib64/libfrida-gadget.so",
|
||||||
|
"/data/local/re.frida.server",
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &path : fridaFiles)
|
||||||
|
{
|
||||||
|
if (access(path.c_str(), F_OK) != -1)
|
||||||
|
{
|
||||||
|
LOGE("Frida file detected: %s", path.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for open ports commonly used by Frida. This is less reliable, as ports can be changed.
|
||||||
|
bool checkFridaPorts()
|
||||||
|
{
|
||||||
|
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sock == -1)
|
||||||
|
{
|
||||||
|
return false; // Couldn't create socket, not a strong indicator.
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_in sa;
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sa.sin_family = AF_INET;
|
||||||
|
sa.sin_port = htons(27042); // Default Frida port
|
||||||
|
inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr);
|
||||||
|
|
||||||
|
if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) != -1)
|
||||||
|
{
|
||||||
|
LOGE("Frida default port (27042) is open.");
|
||||||
|
close(sock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the maps file of the current process for any suspicious entries.
|
||||||
|
bool checkMaps()
|
||||||
|
{
|
||||||
|
std::ifstream mapsFile("/proc/self/maps");
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
if (mapsFile.is_open())
|
||||||
|
{
|
||||||
|
while (std::getline(mapsFile, line))
|
||||||
|
{
|
||||||
|
// Look for lines that indicate injected libraries, especially Frida.
|
||||||
|
if (line.find("frida") != std::string::npos ||
|
||||||
|
line.find("gum-js-") != std::string::npos)
|
||||||
|
{ // Gum is Frida's JavaScript engine
|
||||||
|
LOGE("Suspicious entry in /proc/self/maps: %s", line.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapsFile.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGE("Could not open /proc/self/maps");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check loaded modules.
|
||||||
|
bool checkLoadedModules()
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
dl_iterate_phdr([](struct dl_phdr_info *info, size_t size, void *data)
|
||||||
|
{
|
||||||
|
bool *found_ptr = static_cast<bool *>(data);
|
||||||
|
if (std::string(info->dlpi_name).find("frida") != std::string::npos)
|
||||||
|
{
|
||||||
|
LOGE("Frida module detected: %s", info->dlpi_name);
|
||||||
|
*found_ptr = true;
|
||||||
|
return 1; // Stop iterating
|
||||||
|
}
|
||||||
|
return 0; // Continue iterating
|
||||||
|
},
|
||||||
|
&found);
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a simple ptrace check. More sophisticated checks are possible (and necessary for robust detection).
|
||||||
|
// bool checkPtrace() {
|
||||||
|
// // Attempt to ptrace ourselves. If another process is already tracing us, this will fail.
|
||||||
|
// if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
|
||||||
|
// LOGE("ptrace failed. Debugger or tracer detected.");
|
||||||
|
// return true; // Likely being traced
|
||||||
|
// }
|
||||||
|
// // Detach. If attached, need to detach to not interfere.
|
||||||
|
// ptrace(PTRACE_DETACH, 0, 0, 0);
|
||||||
|
// return false;
|
||||||
|
//}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT jboolean JNICALL
|
||||||
|
Java_com_siro_siro_1driver_RootDetection_isNativeRooted(JNIEnv *env, jobject /* this */)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (isRooted())
|
||||||
|
{
|
||||||
|
return JNI_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkFridaFiles())
|
||||||
|
{
|
||||||
|
return JNI_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkFridaPorts())
|
||||||
|
{
|
||||||
|
return JNI_TRUE;
|
||||||
|
}
|
||||||
|
if (checkMaps())
|
||||||
|
{
|
||||||
|
return JNI_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkLoadedModules())
|
||||||
|
{
|
||||||
|
return JNI_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (checkPtrace()) {
|
||||||
|
// return JNI_TRUE;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return JNI_FALSE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.siro.siro_driver
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
|
||||||
|
/**
|
||||||
|
* كائن مشترك (Singleton) يحمل بيانات التوجيه في الوقت الحقيقي.
|
||||||
|
* يتم تحديثه من فلاتر عبر MethodChannel في MainActivity،
|
||||||
|
* ويتم قراءته من MyCarScreen و MapPresentation لعرض البيانات على شاشة السيارة.
|
||||||
|
*/
|
||||||
|
object CarNavigationData {
|
||||||
|
// --- بيانات الموقع ---
|
||||||
|
var currentLat: Double = 0.0
|
||||||
|
var currentLng: Double = 0.0
|
||||||
|
var currentBearing: Double = 0.0
|
||||||
|
|
||||||
|
// --- بيانات التوجيه ---
|
||||||
|
var currentInstruction: String = "في انتظار بدء الرحلة..."
|
||||||
|
var distanceToNextStepMeters: Double = 0.0
|
||||||
|
var totalDistanceRemainingMeters: Double = 0.0
|
||||||
|
var estimatedTimeRemainingSeconds: Double = 0.0
|
||||||
|
var maneuverType: Int = 0 // يطابق قيم Maneuver.TYPE_* من Car App Library
|
||||||
|
|
||||||
|
// --- حالة التوجيه ---
|
||||||
|
var isNavigating: Boolean = false
|
||||||
|
var currentSpeed: Double = 0.0 // km/h
|
||||||
|
|
||||||
|
// --- إعدادات المظهر ---
|
||||||
|
var isMapDarkMode: Boolean = false // true = dark (style_dark.json), false = light (style.json)
|
||||||
|
|
||||||
|
// --- نظام المستمعين ---
|
||||||
|
private val listeners = mutableListOf<() -> Unit>()
|
||||||
|
|
||||||
|
fun addListener(listener: () -> Unit) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeListener(listener: () -> Unit) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notifyListeners() {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
listeners.forEach { it.invoke() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package com.siro.siro_driver
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.setPadding
|
||||||
|
import com.scottyab.rootbeer.RootBeer
|
||||||
|
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Timer
|
||||||
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
|
class MainActivity : FlutterFragmentActivity() {
|
||||||
|
private val SECURITY_CHANNEL = "com.siro.siro_driver/security"
|
||||||
|
private val APP_CONTROL_CHANNEL = "com.siro.siro_driver/app_control"
|
||||||
|
private var appControlChannel: MethodChannel? = null
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
|
appControlChannel =
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, APP_CONTROL_CHANNEL)
|
||||||
|
|
||||||
|
// Channel for security checks (isRooted)
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SECURITY_CHANNEL)
|
||||||
|
.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"isNativeRooted" -> result.success(isDeviceCompromised())
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel for app control (bringing to foreground)
|
||||||
|
appControlChannel?.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"bringToForeground" -> {
|
||||||
|
val intent =
|
||||||
|
Intent(this, MainActivity::class.java).apply {
|
||||||
|
action = Intent.ACTION_MAIN
|
||||||
|
addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
|
addFlags(
|
||||||
|
Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||||
|
Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
||||||
|
Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
startActivity(intent)
|
||||||
|
result.success(true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
result.error("ACTIVITY_START_FAILED", e.message, e.stackTraceToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Channel for Android Auto Navigation Updates
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.siro.siro_driver/car_navigation")
|
||||||
|
.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"updateNavState" -> {
|
||||||
|
// تحديث شامل لجميع بيانات التوجيه دفعة واحدة
|
||||||
|
CarNavigationData.currentLat = call.argument<Double>("lat") ?: CarNavigationData.currentLat
|
||||||
|
CarNavigationData.currentLng = call.argument<Double>("lng") ?: CarNavigationData.currentLng
|
||||||
|
CarNavigationData.currentBearing = call.argument<Double>("bearing") ?: CarNavigationData.currentBearing
|
||||||
|
CarNavigationData.currentSpeed = call.argument<Double>("speed") ?: CarNavigationData.currentSpeed
|
||||||
|
CarNavigationData.currentInstruction = call.argument<String>("instruction") ?: CarNavigationData.currentInstruction
|
||||||
|
CarNavigationData.distanceToNextStepMeters = call.argument<Double>("distanceToStep") ?: CarNavigationData.distanceToNextStepMeters
|
||||||
|
CarNavigationData.totalDistanceRemainingMeters = call.argument<Double>("totalDistance") ?: CarNavigationData.totalDistanceRemainingMeters
|
||||||
|
CarNavigationData.estimatedTimeRemainingSeconds = call.argument<Double>("eta") ?: CarNavigationData.estimatedTimeRemainingSeconds
|
||||||
|
CarNavigationData.maneuverType = call.argument<Int>("maneuver") ?: CarNavigationData.maneuverType
|
||||||
|
CarNavigationData.isNavigating = call.argument<Boolean>("isNavigating") ?: CarNavigationData.isNavigating
|
||||||
|
CarNavigationData.isMapDarkMode = call.argument<Boolean>("isMapDarkMode") ?: CarNavigationData.isMapDarkMode
|
||||||
|
CarNavigationData.notifyListeners()
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
"updateLocation" -> {
|
||||||
|
CarNavigationData.currentLat = call.argument<Double>("lat") ?: 0.0
|
||||||
|
CarNavigationData.currentLng = call.argument<Double>("lng") ?: 0.0
|
||||||
|
CarNavigationData.currentBearing = call.argument<Double>("bearing") ?: CarNavigationData.currentBearing
|
||||||
|
CarNavigationData.currentSpeed = call.argument<Double>("speed") ?: CarNavigationData.currentSpeed
|
||||||
|
CarNavigationData.notifyListeners()
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
"updateInstruction" -> {
|
||||||
|
CarNavigationData.currentInstruction = call.argument<String>("instruction") ?: ""
|
||||||
|
CarNavigationData.maneuverType = call.argument<Int>("maneuver") ?: 0
|
||||||
|
CarNavigationData.distanceToNextStepMeters = call.argument<Double>("distanceToStep") ?: 0.0
|
||||||
|
CarNavigationData.notifyListeners()
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
"stopNavigation" -> {
|
||||||
|
CarNavigationData.isNavigating = false
|
||||||
|
CarNavigationData.currentInstruction = "تمت الرحلة بنجاح!"
|
||||||
|
CarNavigationData.notifyListeners()
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
if (isDeviceCompromised()) {
|
||||||
|
showSecurityWarningDialog()
|
||||||
|
}
|
||||||
|
// ✅ فحص هل التطبيق فتح بسبب زر "قبول" في النافذة
|
||||||
|
checkIntentForOverlayAccept(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
setIntent(intent)
|
||||||
|
// ✅ فحص النية (Intent) عند فتح التطبيق من الخلفية
|
||||||
|
checkIntentForOverlayAccept(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔥 هذه الدالة السحرية التي تلتقط زر القبول وترسله للتطبيق الرئيسي 🔥
|
||||||
|
private fun checkIntentForOverlayAccept(intent: Intent) {
|
||||||
|
val acceptedTripId = intent.getStringExtra("acceptedTripId")
|
||||||
|
if (acceptedTripId != null) {
|
||||||
|
Log.d("MainActivity", "✅ Trip accepted via Native Intent: $acceptedTripId")
|
||||||
|
appControlChannel?.invokeMethod("onOverlayTripAccepted", acceptedTripId)
|
||||||
|
intent.removeExtra("acceptedTripId") // مسح النية لكي لا تتكرر
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- بقية كود الحماية الخاص بك ---
|
||||||
|
private fun isDeviceCompromised(): Boolean {
|
||||||
|
val isDebug = (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
||||||
|
if (isDebug) {
|
||||||
|
Log.d("MainActivity", "Debug build: bypassing root check")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
val isRootedByRootBeer = RootBeer(this).isRooted
|
||||||
|
isRootedByRootBeer
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.e("MainActivity", "RootBeer check failed: ", t)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSecurityWarningDialog() {
|
||||||
|
var secondsRemaining = 10
|
||||||
|
val progressBar =
|
||||||
|
ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal).apply {
|
||||||
|
max = 10
|
||||||
|
progress = 10
|
||||||
|
}
|
||||||
|
val textView =
|
||||||
|
TextView(this).apply {
|
||||||
|
text = getString(R.string.security_warning_message)
|
||||||
|
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
|
||||||
|
}
|
||||||
|
val layout =
|
||||||
|
LinearLayout(this).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
setPadding(48)
|
||||||
|
addView(textView)
|
||||||
|
addView(progressBar)
|
||||||
|
}
|
||||||
|
val dialog =
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(getString(R.string.security_warning_title))
|
||||||
|
.setView(layout)
|
||||||
|
.setCancelable(false)
|
||||||
|
.create()
|
||||||
|
dialog.show()
|
||||||
|
val timer = Timer()
|
||||||
|
timer.schedule(0, 1000) {
|
||||||
|
secondsRemaining--
|
||||||
|
runOnUiThread {
|
||||||
|
progressBar.progress = secondsRemaining
|
||||||
|
if (secondsRemaining <= 0) {
|
||||||
|
timer.cancel()
|
||||||
|
dialog.dismiss()
|
||||||
|
clearAppDataAndExit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearAppDataAndExit() {
|
||||||
|
try {
|
||||||
|
Runtime.getRuntime().exec("pm clear $packageName")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
clearCache()
|
||||||
|
clearAppData()
|
||||||
|
}
|
||||||
|
finishAffinity()
|
||||||
|
System.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearCache() {
|
||||||
|
deleteDir(cacheDir)
|
||||||
|
deleteDir(externalCacheDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearAppData() {}
|
||||||
|
|
||||||
|
private fun deleteDir(dir: File?): Boolean {
|
||||||
|
if (dir != null && dir.isDirectory) {
|
||||||
|
dir.list()?.forEach { deleteDir(File(dir, it)) }
|
||||||
|
}
|
||||||
|
return dir?.delete() ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package com.siro.siro_driver
|
||||||
|
|
||||||
|
import android.app.Presentation
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Display
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import org.maplibre.android.MapLibre
|
||||||
|
import org.maplibre.android.camera.CameraPosition
|
||||||
|
import org.maplibre.android.camera.CameraUpdateFactory
|
||||||
|
import org.maplibre.android.geometry.LatLng
|
||||||
|
import org.maplibre.android.maps.MapView
|
||||||
|
import org.maplibre.android.maps.MapLibreMap
|
||||||
|
import org.maplibre.android.maps.Style
|
||||||
|
|
||||||
|
/**
|
||||||
|
* شاشة عرض وهمية (Presentation) تُرسم على VirtualDisplay الخاص بشاشة السيارة.
|
||||||
|
* تستخدم MapLibre Native SDK لعرض الخريطة بنفس ستايل تطبيق سيرو.
|
||||||
|
*/
|
||||||
|
class MapPresentation(outerContext: Context, display: Display) : Presentation(outerContext, display) {
|
||||||
|
lateinit var mapView: MapView
|
||||||
|
private var mapboxMap: MapLibreMap? = null
|
||||||
|
private var isMapReady = false
|
||||||
|
|
||||||
|
private val locationListener: () -> Unit = {
|
||||||
|
updateCameraPosition()
|
||||||
|
reloadStyleIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var currentStyleLoaded: String? = null
|
||||||
|
|
||||||
|
private fun reloadStyleIfNeeded() {
|
||||||
|
val expectedStyle = if (CarNavigationData.isMapDarkMode)
|
||||||
|
"asset://packages/intaleq_maps/assets/style_dark.json"
|
||||||
|
else
|
||||||
|
"asset://packages/intaleq_maps/assets/style.json"
|
||||||
|
|
||||||
|
if (currentStyleLoaded != expectedStyle && isMapReady) {
|
||||||
|
currentStyleLoaded = expectedStyle
|
||||||
|
mapboxMap?.setStyle(Style.Builder().fromUri(expectedStyle)) { isMapReady = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
MapLibre.getInstance(context)
|
||||||
|
|
||||||
|
val root = FrameLayout(context)
|
||||||
|
root.layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
|
||||||
|
mapView = MapView(context)
|
||||||
|
mapView.layoutParams = FrameLayout.LayoutParams(
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
|
||||||
|
root.addView(mapView)
|
||||||
|
setContentView(root)
|
||||||
|
|
||||||
|
mapView.onCreate(savedInstanceState)
|
||||||
|
mapView.getMapAsync { map ->
|
||||||
|
mapboxMap = map
|
||||||
|
|
||||||
|
// تحميل ستايل خرائط سيرو من حزمة intaleq_maps حسب الوضعية
|
||||||
|
currentStyleLoaded = if (CarNavigationData.isMapDarkMode)
|
||||||
|
"asset://packages/intaleq_maps/assets/style_dark.json"
|
||||||
|
else
|
||||||
|
"asset://packages/intaleq_maps/assets/style.json"
|
||||||
|
map.setStyle(Style.Builder().fromUri(currentStyleLoaded!!)) {
|
||||||
|
isMapReady = true
|
||||||
|
updateCameraPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
// إعدادات مناسبة لشاشة السيارة
|
||||||
|
map.uiSettings.isCompassEnabled = false
|
||||||
|
map.uiSettings.isLogoEnabled = false
|
||||||
|
map.uiSettings.isAttributionEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// الاستماع لتحديثات الموقع القادمة من فلاتر
|
||||||
|
CarNavigationData.addListener(locationListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCameraPosition() {
|
||||||
|
if (!isMapReady || mapboxMap == null) return
|
||||||
|
|
||||||
|
val lat = CarNavigationData.currentLat
|
||||||
|
val lng = CarNavigationData.currentLng
|
||||||
|
val bearing = CarNavigationData.currentBearing
|
||||||
|
val speed = CarNavigationData.currentSpeed
|
||||||
|
|
||||||
|
if (lat == 0.0 && lng == 0.0) return
|
||||||
|
|
||||||
|
// حساب الزوم المناسب بناءً على السرعة (نفس المنطق في فلاتر)
|
||||||
|
val zoom = when {
|
||||||
|
speed < 15 -> 17.0
|
||||||
|
speed < 40 -> 16.5
|
||||||
|
speed < 70 -> 15.5
|
||||||
|
speed < 100 -> 15.0
|
||||||
|
else -> 14.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// حساب الميل (Tilt) بناءً على السرعة لتأثير ثلاثي الأبعاد
|
||||||
|
val tilt = when {
|
||||||
|
speed < 10 -> 0.0
|
||||||
|
speed < 40 -> 40.0
|
||||||
|
else -> 55.0
|
||||||
|
}
|
||||||
|
|
||||||
|
val position = CameraPosition.Builder()
|
||||||
|
.target(LatLng(lat, lng))
|
||||||
|
.zoom(zoom)
|
||||||
|
.bearing(bearing)
|
||||||
|
.tilt(tilt)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
mapboxMap?.animateCamera(
|
||||||
|
CameraUpdateFactory.newCameraPosition(position),
|
||||||
|
1000 // انتقال سلس خلال ثانية
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() { super.onStart(); mapView.onStart() }
|
||||||
|
override fun onStop() { super.onStop(); mapView.onStop() }
|
||||||
|
|
||||||
|
fun onResume() { mapView.onResume() }
|
||||||
|
fun onPause() { mapView.onPause() }
|
||||||
|
fun onDestroy() {
|
||||||
|
CarNavigationData.removeListener(locationListener)
|
||||||
|
mapView.onDestroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.siro.siro_driver
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Intent
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.embedding.engine.dart.DartExecutor
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
|
class MyApplication : Application() {
|
||||||
|
companion object {
|
||||||
|
lateinit var instance: MyApplication
|
||||||
|
private set
|
||||||
|
|
||||||
|
val flutterEngine: FlutterEngine by lazy {
|
||||||
|
FlutterEngine(instance).apply {
|
||||||
|
dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
instance = this
|
||||||
|
|
||||||
|
MethodChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger,
|
||||||
|
"com.siro.siro_driver/app_lifecycle"
|
||||||
|
)
|
||||||
|
.setMethodCallHandler { call, result ->
|
||||||
|
if (call.method == "bringAppToForeground") {
|
||||||
|
bringAppToForeground()
|
||||||
|
result.success(null)
|
||||||
|
} else {
|
||||||
|
result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bringAppToForeground() {
|
||||||
|
val intent = Intent(applicationContext, MainActivity::class.java)
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.siro.siro_driver
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import androidx.car.app.CarAppService
|
||||||
|
import androidx.car.app.Session
|
||||||
|
import androidx.car.app.validation.HostValidator
|
||||||
|
|
||||||
|
class MyCarAppService : CarAppService() {
|
||||||
|
override fun createHostValidator(): HostValidator {
|
||||||
|
// في وضع التطوير: نسمح لجميع المستضيفين (DHU + أي تطبيق)
|
||||||
|
// في وضع الإنتاج: نسمح فقط لتطبيقات Android Auto و Google الرسمية
|
||||||
|
return if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) {
|
||||||
|
HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
|
||||||
|
} else {
|
||||||
|
HostValidator.Builder(applicationContext)
|
||||||
|
.addAllowedHosts(androidx.car.app.R.array.hosts_allowlist_sample)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateSession(): Session {
|
||||||
|
return MyCarSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.siro.siro_driver
|
||||||
|
|
||||||
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.Screen
|
||||||
|
import androidx.car.app.model.Action
|
||||||
|
import androidx.car.app.model.CarColor
|
||||||
|
import androidx.car.app.model.CarIcon
|
||||||
|
import androidx.car.app.model.Distance
|
||||||
|
import androidx.car.app.model.MessageTemplate
|
||||||
|
import androidx.car.app.model.Template
|
||||||
|
import androidx.car.app.navigation.model.Maneuver
|
||||||
|
import androidx.car.app.navigation.model.NavigationTemplate
|
||||||
|
import androidx.car.app.navigation.model.RoutingInfo
|
||||||
|
import androidx.car.app.navigation.model.Step
|
||||||
|
|
||||||
|
class MyCarScreen(carContext: CarContext) : Screen(carContext) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
CarNavigationData.addListener {
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetTemplate(): Template {
|
||||||
|
// إذا لم يكن التوجيه نشطاً بعد، نعرض شاشة ترحيبية
|
||||||
|
if (!CarNavigationData.isNavigating) {
|
||||||
|
return MessageTemplate.Builder("مرحباً بك في سيرو درايفر\nبانتظار بدء رحلة جديدة...")
|
||||||
|
.setTitle("Siro Driver")
|
||||||
|
.setHeaderAction(Action.APP_ICON)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- بناء معلومات التوجيه (Turn-by-Turn) ---
|
||||||
|
val maneuverType = mapSiroManeuverToCarManeuver(CarNavigationData.maneuverType)
|
||||||
|
val maneuver = Maneuver.Builder(maneuverType).build()
|
||||||
|
|
||||||
|
val step = Step.Builder(CarNavigationData.currentInstruction)
|
||||||
|
.setManeuver(maneuver)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val distanceToStep = Distance.create(
|
||||||
|
CarNavigationData.distanceToNextStepMeters,
|
||||||
|
Distance.UNIT_METERS
|
||||||
|
)
|
||||||
|
|
||||||
|
val routingInfo = RoutingInfo.Builder()
|
||||||
|
.setCurrentStep(step, distanceToStep)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// --- بناء قالب التوجيه ---
|
||||||
|
return NavigationTemplate.Builder()
|
||||||
|
.setNavigationInfo(routingInfo)
|
||||||
|
.setActionStrip(
|
||||||
|
androidx.car.app.model.ActionStrip.Builder()
|
||||||
|
.addAction(Action.APP_ICON)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.setBackgroundColor(CarColor.PRIMARY)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تحويل أكواد الانعطاف الخاصة بتطبيق سيرو (NavigationController.currentManeuverModifier)
|
||||||
|
* إلى أكواد Maneuver الرسمية من مكتبة Android for Cars.
|
||||||
|
*/
|
||||||
|
private fun mapSiroManeuverToCarManeuver(siroCode: Int): Int {
|
||||||
|
return when (siroCode) {
|
||||||
|
0 -> Maneuver.TYPE_STRAIGHT // مستقيم
|
||||||
|
2 -> Maneuver.TYPE_TURN_NORMAL_RIGHT // يمين
|
||||||
|
3 -> Maneuver.TYPE_TURN_SLIGHT_RIGHT // يمين خفيف
|
||||||
|
-2 -> Maneuver.TYPE_TURN_NORMAL_LEFT // يسار
|
||||||
|
-1 -> Maneuver.TYPE_TURN_SLIGHT_LEFT // يسار خفيف
|
||||||
|
4 -> Maneuver.TYPE_DESTINATION // وصلت
|
||||||
|
6 -> Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW // دوار
|
||||||
|
7 -> Maneuver.TYPE_KEEP_RIGHT // ابق يمين
|
||||||
|
-7 -> Maneuver.TYPE_KEEP_LEFT // ابق يسار
|
||||||
|
else -> Maneuver.TYPE_UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.siro.siro_driver
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.hardware.display.DisplayManager
|
||||||
|
import android.hardware.display.VirtualDisplay
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import androidx.car.app.AppManager
|
||||||
|
import androidx.car.app.Screen
|
||||||
|
import androidx.car.app.Session
|
||||||
|
import androidx.car.app.SurfaceCallback
|
||||||
|
import androidx.car.app.SurfaceContainer
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
|
||||||
|
class MyCarSession : Session(), DefaultLifecycleObserver {
|
||||||
|
private var virtualDisplay: VirtualDisplay? = null
|
||||||
|
private var presentation: MapPresentation? = null
|
||||||
|
|
||||||
|
override fun onCreateScreen(intent: Intent): Screen {
|
||||||
|
lifecycle.addObserver(this)
|
||||||
|
|
||||||
|
val appManager = carContext.getCarService(AppManager::class.java)
|
||||||
|
appManager.setSurfaceCallback(object : SurfaceCallback {
|
||||||
|
override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
|
||||||
|
val surface = surfaceContainer.surface ?: return
|
||||||
|
val width = surfaceContainer.width
|
||||||
|
val height = surfaceContainer.height
|
||||||
|
val dpi = surfaceContainer.dpi
|
||||||
|
|
||||||
|
val displayManager = carContext.getSystemService(DisplayManager::class.java)
|
||||||
|
virtualDisplay = displayManager.createVirtualDisplay(
|
||||||
|
"CarAppMapDisplay",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
dpi,
|
||||||
|
surface,
|
||||||
|
DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
virtualDisplay?.display?.let { display ->
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
presentation = MapPresentation(carContext, display)
|
||||||
|
presentation?.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onVisibleAreaChanged(visibleArea: android.graphics.Rect) {
|
||||||
|
// تحديث المساحة المرئية إذا لزم الأمر
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
presentation?.dismiss()
|
||||||
|
presentation = null
|
||||||
|
}
|
||||||
|
virtualDisplay?.release()
|
||||||
|
virtualDisplay = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return MyCarScreen(carContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume(owner: LifecycleOwner) { presentation?.onResume() }
|
||||||
|
override fun onPause(owner: LifecycleOwner) { presentation?.onPause() }
|
||||||
|
override fun onDestroy(owner: LifecycleOwner) { presentation?.onDestroy() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.siro.siro_driver
|
||||||
|
|
||||||
|
object RootDetection {
|
||||||
|
init {
|
||||||
|
System.loadLibrary("native-lib") // Load the native library
|
||||||
|
}
|
||||||
|
|
||||||
|
external fun isNativeRooted(): Boolean // Declare the external function
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import android.content.Context
|
||||||
|
import android.util.Base64
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.android.gms.safetynet.SafetyNet
|
||||||
|
import java.io.IOException
|
||||||
|
import java.security.GeneralSecurityException
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
object SafetyNetCheck {
|
||||||
|
|
||||||
|
private const val TAG = "SafetyNetCheck"
|
||||||
|
|
||||||
|
fun checkSafetyNet(context: Context, apiKey: String, callback: (Boolean) -> Unit) {
|
||||||
|
// Generate a nonce. A good nonce is large, random, and used only once.
|
||||||
|
val nonce = generateNonce()
|
||||||
|
|
||||||
|
SafetyNet.getClient(context)
|
||||||
|
.attest(nonce, apiKey)
|
||||||
|
.addOnSuccessListener { response ->
|
||||||
|
// Success! Now, *verify* the response.
|
||||||
|
val jwsResult = response.jwsResult
|
||||||
|
if (jwsResult != null) {
|
||||||
|
try {
|
||||||
|
val isSafe = SafetyNetResponseVerifier.verify(jwsResult)
|
||||||
|
Log.d(TAG, "SafetyNet verification result: $isSafe")
|
||||||
|
callback(isSafe) // Now passing a *verified* result.
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error verifying SafetyNet response: ${e.message}", e)
|
||||||
|
callback(false) // Treat verification errors as failures.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "SafetyNet jwsResult is null")
|
||||||
|
callback(false) // Null result is a failure.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.addOnFailureListener { e ->
|
||||||
|
Log.e(TAG, "SafetyNet attest API call failed: ${e.message}", e)
|
||||||
|
callback(false) // API call failure.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to generate a nonce.
|
||||||
|
private fun generateNonce(): ByteArray {
|
||||||
|
val byteGenerator = SecureRandom()
|
||||||
|
val nonce = ByteArray(32)
|
||||||
|
byteGenerator.nextBytes(nonce)
|
||||||
|
return nonce
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper class to verify the SafetyNet response.
|
||||||
|
object SafetyNetResponseVerifier {
|
||||||
|
|
||||||
|
private const val TAG = "SafetyNetVerifier"
|
||||||
|
|
||||||
|
// This method *must* be implemented on a *backend server* for real security.
|
||||||
|
// This is just a *simplified example* for demonstration purposes and is
|
||||||
|
// *not* suitable for production without a backend check.
|
||||||
|
@Throws(GeneralSecurityException::class, IOException::class)
|
||||||
|
fun verify(jwsResult: String): Boolean {
|
||||||
|
// 1. Parse the JWS: Split into header, payload, and signature.
|
||||||
|
val parts = jwsResult.split(".")
|
||||||
|
if (parts.size != 3) {
|
||||||
|
Log.e(TAG, "Invalid JWS format")
|
||||||
|
return false // Invalid JWS format
|
||||||
|
}
|
||||||
|
|
||||||
|
val header = parts[0]
|
||||||
|
val payload = parts[1]
|
||||||
|
val signature = parts[2]
|
||||||
|
|
||||||
|
// 2. Decode the payload (it's Base64 encoded).
|
||||||
|
val decodedPayload = Base64.decode(payload, Base64.DEFAULT)
|
||||||
|
val payloadJson = JSONObject(String(decodedPayload))
|
||||||
|
|
||||||
|
// 3. Check the ctsProfileMatch and basicIntegrity.
|
||||||
|
val ctsProfileMatch = payloadJson.optBoolean("ctsProfileMatch", false)
|
||||||
|
val basicIntegrity = payloadJson.optBoolean("basicIntegrity", false)
|
||||||
|
|
||||||
|
Log.d(TAG, "ctsProfileMatch: $ctsProfileMatch, basicIntegrity: $basicIntegrity")
|
||||||
|
|
||||||
|
// 4. **CRITICAL: In a real application, you *must* send the JWS to your
|
||||||
|
// backend server for verification. The server should use the
|
||||||
|
// Google SafetyNet API (or a library that wraps it) to verify
|
||||||
|
// the signature and check the fields. This prevents attackers
|
||||||
|
// from tampering with the response on the device.**
|
||||||
|
//
|
||||||
|
// // Example (pseudo-code) of what the backend check would do:
|
||||||
|
// // GoogleCredential credential = ...;
|
||||||
|
// // SafetyNet safetyNet = new SafetyNet.Builder(httpTransport, jsonFactory)
|
||||||
|
// // .setApplicationName("YourAppName")
|
||||||
|
// // .setHttpRequestInitializer(credential)
|
||||||
|
// // .build();
|
||||||
|
// // SafetyNetApi.VerifyJwsRequest request = safetyNet.safetynet().verifyJws(jwsResult);
|
||||||
|
// // SafetyNetApi.VerifyJwsResponse response = request.execute();
|
||||||
|
// // return response.isValidSignature() && response.getCtsProfileMatch() &&
|
||||||
|
// response.getBasicIntegrity();
|
||||||
|
|
||||||
|
// 5. For this *example* (without a backend), we'll just check the fields.
|
||||||
|
// This is *NOT SECURE* for production!
|
||||||
|
return ctsProfileMatch && basicIntegrity
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
After Width: | Height: | Size: 9.8 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 61 KiB |