catalyst-core 0.1.0-canary.2 → 0.1.0-canary.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/native/androidProject/app/build.gradle.kts +12 -4
- package/dist/native/androidProject/app/proguard-rules.pro +25 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +10 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/design/DesignTokens.kt +307 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/EmulatorDetector.kt +203 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/FridaDetector.kt +187 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityAlertHandler.kt +170 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityAlertUI.kt +366 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityBottomSheet.kt +138 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityCheckManager.kt +511 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityCheckScheduler.kt +68 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DeviceInfoUtils.kt +12 -0
- package/dist/native/androidProject/app/src/main/res/values/themes.xml +12 -0
- package/package.json +1 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/LegacyGoogleSignInPlugin.kt +0 -176
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/README.md +0 -97
|
@@ -114,12 +114,12 @@ android {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
compileOptions {
|
|
117
|
-
sourceCompatibility = JavaVersion.
|
|
118
|
-
targetCompatibility = JavaVersion.
|
|
117
|
+
sourceCompatibility = JavaVersion.VERSION_11
|
|
118
|
+
targetCompatibility = JavaVersion.VERSION_11
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
kotlinOptions {
|
|
122
|
-
jvmTarget = "
|
|
122
|
+
jvmTarget = "11"
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
buildFeatures {
|
|
@@ -136,6 +136,10 @@ android {
|
|
|
136
136
|
excludes.add("META-INF/INDEX.LIST")
|
|
137
137
|
excludes.add("META-INF/io.netty.versions.properties")
|
|
138
138
|
}
|
|
139
|
+
jniLibs {
|
|
140
|
+
// Enable 16KB page size alignment for all native libraries
|
|
141
|
+
useLegacyPackaging = false
|
|
142
|
+
}
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
// Configure lint options
|
|
@@ -168,13 +172,17 @@ dependencies {
|
|
|
168
172
|
androidTestImplementation(libs.androidx.espresso.core)
|
|
169
173
|
implementation(libs.androidx.webkit)
|
|
170
174
|
implementation("org.json:json:20231013")
|
|
171
|
-
|
|
175
|
+
|
|
172
176
|
// Ktor Server dependencies for FrameworkServer (~200KB total)
|
|
173
177
|
implementation("io.ktor:ktor-server-core:3.0.3")
|
|
174
178
|
implementation("io.ktor:ktor-server-netty:3.0.3")
|
|
175
179
|
implementation("io.ktor:ktor-server-content-negotiation:3.0.3")
|
|
176
180
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
|
177
181
|
|
|
182
|
+
// Security detection dependencies
|
|
183
|
+
implementation("com.scottyab:rootbeer-lib:0.1.1") // Root detection
|
|
184
|
+
implementation("com.google.android.play:integrity:1.3.0") // Play Integrity API
|
|
185
|
+
|
|
178
186
|
// SLF4J simple logger for Ktor (optional, can be excluded if needed)
|
|
179
187
|
implementation("org.slf4j:slf4j-simple:2.0.9")
|
|
180
188
|
|
|
@@ -59,6 +59,31 @@
|
|
|
59
59
|
-keep class io.yourname.androidproject.WebCacheManager$CacheEntry { *; }
|
|
60
60
|
-keep class io.yourname.androidproject.WebCacheManager$CacheMetadata { *; }
|
|
61
61
|
|
|
62
|
+
# Keep RootBeer library for root detection
|
|
63
|
+
-keep class com.scottyab.rootbeer.** { *; }
|
|
64
|
+
-dontwarn com.scottyab.rootbeer.**
|
|
65
|
+
|
|
66
|
+
# Security: keep only the public API surface that MainActivity wires up.
|
|
67
|
+
# Detection internals (EmulatorDetector, FridaDetector) and UI classes
|
|
68
|
+
# (SecurityBottomSheet, SecurityAlertUI) are intentionally obfuscated.
|
|
69
|
+
-keep class io.yourname.androidproject.security.SecurityCheckScheduler {
|
|
70
|
+
public static void initialize(android.content.Context, kotlinx.coroutines.CoroutineScope, io.yourname.androidproject.security.SecurityCheckScheduler$SecurityCheckCallback);
|
|
71
|
+
}
|
|
72
|
+
-keep interface io.yourname.androidproject.security.SecurityCheckScheduler$SecurityCheckCallback {
|
|
73
|
+
public void onSecurityCheckComplete(org.json.JSONObject);
|
|
74
|
+
}
|
|
75
|
+
-keep class io.yourname.androidproject.security.SecurityCheckManager {
|
|
76
|
+
public static org.json.JSONObject getLatestSecurityResults(android.content.Context);
|
|
77
|
+
public static java.lang.String getSecurityMode(android.content.Context);
|
|
78
|
+
}
|
|
79
|
+
-keep class io.yourname.androidproject.security.SecurityAlertHandler {
|
|
80
|
+
public static void handleSecurityCheckResults(android.app.Activity, org.json.JSONObject);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Keep Play Integrity API
|
|
84
|
+
-keep class com.google.android.play.core.integrity.** { *; }
|
|
85
|
+
-dontwarn com.google.android.play.core.integrity.**
|
|
86
|
+
|
|
62
87
|
# Remove debug logging in release builds
|
|
63
88
|
-assumenosideeffects class android.util.Log {
|
|
64
89
|
public static *** d(...);
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt
CHANGED
|
@@ -20,6 +20,7 @@ import io.yourname.androidproject.utils.NetworkUtils
|
|
|
20
20
|
import io.yourname.androidproject.utils.NotificationConstants
|
|
21
21
|
import io.yourname.androidproject.utils.SafeAreaInsets
|
|
22
22
|
import io.yourname.androidproject.utils.SafeAreaUtils
|
|
23
|
+
import io.yourname.androidproject.security.SecurityCheckScheduler
|
|
23
24
|
import kotlinx.coroutines.CoroutineScope
|
|
24
25
|
import kotlinx.coroutines.MainScope
|
|
25
26
|
import kotlinx.coroutines.cancelChildren
|
|
@@ -253,6 +254,15 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
253
254
|
|
|
254
255
|
configureEdgeToEdge()
|
|
255
256
|
|
|
257
|
+
// Initialize security checks (release builds only)
|
|
258
|
+
if (!BuildConfig.DEBUG) {
|
|
259
|
+
SecurityCheckScheduler.initialize(this, this, object : SecurityCheckScheduler.SecurityCheckCallback {
|
|
260
|
+
override fun onSecurityCheckComplete(results: JSONObject) {
|
|
261
|
+
io.yourname.androidproject.security.SecurityAlertHandler.handleSecurityCheckResults(this@MainActivity, results)
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
256
266
|
// Initialize MetricsMonitor
|
|
257
267
|
metricsMonitor = MetricsMonitor.getInstance(this)
|
|
258
268
|
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
package io.yourname.androidproject.design
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.res.Configuration
|
|
5
|
+
import androidx.annotation.ColorInt
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Global design tokens for the application.
|
|
9
|
+
* Follows Material Design 3 principles with support for light/dark themes.
|
|
10
|
+
*
|
|
11
|
+
* All spacing follows 8dp grid system.
|
|
12
|
+
* Color system supports dynamic theming based on system configuration.
|
|
13
|
+
*/
|
|
14
|
+
object DesignTokens {
|
|
15
|
+
|
|
16
|
+
// ============================================================
|
|
17
|
+
// COLORS - Light Theme
|
|
18
|
+
// ============================================================
|
|
19
|
+
object LightColors {
|
|
20
|
+
// Surface colors
|
|
21
|
+
@ColorInt const val SURFACE = 0xFFFFFFFF.toInt() // Pure white
|
|
22
|
+
@ColorInt const val SURFACE_VARIANT = 0xFFF5F5F5.toInt() // Light gray background
|
|
23
|
+
|
|
24
|
+
// Error colors (for alerts, warnings)
|
|
25
|
+
@ColorInt const val ERROR = 0xFFB3261E.toInt() // Material error red
|
|
26
|
+
@ColorInt const val ERROR_CONTAINER = 0xFFF9DEDC.toInt() // Light red background
|
|
27
|
+
@ColorInt const val ON_ERROR = 0xFFFFFFFF.toInt() // White text on error
|
|
28
|
+
@ColorInt const val ON_ERROR_CONTAINER = 0xFF410E0B.toInt() // Dark red text
|
|
29
|
+
|
|
30
|
+
// Text colors
|
|
31
|
+
@ColorInt const val ON_SURFACE = 0xFF1C1B1F.toInt() // Primary text (near black)
|
|
32
|
+
@ColorInt const val ON_SURFACE_VARIANT = 0xFF49454F.toInt() // Secondary text (gray)
|
|
33
|
+
@ColorInt const val OUTLINE = 0xFF79747E.toInt() // Dividers, borders
|
|
34
|
+
|
|
35
|
+
// Accent colors
|
|
36
|
+
@ColorInt const val PRIMARY = 0xFF6750A4.toInt() // Material purple
|
|
37
|
+
@ColorInt const val ON_PRIMARY = 0xFFFFFFFF.toInt() // White text on primary
|
|
38
|
+
@ColorInt const val PRIMARY_CONTAINER = 0xFFEADDFF.toInt() // Light purple background
|
|
39
|
+
@ColorInt const val ON_PRIMARY_CONTAINER = 0xFF21005D.toInt() // Dark purple text
|
|
40
|
+
|
|
41
|
+
// Secondary colors
|
|
42
|
+
@ColorInt const val SECONDARY = 0xFF625B71.toInt() // Material gray-purple
|
|
43
|
+
@ColorInt const val ON_SECONDARY = 0xFFFFFFFF.toInt() // White text on secondary
|
|
44
|
+
@ColorInt const val SECONDARY_CONTAINER = 0xFFE8DEF8.toInt() // Light secondary background
|
|
45
|
+
@ColorInt const val ON_SECONDARY_CONTAINER = 0xFF1D192B.toInt() // Dark text
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================================
|
|
49
|
+
// COLORS - Dark Theme
|
|
50
|
+
// ============================================================
|
|
51
|
+
object DarkColors {
|
|
52
|
+
// Surface colors
|
|
53
|
+
@ColorInt const val SURFACE = 0xFF1C1B1F.toInt() // Dark surface
|
|
54
|
+
@ColorInt const val SURFACE_VARIANT = 0xFF2B2930.toInt() // Darker variant
|
|
55
|
+
|
|
56
|
+
// Error colors (for alerts, warnings)
|
|
57
|
+
@ColorInt const val ERROR = 0xFFF2B8B5.toInt() // Light red for dark mode
|
|
58
|
+
@ColorInt const val ERROR_CONTAINER = 0xFF8C1D18.toInt() // Dark red background
|
|
59
|
+
@ColorInt const val ON_ERROR = 0xFF601410.toInt() // Dark text on light error
|
|
60
|
+
@ColorInt const val ON_ERROR_CONTAINER = 0xFFF2B8B5.toInt() // Light text on dark error
|
|
61
|
+
|
|
62
|
+
// Text colors
|
|
63
|
+
@ColorInt const val ON_SURFACE = 0xFFE6E1E5.toInt() // Light text
|
|
64
|
+
@ColorInt const val ON_SURFACE_VARIANT = 0xFFCAC4D0.toInt() // Secondary light text
|
|
65
|
+
@ColorInt const val OUTLINE = 0xFF938F99.toInt() // Light dividers, borders
|
|
66
|
+
|
|
67
|
+
// Accent colors
|
|
68
|
+
@ColorInt const val PRIMARY = 0xFFD0BCFF.toInt() // Light purple for dark mode
|
|
69
|
+
@ColorInt const val ON_PRIMARY = 0xFF381E72.toInt() // Dark text on light primary
|
|
70
|
+
@ColorInt const val PRIMARY_CONTAINER = 0xFF4F378B.toInt() // Dark purple background
|
|
71
|
+
@ColorInt const val ON_PRIMARY_CONTAINER = 0xFFEADDFF.toInt() // Light text
|
|
72
|
+
|
|
73
|
+
// Secondary colors
|
|
74
|
+
@ColorInt const val SECONDARY = 0xFFCCC2DC.toInt() // Light gray-purple for dark
|
|
75
|
+
@ColorInt const val ON_SECONDARY = 0xFF332D41.toInt() // Dark text on light secondary
|
|
76
|
+
@ColorInt const val SECONDARY_CONTAINER = 0xFF4A4458.toInt() // Dark secondary background
|
|
77
|
+
@ColorInt const val ON_SECONDARY_CONTAINER = 0xFFE8DEF8.toInt() // Light text
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================================
|
|
81
|
+
// SPACING - Following 8dp Grid System
|
|
82
|
+
// ============================================================
|
|
83
|
+
object Spacing {
|
|
84
|
+
const val SPACE_XXS = 4 // 4dp - Minimal spacing
|
|
85
|
+
const val SPACE_XS = 8 // 8dp - Tight spacing
|
|
86
|
+
const val SPACE_SM = 12 // 12dp - Small spacing
|
|
87
|
+
const val SPACE_MD = 16 // 16dp - Medium spacing (default)
|
|
88
|
+
const val SPACE_LG = 24 // 24dp - Large spacing
|
|
89
|
+
const val SPACE_XL = 32 // 32dp - Extra large spacing
|
|
90
|
+
const val SPACE_XXL = 48 // 48dp - Maximum spacing
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================
|
|
94
|
+
// DIMENSIONS
|
|
95
|
+
// ============================================================
|
|
96
|
+
object Dimensions {
|
|
97
|
+
// Corner radius
|
|
98
|
+
const val CORNER_RADIUS_SM = 8 // Small corners (buttons, chips)
|
|
99
|
+
const val CORNER_RADIUS_MD = 16 // Medium corners (cards)
|
|
100
|
+
const val CORNER_RADIUS_LG = 24 // Large corners (bottom sheet top)
|
|
101
|
+
const val CORNER_RADIUS_XL = 28 // Extra large (full rounded)
|
|
102
|
+
|
|
103
|
+
// Icon sizes
|
|
104
|
+
const val ICON_SIZE_SM = 24 // Small icons
|
|
105
|
+
const val ICON_SIZE_MD = 48 // Medium icons
|
|
106
|
+
const val ICON_SIZE_LG = 64 // Large icons (hero icon)
|
|
107
|
+
const val ICON_SIZE_XL = 96 // Extra large icons
|
|
108
|
+
|
|
109
|
+
// Minimum touch target (Material Design)
|
|
110
|
+
const val MIN_TOUCH_TARGET = 48 // 48dp minimum for accessibility
|
|
111
|
+
|
|
112
|
+
// Elevation (for shadows/depth)
|
|
113
|
+
const val ELEVATION_NONE = 0f
|
|
114
|
+
const val ELEVATION_SM = 2f // Small elevation
|
|
115
|
+
const val ELEVATION_MD = 4f // Medium elevation
|
|
116
|
+
const val ELEVATION_LG = 8f // Large elevation
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================
|
|
120
|
+
// TYPOGRAPHY
|
|
121
|
+
// ============================================================
|
|
122
|
+
object Typography {
|
|
123
|
+
// Text sizes (SP - scalable pixels)
|
|
124
|
+
const val TEXT_SIZE_DISPLAY = 32f // Large headlines
|
|
125
|
+
const val TEXT_SIZE_HEADLINE = 24f // Section headlines
|
|
126
|
+
const val TEXT_SIZE_TITLE = 20f // Card titles
|
|
127
|
+
const val TEXT_SIZE_BODY_LG = 16f // Large body text
|
|
128
|
+
const val TEXT_SIZE_BODY = 14f // Default body text
|
|
129
|
+
const val TEXT_SIZE_LABEL = 12f // Small labels, captions
|
|
130
|
+
|
|
131
|
+
// Line height multipliers
|
|
132
|
+
const val LINE_HEIGHT_TIGHT = 1.2f // Tight line height
|
|
133
|
+
const val LINE_HEIGHT_NORMAL = 1.5f // Normal line height
|
|
134
|
+
const val LINE_HEIGHT_RELAXED = 1.7f // Relaxed line height
|
|
135
|
+
|
|
136
|
+
// Font weights (Android Typeface)
|
|
137
|
+
const val FONT_WEIGHT_NORMAL = android.graphics.Typeface.NORMAL
|
|
138
|
+
const val FONT_WEIGHT_BOLD = android.graphics.Typeface.BOLD
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ============================================================
|
|
142
|
+
// ANIMATION DURATIONS (milliseconds)
|
|
143
|
+
// ============================================================
|
|
144
|
+
object Animation {
|
|
145
|
+
const val DURATION_SHORT = 200L // Quick transitions
|
|
146
|
+
const val DURATION_MEDIUM = 300L // Standard transitions
|
|
147
|
+
const val DURATION_LONG = 500L // Emphasized transitions
|
|
148
|
+
const val DURATION_EXTRA_LONG = 700L // Hero transitions
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ============================================================
|
|
152
|
+
// OPACITY/ALPHA VALUES
|
|
153
|
+
// ============================================================
|
|
154
|
+
object Opacity {
|
|
155
|
+
const val DISABLED = 0.38f // Disabled state
|
|
156
|
+
const val MEDIUM = 0.60f // Medium emphasis
|
|
157
|
+
const val HIGH = 0.87f // High emphasis
|
|
158
|
+
const val FULL = 1.0f // Full opacity
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============================================================
|
|
162
|
+
// HELPER FUNCTIONS
|
|
163
|
+
// ============================================================
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if the device is in dark mode.
|
|
167
|
+
*
|
|
168
|
+
* @param context Android context
|
|
169
|
+
* @return true if dark mode is enabled
|
|
170
|
+
*/
|
|
171
|
+
fun isDarkMode(context: Context): Boolean {
|
|
172
|
+
val nightModeFlags = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
|
173
|
+
return nightModeFlags == Configuration.UI_MODE_NIGHT_YES
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get surface color based on current theme.
|
|
178
|
+
*/
|
|
179
|
+
@ColorInt
|
|
180
|
+
fun getSurfaceColor(context: Context): Int {
|
|
181
|
+
return if (isDarkMode(context)) DarkColors.SURFACE else LightColors.SURFACE
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get surface variant color based on current theme.
|
|
186
|
+
*/
|
|
187
|
+
@ColorInt
|
|
188
|
+
fun getSurfaceVariantColor(context: Context): Int {
|
|
189
|
+
return if (isDarkMode(context)) DarkColors.SURFACE_VARIANT else LightColors.SURFACE_VARIANT
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get error color based on current theme.
|
|
194
|
+
*/
|
|
195
|
+
@ColorInt
|
|
196
|
+
fun getErrorColor(context: Context): Int {
|
|
197
|
+
return if (isDarkMode(context)) DarkColors.ERROR else LightColors.ERROR
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get error container color based on current theme.
|
|
202
|
+
*/
|
|
203
|
+
@ColorInt
|
|
204
|
+
fun getErrorContainerColor(context: Context): Int {
|
|
205
|
+
return if (isDarkMode(context)) DarkColors.ERROR_CONTAINER else LightColors.ERROR_CONTAINER
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get on-error color (text on error background) based on current theme.
|
|
210
|
+
*/
|
|
211
|
+
@ColorInt
|
|
212
|
+
fun getOnErrorColor(context: Context): Int {
|
|
213
|
+
return if (isDarkMode(context)) DarkColors.ON_ERROR else LightColors.ON_ERROR
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get on-error-container color based on current theme.
|
|
218
|
+
*/
|
|
219
|
+
@ColorInt
|
|
220
|
+
fun getOnErrorContainerColor(context: Context): Int {
|
|
221
|
+
return if (isDarkMode(context)) DarkColors.ON_ERROR_CONTAINER else LightColors.ON_ERROR_CONTAINER
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get primary text color based on current theme.
|
|
226
|
+
*/
|
|
227
|
+
@ColorInt
|
|
228
|
+
fun getOnSurfaceColor(context: Context): Int {
|
|
229
|
+
return if (isDarkMode(context)) DarkColors.ON_SURFACE else LightColors.ON_SURFACE
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get secondary text color based on current theme.
|
|
234
|
+
*/
|
|
235
|
+
@ColorInt
|
|
236
|
+
fun getOnSurfaceVariantColor(context: Context): Int {
|
|
237
|
+
return if (isDarkMode(context)) DarkColors.ON_SURFACE_VARIANT else LightColors.ON_SURFACE_VARIANT
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get outline color (dividers, borders) based on current theme.
|
|
242
|
+
*/
|
|
243
|
+
@ColorInt
|
|
244
|
+
fun getOutlineColor(context: Context): Int {
|
|
245
|
+
return if (isDarkMode(context)) DarkColors.OUTLINE else LightColors.OUTLINE
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get primary color based on current theme.
|
|
250
|
+
*/
|
|
251
|
+
@ColorInt
|
|
252
|
+
fun getPrimaryColor(context: Context): Int {
|
|
253
|
+
return if (isDarkMode(context)) DarkColors.PRIMARY else LightColors.PRIMARY
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get on-primary color (text on primary background) based on current theme.
|
|
258
|
+
*/
|
|
259
|
+
@ColorInt
|
|
260
|
+
fun getOnPrimaryColor(context: Context): Int {
|
|
261
|
+
return if (isDarkMode(context)) DarkColors.ON_PRIMARY else LightColors.ON_PRIMARY
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get primary container color based on current theme.
|
|
266
|
+
*/
|
|
267
|
+
@ColorInt
|
|
268
|
+
fun getPrimaryContainerColor(context: Context): Int {
|
|
269
|
+
return if (isDarkMode(context)) DarkColors.PRIMARY_CONTAINER else LightColors.PRIMARY_CONTAINER
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get on-primary-container color based on current theme.
|
|
274
|
+
*/
|
|
275
|
+
@ColorInt
|
|
276
|
+
fun getOnPrimaryContainerColor(context: Context): Int {
|
|
277
|
+
return if (isDarkMode(context)) DarkColors.ON_PRIMARY_CONTAINER else LightColors.ON_PRIMARY_CONTAINER
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get secondary color based on current theme.
|
|
282
|
+
*/
|
|
283
|
+
@ColorInt
|
|
284
|
+
fun getSecondaryColor(context: Context): Int {
|
|
285
|
+
return if (isDarkMode(context)) DarkColors.SECONDARY else LightColors.SECONDARY
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get on-secondary color based on current theme.
|
|
290
|
+
*/
|
|
291
|
+
@ColorInt
|
|
292
|
+
fun getOnSecondaryColor(context: Context): Int {
|
|
293
|
+
return if (isDarkMode(context)) DarkColors.ON_SECONDARY else LightColors.ON_SECONDARY
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Convert DP to pixels.
|
|
298
|
+
*
|
|
299
|
+
* @param context Android context
|
|
300
|
+
* @param dp Value in density-independent pixels
|
|
301
|
+
* @return Value in pixels
|
|
302
|
+
*/
|
|
303
|
+
fun dpToPx(context: Context, dp: Int): Int {
|
|
304
|
+
return (dp * context.resources.displayMetrics.density).toInt()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
package io.yourname.androidproject.security
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.hardware.Sensor
|
|
5
|
+
import android.hardware.SensorManager
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import io.yourname.androidproject.utils.BridgeUtils
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Local emulator detection based on Build properties and hardware features.
|
|
11
|
+
* No external telemetry or logging to third parties.
|
|
12
|
+
*/
|
|
13
|
+
object EmulatorDetector {
|
|
14
|
+
private const val TAG = "EmulatorDetector"
|
|
15
|
+
|
|
16
|
+
// Known emulator indicators
|
|
17
|
+
private val EMULATOR_PROPERTIES = setOf(
|
|
18
|
+
"sdk", "google_sdk", "sdk_gphone", "sdk_google",
|
|
19
|
+
"vbox", "goldfish", "ranchu", "generic"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
// Whitelist: Known legitimate patterns that contain 'sdk' but are NOT emulators
|
|
23
|
+
// These are excluded from emulator detection to prevent false positives
|
|
24
|
+
private val LEGITIMATE_SDK_PATTERNS = setOf(
|
|
25
|
+
"mt", "mediatek", "helio", "qcom", "qualcomm", "snapdragon"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
private val SUSPICIOUS_MANUFACTURERS = setOf(
|
|
29
|
+
"Genymotion", "unknown", "Generic"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
private val SUSPICIOUS_MODELS = setOf(
|
|
33
|
+
"sdk", "google_sdk", "Android SDK built for x86",
|
|
34
|
+
"Android SDK built for arm", "Emulator"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Performs multiple checks to detect if running on an emulator.
|
|
39
|
+
* Returns true if device shows emulator characteristics.
|
|
40
|
+
*/
|
|
41
|
+
fun isEmulator(context: Context): Boolean {
|
|
42
|
+
try {
|
|
43
|
+
val buildCheck = checkBuildProperties()
|
|
44
|
+
val hardwareCheck = checkHardwareFeatures(context)
|
|
45
|
+
val fingerprintCheck = checkFingerprint()
|
|
46
|
+
|
|
47
|
+
val checks = mapOf("Build" to buildCheck, "Hardware" to hardwareCheck, "Fingerprint" to fingerprintCheck)
|
|
48
|
+
val isEmulator = checks.values.any { it }
|
|
49
|
+
|
|
50
|
+
val fired = checks.filter { it.value }.keys
|
|
51
|
+
BridgeUtils.logDebug(TAG, "Emulator check results: $checks — fired: $fired")
|
|
52
|
+
BridgeUtils.logDebug(TAG, "Build info: FINGERPRINT=${Build.FINGERPRINT}, MODEL=${Build.MODEL}, MANUFACTURER=${Build.MANUFACTURER}, HARDWARE=${Build.HARDWARE}, DEVICE=${Build.DEVICE}, PRODUCT=${Build.PRODUCT}")
|
|
53
|
+
|
|
54
|
+
if (isEmulator) {
|
|
55
|
+
BridgeUtils.logDebug(TAG, "EMULATOR DETECTED: $fired")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return isEmulator
|
|
59
|
+
} catch (e: Exception) {
|
|
60
|
+
BridgeUtils.logError(TAG, "Error during emulator detection: ${e.message}", e)
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check Build properties for emulator indicators
|
|
67
|
+
*/
|
|
68
|
+
private fun checkBuildProperties(): Boolean {
|
|
69
|
+
val fingerprint = Build.FINGERPRINT.lowercase()
|
|
70
|
+
val model = Build.MODEL.lowercase()
|
|
71
|
+
val manufacturer = Build.MANUFACTURER.lowercase()
|
|
72
|
+
val brand = Build.BRAND.lowercase()
|
|
73
|
+
val device = Build.DEVICE.lowercase()
|
|
74
|
+
val product = Build.PRODUCT.lowercase()
|
|
75
|
+
val hardware = Build.HARDWARE.lowercase()
|
|
76
|
+
|
|
77
|
+
BridgeUtils.logDebug(TAG, "Build properties - fingerprint: $fingerprint, model: $model, manufacturer: $manufacturer")
|
|
78
|
+
BridgeUtils.logDebug(TAG, "Build properties - brand: $brand, device: $device, product: $product, hardware: $hardware")
|
|
79
|
+
|
|
80
|
+
// Check for emulator-specific strings in Build properties
|
|
81
|
+
var sdkMatchFound = false
|
|
82
|
+
val hasEmulatorProperty = EMULATOR_PROPERTIES.any {
|
|
83
|
+
val found = fingerprint.contains(it) ||
|
|
84
|
+
model.contains(it) ||
|
|
85
|
+
manufacturer.contains(it) ||
|
|
86
|
+
brand.contains(it) ||
|
|
87
|
+
device.contains(it) ||
|
|
88
|
+
product.contains(it) ||
|
|
89
|
+
hardware.contains(it)
|
|
90
|
+
if (found) {
|
|
91
|
+
BridgeUtils.logDebug(TAG, "Found emulator property: '$it'")
|
|
92
|
+
// Track if 'sdk' was the match (for whitelist check)
|
|
93
|
+
if (it == "sdk") {
|
|
94
|
+
sdkMatchFound = true
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
found
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// If flagged as emulator ONLY because of 'sdk', check if it's a legitimate device
|
|
101
|
+
val isLegitimateDevice = if (sdkMatchFound && hasEmulatorProperty) {
|
|
102
|
+
val isLegit = LEGITIMATE_SDK_PATTERNS.any {
|
|
103
|
+
manufacturer.contains(it) ||
|
|
104
|
+
brand.contains(it) ||
|
|
105
|
+
hardware.contains(it) ||
|
|
106
|
+
model.contains(it)
|
|
107
|
+
}
|
|
108
|
+
if (isLegit) {
|
|
109
|
+
BridgeUtils.logDebug(TAG, "Device flagged by 'sdk' but matches legitimate pattern - likely MediaTek/Qualcomm chipset")
|
|
110
|
+
}
|
|
111
|
+
isLegit
|
|
112
|
+
} else {
|
|
113
|
+
false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check suspicious manufacturer/model
|
|
117
|
+
val hasSuspiciousManufacturer = SUSPICIOUS_MANUFACTURERS.any {
|
|
118
|
+
val found = manufacturer.contains(it.lowercase())
|
|
119
|
+
if (found) {
|
|
120
|
+
BridgeUtils.logDebug(TAG, "Found suspicious manufacturer: '$it' in '$manufacturer'")
|
|
121
|
+
}
|
|
122
|
+
found
|
|
123
|
+
}
|
|
124
|
+
val hasSuspiciousModel = SUSPICIOUS_MODELS.any {
|
|
125
|
+
val found = model.contains(it.lowercase())
|
|
126
|
+
if (found) {
|
|
127
|
+
BridgeUtils.logDebug(TAG, "Found suspicious model: '$it' in '$model'")
|
|
128
|
+
}
|
|
129
|
+
found
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Final result: emulator if properties match AND not whitelisted, OR suspicious manufacturer/model
|
|
133
|
+
val result = (hasEmulatorProperty && !isLegitimateDevice) || hasSuspiciousManufacturer || hasSuspiciousModel
|
|
134
|
+
BridgeUtils.logDebug(TAG, "checkBuildProperties result: $result (emulatorProp=$hasEmulatorProperty, legitimateDevice=$isLegitimateDevice, suspiciousManufacturer=$hasSuspiciousManufacturer, suspiciousModel=$hasSuspiciousModel)")
|
|
135
|
+
return result
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check for missing hardware sensors (common in emulators)
|
|
140
|
+
*/
|
|
141
|
+
private fun checkHardwareFeatures(context: Context): Boolean {
|
|
142
|
+
try {
|
|
143
|
+
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager
|
|
144
|
+
?: return true
|
|
145
|
+
|
|
146
|
+
// Real devices typically have these sensors
|
|
147
|
+
val hasAccelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null
|
|
148
|
+
val hasGyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null
|
|
149
|
+
val hasProximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) != null
|
|
150
|
+
val hasLight = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) != null
|
|
151
|
+
|
|
152
|
+
BridgeUtils.logDebug(TAG, "Sensor check - Accelerometer: $hasAccelerometer, Gyroscope: $hasGyroscope, Proximity: $hasProximity, Light: $hasLight")
|
|
153
|
+
|
|
154
|
+
// Count missing sensors
|
|
155
|
+
val missingSensors = listOf(hasAccelerometer, hasGyroscope, hasProximity, hasLight)
|
|
156
|
+
.count { !it }
|
|
157
|
+
|
|
158
|
+
// If 2 or more critical sensors missing, likely an emulator
|
|
159
|
+
val result = missingSensors >= 2
|
|
160
|
+
BridgeUtils.logDebug(TAG, "checkHardwareFeatures result: $result (missing $missingSensors/4 sensors)")
|
|
161
|
+
return result
|
|
162
|
+
} catch (e: Exception) {
|
|
163
|
+
BridgeUtils.logError(TAG, "Error checking hardware features", e)
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check Build.FINGERPRINT for emulator indicators (generic/unknown prefix, test-keys)
|
|
170
|
+
*/
|
|
171
|
+
private fun checkFingerprint(): Boolean {
|
|
172
|
+
return try {
|
|
173
|
+
val fingerprint = Build.FINGERPRINT
|
|
174
|
+
val hasGeneric = fingerprint.startsWith("generic")
|
|
175
|
+
val hasUnknown = fingerprint.startsWith("unknown")
|
|
176
|
+
val hasTestKeys = fingerprint.lowercase().contains("test-keys")
|
|
177
|
+
|
|
178
|
+
val result = hasGeneric || hasUnknown || hasTestKeys
|
|
179
|
+
BridgeUtils.logDebug(TAG, "checkFingerprint result: $result (generic=$hasGeneric, unknown=$hasUnknown, testKeys=$hasTestKeys)")
|
|
180
|
+
BridgeUtils.logDebug(TAG, "Full FINGERPRINT: $fingerprint")
|
|
181
|
+
result
|
|
182
|
+
} catch (e: Exception) {
|
|
183
|
+
BridgeUtils.logError(TAG, "Error checking fingerprint", e)
|
|
184
|
+
false
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get detailed emulator detection info for debugging
|
|
190
|
+
*/
|
|
191
|
+
fun getEmulatorCheckDetails(context: Context): Map<String, Any> {
|
|
192
|
+
return mapOf(
|
|
193
|
+
"isEmulator" to isEmulator(context),
|
|
194
|
+
"buildCheck" to checkBuildProperties(),
|
|
195
|
+
"hardwareCheck" to checkHardwareFeatures(context),
|
|
196
|
+
"fingerprintCheck" to checkFingerprint(),
|
|
197
|
+
"fingerprint" to Build.FINGERPRINT,
|
|
198
|
+
"model" to Build.MODEL,
|
|
199
|
+
"manufacturer" to Build.MANUFACTURER,
|
|
200
|
+
"hardware" to Build.HARDWARE
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
}
|