Introduction
Detecting whether an Android app is running on an emulator is important for security (preventing reverse engineering), analytics (filtering test traffic), and licensing (blocking pirated copies). Emulators have distinct characteristics in Build properties, hardware sensors, telephony, and system files. No single check is definitive — sophisticated emulators can spoof individual properties — so combining multiple signals provides the most reliable detection. Common approaches include checking Build.FINGERPRINT, looking for emulator-specific files, testing telephony properties, and verifying hardware sensor availability.
Checking Build Properties
1import android.os.Build;
2
3public class EmulatorDetector {
4
5 public static boolean isEmulator() {
6 return Build.FINGERPRINT.startsWith("generic")
7|| Build.FINGERPRINT.startsWith("unknown") || Build.MODEL.contains("google_sdk") || Build.MODEL.contains("Emulator") || Build.MODEL.contains("Android SDK built for x86") || Build.MANUFACTURER.contains("Genymotion") || Build.BRAND.startsWith("generic") || Build.DEVICE.startsWith("generic") || "google_sdk".equals(Build.PRODUCT) || "sdk_gphone".equals(Build.PRODUCT) || Build.HARDWARE.contains("goldfish") || Build.HARDWARE.contains("ranchu"); } } // Usage in an Activity if (EmulatorDetector.isEmulator()) { Log.w("Security", "Running on emulator"); } ``` `Build.FINGERPRINT`, `Build.HARDWARE`, and `Build.PRODUCT` contain emulator-specific values like "generic", "goldfish" (QEMU), or "ranchu" (newer QEMU). This is the most common first check. ## Checking Telephony Properties ```java import android.content.Context; import android.telephony.TelephonyManager; public static boolean hasEmulatorTelephony(Context context) { TelephonyManager tm = (TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE); if (tm == null) return false; String networkOperator = tm.getNetworkOperatorName(); // Emulators often report "Android" as the carrier if ("Android".equals(networkOperator)) return true; // Check for emulator phone numbers String phoneNumber = null; try { phoneNumber = tm.getLine1Number(); } catch (SecurityException e) { // Permission not granted } if (phoneNumber != null) { // Emulators use specific number patterns if (phoneNumber.startsWith("1555521") || phoneNumber.equals("15555215554")) { return true; } } return false; } ``` Android emulators assign default phone numbers like `15555215554` and use "Android" as the network operator name. Real devices have carrier-specific operator names. ## Checking for Emulator Files ```java import java.io.File; public static boolean hasEmulatorFiles() { String[] knownFiles = { "/dev/socket/qemud", "/dev/qemu_pipe", "/system/lib/libc_malloc_debug_qemu.so", "/sys/qemu_trace", "/system/bin/qemu-props", }; for (String path : knownFiles) { if (new File(path).exists()) { return true; } } return false; } ``` QEMU-based emulators create specific device files and libraries. Checking for these files provides evidence of an emulator environment. ## Checking Hardware Sensors ```java import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorManager; public static boolean lacksPhysicalSensors(Context context) { SensorManager sm = (SensorManager) context.getSystemService( Context.SENSOR_SERVICE); // Real devices have accelerometer, gyroscope, etc. // Emulators may lack or simulate sensors poorly Sensor accelerometer = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); Sensor gyroscope = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE); Sensor magnetometer = sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); // No sensors at all strongly suggests emulator int sensorCount = sm.getSensorList(Sensor.TYPE_ALL).size(); if (sensorCount == 0) return true; // Very few sensors also suggests emulator if (accelerometer == null && gyroscope == null && magnetometer == null) { return true; } return false; } ``` Physical devices have multiple hardware sensors. Emulators often have none or simulate only a few. Checking sensor count provides a hardware-based signal. ## Combined Detection Score ```kotlin // Kotlin version with weighted scoring object EmulatorDetector { fun getEmulatorScore(context: Context): Int { var score = 0 // Build properties (weight: 3 each) if (Build.FINGERPRINT.startsWith("generic")) score += 3 if (Build.HARDWARE.contains("goldfish") ||
8 Build.HARDWARE.contains("ranchu")) score += 3
9 if (Build.MODEL.contains("Emulator") ||
10 Build.MODEL.contains("sdk")) score += 3
11 if (Build.MANUFACTURER.contains("Genymotion")) score += 3
12
13 // File checks (weight: 2 each)
14 if (File("/dev/qemu_pipe").exists()) score += 2
15 if (File("/dev/socket/qemud").exists()) score += 2
16
17 // Sensor check (weight: 3)
18 val sm = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
19 if (sm.getSensorList(Sensor.TYPE_ALL).size < 3) score += 3
20
21 return score
22 }
23
24 fun isEmulator(context: Context): Boolean {
25 return getEmulatorScore(context) >= 5
26 }
27}
28
29// Usage
30if (EmulatorDetector.isEmulator(applicationContext)) {
31 // Take action: disable features, show warning, or log
32}
A scoring approach is more robust than any single check. Each signal adds to the score, and a threshold determines the final verdict. This reduces false positives from devices with unusual build properties.
Common Pitfalls
Relying on a single Build property check: Sophisticated emulators and custom ROMs can spoof individual Build properties. Checking only Build.FINGERPRINT will miss Genymotion, Nox, and custom AOSP builds. Always combine multiple detection signals for reliability.
Blocking the app entirely on emulator detection: Legitimate users run apps on emulators for accessibility, desktop use, or development. Completely blocking emulators angers legitimate users. Instead, disable sensitive features (payments, DRM) while allowing basic functionality.
Not testing on real devices with unusual Build values: Some budget Android devices have generic-looking Build.FINGERPRINT or Build.MODEL values that trigger false positives. Test your detection logic across a range of real devices to calibrate thresholds.
Checking sensors without handling permission denials: On Android 6+, some sensor and telephony checks require runtime permissions. If the user denies permission, the check returns null, which you might incorrectly interpret as "emulator." Handle permission denial separately from missing hardware.
Assuming emulator detection prevents all tampering: Determined attackers can patch your APK to skip emulator checks, use Frida to hook detection methods, or run on rooted devices that do not trigger emulator signals. Emulator detection is one layer of defense, not a complete security solution.
Summary
Check Build.FINGERPRINT, Build.HARDWARE, and Build.PRODUCT for emulator-specific values
Look for QEMU files like /dev/qemu_pipe and /dev/socket/qemud
Check telephony properties for default emulator phone numbers and carrier names
Verify hardware sensor availability — emulators typically have few or no sensors
Combine multiple signals with a weighted scoring system for robust detection
Never rely on a single check — combine build properties, files, sensors, and telephony