Update: 2026-07-11 05:39:23
This commit is contained in:
+123
-107
@@ -7,22 +7,27 @@ import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
|
||||
/**
|
||||
* Android 16 Progress Style Live Update Plugin
|
||||
*
|
||||
* يستخدم NotificationCompat مع setProgressStyle لتوفير
|
||||
* تجربة Live Update متقدمة على Android 16 (API 36) والأجهزة الأحدث.
|
||||
*
|
||||
* الميزات:
|
||||
* - شريط تقدم متحرك (Progress Style)
|
||||
* - تحديث مباشر للوقت المتبقي
|
||||
* - دعم الحالات المختلفة (بحث، في الطريق، وصل، رحلة جارية)
|
||||
* - إشعار ثابت (Ongoing) لا يمكن إلغاؤه أثناء الرحلة
|
||||
* خدمة تحديث حالة الرحلة في شريط الإشعارات على أندرويد.
|
||||
*
|
||||
* تعمل على ثلاثة مستويات حسب الجهاز:
|
||||
* - أندرويد 16 فما فوق (API 36+) مع موافقة المستخدم: تُستخدم
|
||||
* ميزة Live Updates الحقيقية عبر Notification.ProgressStyle
|
||||
* (نفس الفئة والدوال التي تحققنا منها مباشرة من ملف الـSDK).
|
||||
* - أندرويد 16 فما فوق بدون موافقة المستخدم، أو أي إصدار من
|
||||
* API 23 حتى 35: إشعار عادٍ ثابت (Ongoing) بشريط تقدّم كلاسيكي،
|
||||
* يعمل بشكل متطابق تقريباً بصرياً لكن بدون معاملة النظام الخاصة.
|
||||
* - أقل من API 26: لا يتم إنشاء قناة إشعارات (الكلاس غير موجود
|
||||
* أصلاً في النظام قبل هذا الإصدار)، ويُستخدم مسار الإشعار
|
||||
* الكلاسيكي مباشرة بدون قناة.
|
||||
*/
|
||||
class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
|
||||
@@ -31,9 +36,7 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
private const val CHANNEL_NAME = "Siro Live Ride Progress"
|
||||
private const val CHANNEL_DESC = "Live progress updates for your current Siro ride"
|
||||
private const val NOTIFICATION_ID = 999
|
||||
private const val SUMMARY_NOTIFICATION_ID = 1000
|
||||
|
||||
// Ride status constants
|
||||
private const val STATUS_SEARCHING = "searching"
|
||||
private const val STATUS_DRIVER_ON_WAY = "driver_on_way"
|
||||
private const val STATUS_DRIVER_ARRIVED = "driver_arrived"
|
||||
@@ -51,34 +54,66 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
private var currentBody: String = ""
|
||||
|
||||
init {
|
||||
createNotificationChannel()
|
||||
// NotificationChannel نفسها غير موجودة في النظام قبل API 26،
|
||||
// فاستدعاؤها بدون هذا الشرط يتسبب بانهيار فوري للتطبيق عند
|
||||
// الإقلاع على أي جهاز أقدم (Android 6/7).
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* إنشاء قناة الإشعارات المخصصة للرحلة
|
||||
* مع دعم Android 16 Progress Style
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel() {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_LOW // Low importance for progress style
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = CHANNEL_DESC
|
||||
setShowBadge(false)
|
||||
enableVibration(false)
|
||||
enableLights(false)
|
||||
// Android 16: دعم التقدم المستمر
|
||||
if (Build.VERSION.SDK_INT >= 36) {
|
||||
// يمكن إضافة خصائص Android 16 هنا
|
||||
}
|
||||
}
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
/**
|
||||
* هل يمكن فعلياً استخدام Live Updates الحقيقية الآن؟
|
||||
* تتطلب أندرويد 16+ وموافقة صريحة من المستخدم عبر إعدادات النظام
|
||||
* (Settings > Apps > Siro > Notifications > Live Updates).
|
||||
*/
|
||||
@Suppress("NewApi")
|
||||
private fun canUsePromotedLiveUpdate(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < 36) return false
|
||||
return try {
|
||||
notificationManager.canPostPromotedNotifications()
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun statusColor(): Int = when (currentStatus) {
|
||||
STATUS_DRIVER_ARRIVED -> Color.parseColor("#22C55E")
|
||||
STATUS_IN_PROGRESS -> Color.parseColor("#1A2340")
|
||||
STATUS_SEARCHING -> Color.parseColor("#F59E0B")
|
||||
else -> Color.parseColor("#1A2340")
|
||||
}
|
||||
|
||||
private fun buildContentIntent(): PendingIntent {
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra("notification_ride", true)
|
||||
}
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* بدء التحديث المباشر للرحلة
|
||||
* يعرض إشعاراً ثابتاً مع شريط تقدم
|
||||
*/
|
||||
fun startLiveUpdate(
|
||||
rideId: String,
|
||||
@@ -93,19 +128,11 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
|
||||
currentMaxProgress = 100
|
||||
|
||||
showNotification(
|
||||
title = currentTitle,
|
||||
body = currentBody,
|
||||
progress = currentProgress,
|
||||
maxProgress = currentMaxProgress,
|
||||
indeterminate = false,
|
||||
ongoing = true
|
||||
)
|
||||
render(indeterminate = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* تحديث التقدم والوقت المتبقي
|
||||
* يستخدم Progress Style مع animate للتحديثات
|
||||
*/
|
||||
fun updateProgress(progress: Double, etaText: String) {
|
||||
currentProgress = (progress * 100).toInt().coerceIn(0, 100)
|
||||
@@ -122,19 +149,11 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
else -> currentBody
|
||||
}
|
||||
|
||||
showNotification(
|
||||
title = currentTitle,
|
||||
body = currentBody,
|
||||
progress = currentProgress,
|
||||
maxProgress = currentMaxProgress,
|
||||
indeterminate = false,
|
||||
ongoing = true
|
||||
)
|
||||
render(indeterminate = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* تحديث حالة الرحلة بالكامل
|
||||
* يستخدم لتغيير الحالة بين (في الطريق، وصل، رحلة جارية)
|
||||
*/
|
||||
fun updateStatus(
|
||||
status: String,
|
||||
@@ -150,50 +169,23 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
STATUS_SEARCHING -> {
|
||||
currentTitle = "🔍 جاري البحث عن سائق"
|
||||
currentBody = etaText
|
||||
showNotification(
|
||||
title = currentTitle,
|
||||
body = currentBody,
|
||||
progress = 0,
|
||||
maxProgress = 0,
|
||||
indeterminate = true,
|
||||
ongoing = true
|
||||
)
|
||||
render(indeterminate = true)
|
||||
}
|
||||
STATUS_DRIVER_ON_WAY -> {
|
||||
currentTitle = "🚗 السائق في الطريق إليك"
|
||||
currentBody = "$driverName • $carDetails • $etaText"
|
||||
showNotification(
|
||||
title = currentTitle,
|
||||
body = currentBody,
|
||||
progress = currentProgress,
|
||||
maxProgress = 100,
|
||||
indeterminate = false,
|
||||
ongoing = true
|
||||
)
|
||||
render(indeterminate = false)
|
||||
}
|
||||
STATUS_DRIVER_ARRIVED -> {
|
||||
currentTitle = "📍 السائق وصل"
|
||||
currentBody = "الرجاء التوجّه لمقابلة $driverName عند نقطة الالتقاء"
|
||||
showNotification(
|
||||
title = currentTitle,
|
||||
body = currentBody,
|
||||
progress = 100,
|
||||
maxProgress = 100,
|
||||
indeterminate = false,
|
||||
ongoing = true
|
||||
)
|
||||
currentProgress = 100
|
||||
render(indeterminate = false)
|
||||
}
|
||||
STATUS_IN_PROGRESS -> {
|
||||
currentTitle = "🚀 الرحلة جارية الآن"
|
||||
currentBody = "المتبقي تقريبًا: $etaText"
|
||||
showNotification(
|
||||
title = currentTitle,
|
||||
body = currentBody,
|
||||
progress = currentProgress,
|
||||
maxProgress = 100,
|
||||
indeterminate = false,
|
||||
ongoing = true
|
||||
)
|
||||
render(indeterminate = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,29 +200,58 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* إظهار الإشعار مع الإعدادات المناسبة
|
||||
* يختار المسار المناسب: Live Update الحقيقية إن كانت متاحة ومسموحة،
|
||||
* وإلا الإشعار الكلاسيكي الآمن على كل الإصدارات من API 23.
|
||||
*/
|
||||
private fun showNotification(
|
||||
title: String,
|
||||
body: String,
|
||||
progress: Int,
|
||||
maxProgress: Int,
|
||||
indeterminate: Boolean,
|
||||
ongoing: Boolean
|
||||
) {
|
||||
// Intent لفتح التطبيق عند النقر على الإشعار
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra("notification_ride", true)
|
||||
private fun render(indeterminate: Boolean) {
|
||||
if (canUsePromotedLiveUpdate()) {
|
||||
try {
|
||||
showPromotedProgressNotification(indeterminate)
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
// أي فشل غير متوقع بالمسار الجديد يرجعنا فوراً للمسار الآمن
|
||||
}
|
||||
}
|
||||
showClassicNotification(indeterminate)
|
||||
}
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
/**
|
||||
* المسار الجديد: Notification.ProgressStyle الحقيقية (Android 16 / API 36+).
|
||||
* التوقيعات هنا مؤكدة مباشرة من ملف android-36/android.jar (javap)، وليست تخميناً.
|
||||
*/
|
||||
@RequiresApi(36)
|
||||
private fun showPromotedProgressNotification(indeterminate: Boolean) {
|
||||
val trackerIcon = Icon.createWithResource(context, android.R.drawable.ic_menu_directions)
|
||||
|
||||
val progressStyle = Notification.ProgressStyle()
|
||||
.setProgressSegments(listOf(
|
||||
Notification.ProgressStyle.Segment(100).setColor(statusColor())
|
||||
))
|
||||
.setProgress(currentProgress)
|
||||
.setProgressIndeterminate(indeterminate)
|
||||
.setProgressTrackerIcon(trackerIcon)
|
||||
.setStyledByProgress(true)
|
||||
|
||||
val notification = Notification.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setContentTitle(currentTitle)
|
||||
.setContentText(currentBody)
|
||||
.setStyle(progressStyle)
|
||||
.setCategory(Notification.CATEGORY_PROGRESS)
|
||||
.setOngoing(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentIntent(buildContentIntent())
|
||||
.setFlag(Notification.FLAG_PROMOTED_ONGOING, true)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
/**
|
||||
* المسار الكلاسيكي: إشعار ثابت بشريط تقدّم عادي، متوافق من API 23 وما فوق
|
||||
* (وبدون قناة إشعارات على الإصدارات الأقدم من API 26).
|
||||
*/
|
||||
private fun showClassicNotification(indeterminate: Boolean) {
|
||||
val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setLargeIcon(
|
||||
@@ -239,30 +260,23 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
android.R.drawable.ic_menu_directions
|
||||
)
|
||||
)
|
||||
.setContentTitle(title)
|
||||
.setContentText(body)
|
||||
.setContentTitle(currentTitle)
|
||||
.setContentText(currentBody)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.bigText(body)
|
||||
.bigText(currentBody)
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(ongoing)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setContentIntent(buildContentIntent())
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
|
||||
// تطبيق Progress Style
|
||||
if (indeterminate) {
|
||||
notificationBuilder.setProgress(0, 0, true)
|
||||
} else {
|
||||
notificationBuilder.setProgress(maxProgress, progress, false)
|
||||
}
|
||||
|
||||
// Android 16: إضافة إجراءات سريعة للإشعار
|
||||
if (Build.VERSION.SDK_INT >= 36) {
|
||||
// يمكن إضافة إجراءات Android 16 هنا
|
||||
// مثل: إظهار الخريطة، مشاركة الرحلة، إلخ
|
||||
notificationBuilder.setProgress(currentMaxProgress, currentProgress, false)
|
||||
}
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
||||
@@ -302,11 +316,13 @@ class AndroidLiveUpdatePlugin(private val context: Context) {
|
||||
result.success(true)
|
||||
}
|
||||
"isSupported" -> {
|
||||
result.success(Build.VERSION.SDK_INT >= 30) // يدعم Android 11+
|
||||
// الإشعار الكلاسيكي يعمل من API 23 وما فوق؛ الأجهزة الأحدث
|
||||
// تحصل تلقائياً على Live Update الحقيقية عبر canUsePromotedLiveUpdate().
|
||||
result.success(Build.VERSION.SDK_INT >= 23)
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ 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
|
||||
@@ -131,7 +130,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
|
||||
private fun isDeviceCompromised(): Boolean {
|
||||
return try {
|
||||
nativeRootCheck() || rootBeerCheck() // || !safetyNetCheck()
|
||||
nativeRootCheck() // || !safetyNetCheck()
|
||||
} catch (e: Exception) {
|
||||
Log.e("SecurityCheck", "Error during security checks: ${e.message}", e)
|
||||
true
|
||||
@@ -150,14 +149,6 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun rootBeerCheck(): Boolean {
|
||||
Log.d("SecurityCheck", "Starting RootBeer root detection...")
|
||||
val rootBeer = RootBeer(this)
|
||||
val isRooted = rootBeer.isRooted
|
||||
Log.d("SecurityCheck", "RootBeer detection result: $isRooted")
|
||||
return isRooted
|
||||
}
|
||||
|
||||
// SafetyNet (معلّق كما هو)
|
||||
private fun safetyNetCheck(): Boolean {
|
||||
Log.d("SecurityCheck", "Starting SafetyNet check...")
|
||||
|
||||
Reference in New Issue
Block a user