first commit
188
siro_driver/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,188 @@
|
||||
<?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" />
|
||||
<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: siroapp://... -->
|
||||
<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="siroapp" />
|
||||
</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" />
|
||||
|
||||
<!-- Android Auto Support -->
|
||||
<meta-data
|
||||
android:name="androidx.car.app.minCarAppApiLevel"
|
||||
android:value="1" />
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc" />
|
||||
<service
|
||||
android:name=".MyCarAppService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.car.app.CarAppService" />
|
||||
<category android:name="androidx.car.app.category.NAVIGATION" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
11
siro_driver/android/app/src/main/cpp/CMakeLists.txt
Executable file
@@ -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(intaleq_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})
|
||||
187
siro_driver/android/app/src/main/cpp/native-lib.cpp
Executable file
@@ -0,0 +1,187 @@
|
||||
|
||||
#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
|
||||
bool isRooted()
|
||||
{
|
||||
std::string paths[] = {
|
||||
"/system/app/Superuser.apk",
|
||||
"/system/xbin/su",
|
||||
"/system/bin/su",
|
||||
"/system/bin/magisk",
|
||||
"/system/xbin/magisk",
|
||||
"/sbin/magisk"};
|
||||
|
||||
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,44 @@
|
||||
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
|
||||
|
||||
// --- نظام المستمعين ---
|
||||
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,209 @@
|
||||
package com.siro.siro_driver
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
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.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 {
|
||||
return try {
|
||||
val isRootedByRootBeer = RootBeer(this).isRooted
|
||||
isRootedByRootBeer
|
||||
} catch (e: Exception) {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
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,119 @@
|
||||
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()
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// تحميل ستايل خرائط انطلق من أصول فلاتر
|
||||
val styleUrl = "asset://flutter_assets/assets/style.json"
|
||||
map.setStyle(Style.Builder().fromUri(styleUrl)) {
|
||||
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("Intaleq Driver")
|
||||
.setHeaderAction(Action.APP_ICON)
|
||||
.build()
|
||||
}
|
||||
|
||||
// --- بناء معلومات التوجيه (Turn-by-Turn) ---
|
||||
val maneuverType = mapIntaleqManeuverToCarManeuver(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 mapIntaleqManeuverToCarManeuver(intaleqCode: Int): Int {
|
||||
return when (intaleqCode) {
|
||||
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>
|
||||
BIN
siro_driver/android/app/src/main/res/drawable/app_icon.png
Normal file
|
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>
|
||||
BIN
siro_driver/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 3.1 KiB |
BIN
siro_driver/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
siro_driver/android/app/src/main/res/raw/app_icon.png
Executable file
|
After Width: | Height: | Size: 61 KiB |
BIN
siro_driver/android/app/src/main/res/raw/cancel.wav
Executable file
BIN
siro_driver/android/app/src/main/res/raw/ding.wav
Executable file
BIN
siro_driver/android/app/src/main/res/raw/iphone_ringtone.wav
Executable file
BIN
siro_driver/android/app/src/main/res/raw/order.wav
Executable file
BIN
siro_driver/android/app/src/main/res/raw/order1.wav
Executable file
BIN
siro_driver/android/app/src/main/res/raw/promo.wav
Executable file
BIN
siro_driver/android/app/src/main/res/raw/start.wav
Executable file
BIN
siro_driver/android/app/src/main/res/raw/tone1.mp3
Executable file
BIN
siro_driver/android/app/src/main/res/raw/tone2.wav
Executable file
8
siro_driver/android/app/src/main/res/values-ar/strings.xml
Executable file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
<string name="security_warning_title">تحذير أمني</string>
|
||||
<string name="security_warning_message">تم اكتشاف مشكلة أمنية أو تعديل على هذا الجهاز. لا يمكن
|
||||
تشغيل التطبيق على هذا الجهاز.</string>
|
||||
<string name="exit_button">إغلاق التطبيق</string>
|
||||
<string name="device_secure">الجهاز آمن. الاستمرار بشكل طبيعي.</string>
|
||||
<string name="label">انطلق درايفر</string>
|
||||
</resources>
|
||||
18
siro_driver/android/app/src/main/res/values-night/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
21
siro_driver/android/app/src/main/res/values/strings.xml
Executable file
@@ -0,0 +1,21 @@
|
||||
<resources>
|
||||
<string name="app_name">My App</string>
|
||||
<!-- <string name="default_notification_channel_id">ride_channel</string> -->
|
||||
<!-- <string name="default_notification_channel_id">default_channel</string> -->
|
||||
<string name="default_notification_channel_id">high_importance_channel</string>
|
||||
<string name="api_key">e</string>
|
||||
<string name="security_warning_title">Security Warning</string>
|
||||
<string name="api_key_safety">AIzaSyB04YNW3LbvmQ5lX1t2bOwEU18-</string>
|
||||
<string name="label">Siro Driver</string>
|
||||
|
||||
<string name="security_warning_message">A security issue or modification has been detected on
|
||||
this device. The app cannot run on this device.</string>
|
||||
<string name="exit_button">Exit App</string>
|
||||
<string name="device_secure">Device is secure. Proceeding normally.</string>
|
||||
|
||||
|
||||
<!-- <string name="default_notification_channel_id">driver_service_channel</string> -->
|
||||
<string name="location_service_channel_id">location_service_channel</string>
|
||||
<string name="geolocator_channel_id">geolocator_channel</string>
|
||||
|
||||
</resources>
|
||||
18
siro_driver/android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<automotiveApp>
|
||||
<uses name="navigation" />
|
||||
</automotiveApp>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">intaleq.xyz</domain>
|
||||
|
||||
<pin-set expiration="2027-01-01">
|
||||
<!-- <pin digest="SHA-256">pXmP2hTQLxDEvlTVmP5N7xpiA32sycBsxB6hBFT2uL4=</pin> -->
|
||||
<pin digest="SHA-256">XJXX7XthMj5VlSHfvo1q73sY7orJ9Wle0X4avj0/Vwo=</pin>
|
||||
|
||||
<pin digest="SHA-256">C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHESsl=</pin>
|
||||
</pin-set>
|
||||
</domain-config>
|
||||
|
||||
|
||||
</network-security-config>
|
||||