@capgo/capacitor-device-info 8.0.0

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.
@@ -0,0 +1,765 @@
1
+ package app.capgo.deviceinfo;
2
+
3
+ import android.app.ActivityManager;
4
+ import android.content.Context;
5
+ import android.content.Intent;
6
+ import android.content.IntentFilter;
7
+ import android.content.pm.ConfigurationInfo;
8
+ import android.hardware.Sensor;
9
+ import android.hardware.SensorEvent;
10
+ import android.hardware.SensorEventListener;
11
+ import android.hardware.SensorManager;
12
+ import android.opengl.EGL14;
13
+ import android.opengl.EGLConfig;
14
+ import android.opengl.EGLContext;
15
+ import android.opengl.EGLDisplay;
16
+ import android.opengl.EGLSurface;
17
+ import android.opengl.GLES20;
18
+ import android.os.BatteryManager;
19
+ import android.os.Build;
20
+ import android.os.Handler;
21
+ import android.os.HandlerThread;
22
+ import android.os.PowerManager;
23
+ import android.os.StatFs;
24
+ import com.getcapacitor.JSObject;
25
+ import java.io.BufferedReader;
26
+ import java.io.File;
27
+ import java.io.FileReader;
28
+ import java.io.IOException;
29
+ import java.util.ArrayList;
30
+ import java.util.HashMap;
31
+ import java.util.List;
32
+ import java.util.Locale;
33
+ import java.util.Map;
34
+ import java.util.concurrent.CountDownLatch;
35
+ import java.util.concurrent.TimeUnit;
36
+ import org.json.JSONArray;
37
+ import org.json.JSONException;
38
+
39
+ public class DeviceInfo {
40
+
41
+ private CpuTicks previousCpuTicks;
42
+ private String cachedCpuModel;
43
+ private Long cachedMaxCpuFrequencyHz;
44
+ private JSObject cachedGpuInfo;
45
+
46
+ public JSObject getInfo(Context context) {
47
+ JSObject result = new JSObject();
48
+ result.put("timestamp", currentTimestamp());
49
+ result.put("platform", "android");
50
+ result.put("cpu", getCpuInfo());
51
+ result.put("memory", getMemoryInfo(context));
52
+ result.put("storage", getStorageInfo(context));
53
+ result.put("gpu", getGpuInfo(context));
54
+ result.put("thermalState", getThermalState(context));
55
+ result.put("lowPowerMode", isLowPowerMode(context));
56
+ result.put("sensors", getOnboardSensorsInfo(context));
57
+ return result;
58
+ }
59
+
60
+ public String getPluginVersion() {
61
+ return "8.0.0";
62
+ }
63
+
64
+ private JSObject getCpuInfo() {
65
+ JSObject cpu = new JSObject();
66
+ int cores = Runtime.getRuntime().availableProcessors();
67
+ cpu.put("cores", cores);
68
+ cpu.put("activeCores", cores);
69
+ cpu.put("architecture", getArchitecture());
70
+
71
+ String model = getCpuModel();
72
+ if (model != null && !model.isEmpty()) {
73
+ cpu.put("model", model);
74
+ }
75
+
76
+ Double usagePercent = getCpuUsagePercent();
77
+ if (usagePercent != null) {
78
+ cpu.put("usagePercent", usagePercent);
79
+ }
80
+
81
+ Long maxFrequencyHz = getMaxCpuFrequencyHz();
82
+ if (maxFrequencyHz != null) {
83
+ cpu.put("maxFrequencyHz", maxFrequencyHz.doubleValue());
84
+ }
85
+ putIfNotNull(cpu, "temperatureCelsius", getCpuTemperatureCelsius());
86
+
87
+ return cpu;
88
+ }
89
+
90
+ private JSObject getMemoryInfo(Context context) {
91
+ JSObject memory = new JSObject();
92
+ ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
93
+ Runtime runtime = Runtime.getRuntime();
94
+ long appUsedBytes = runtime.totalMemory() - runtime.freeMemory();
95
+ long appLimitBytes = runtime.maxMemory();
96
+
97
+ memory.put("appUsedBytes", (double) appUsedBytes);
98
+ memory.put("appLimitBytes", (double) appLimitBytes);
99
+ memory.put("pressure", "unknown");
100
+
101
+ if (activityManager == null) {
102
+ return memory;
103
+ }
104
+
105
+ ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
106
+ activityManager.getMemoryInfo(memoryInfo);
107
+ long totalBytes = memoryInfo.totalMem;
108
+ long freeBytes = memoryInfo.availMem;
109
+ long usedBytes = Math.max(totalBytes - freeBytes, 0);
110
+
111
+ memory.put("totalBytes", (double) totalBytes);
112
+ memory.put("freeBytes", (double) freeBytes);
113
+ memory.put("usedBytes", (double) usedBytes);
114
+ memory.put("usedPercent", percent(usedBytes, totalBytes));
115
+ memory.put("lowMemory", memoryInfo.lowMemory);
116
+ memory.put("pressure", memoryPressure(memoryInfo));
117
+
118
+ return memory;
119
+ }
120
+
121
+ private JSObject getStorageInfo(Context context) {
122
+ JSObject storage = new JSObject();
123
+ File filesDir = context.getFilesDir();
124
+ File target = filesDir != null ? filesDir : context.getDataDir();
125
+ StatFs statFs = new StatFs(target.getAbsolutePath());
126
+ long totalBytes = statFs.getTotalBytes();
127
+ long freeBytes = statFs.getAvailableBytes();
128
+ long usedBytes = Math.max(totalBytes - freeBytes, 0);
129
+
130
+ storage.put("totalBytes", (double) totalBytes);
131
+ storage.put("freeBytes", (double) freeBytes);
132
+ storage.put("usedBytes", (double) usedBytes);
133
+ storage.put("usedPercent", percent(usedBytes, totalBytes));
134
+ return storage;
135
+ }
136
+
137
+ private synchronized JSObject getGpuInfo(Context context) {
138
+ if (cachedGpuInfo != null) {
139
+ return gpuInfoWithDynamicValues(cachedGpuInfo);
140
+ }
141
+
142
+ JSObject gpu = new JSObject();
143
+ gpu.put("api", "opengl");
144
+
145
+ ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
146
+ if (activityManager != null) {
147
+ ConfigurationInfo config = activityManager.getDeviceConfigurationInfo();
148
+ if (config != null) {
149
+ gpu.put("version", formatGlEsVersion(config.reqGlEsVersion));
150
+ }
151
+ }
152
+
153
+ GpuStrings gpuStrings = readGpuStrings();
154
+ if (gpuStrings.vendor != null) {
155
+ gpu.put("vendor", gpuStrings.vendor);
156
+ }
157
+ if (gpuStrings.renderer != null) {
158
+ gpu.put("renderer", gpuStrings.renderer);
159
+ }
160
+ if (gpuStrings.version != null) {
161
+ gpu.put("version", gpuStrings.version);
162
+ }
163
+ if (gpuStrings.maxTextureSize > 0) {
164
+ gpu.put("maxTextureSize", gpuStrings.maxTextureSize);
165
+ }
166
+
167
+ cachedGpuInfo = gpu;
168
+ return gpuInfoWithDynamicValues(gpu);
169
+ }
170
+
171
+ private JSObject gpuInfoWithDynamicValues(JSObject staticGpuInfo) {
172
+ JSObject gpu = copyObject(staticGpuInfo);
173
+ putIfNotNull(gpu, "temperatureCelsius", getGpuTemperatureCelsius());
174
+ return gpu;
175
+ }
176
+
177
+ private JSObject getOnboardSensorsInfo(Context context) {
178
+ JSObject sensors = new JSObject();
179
+ JSONArray availableSensors = new JSONArray();
180
+ JSONArray readings = new JSONArray();
181
+ SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
182
+
183
+ if (sensorManager != null) {
184
+ for (Sensor sensor : sensorManager.getSensorList(Sensor.TYPE_ALL)) {
185
+ availableSensors.put(sensorDescriptor(sensor));
186
+ }
187
+
188
+ Map<Integer, Double> sensorValues = readSensorValues(sensorManager, commonEnvironmentalSensorTypes());
189
+ addSensorReading(
190
+ sensorManager,
191
+ sensorValues,
192
+ sensors,
193
+ readings,
194
+ Sensor.TYPE_AMBIENT_TEMPERATURE,
195
+ "ambientTemperature",
196
+ "celsius",
197
+ "ambientTemperatureCelsius"
198
+ );
199
+ addSensorReading(
200
+ sensorManager,
201
+ sensorValues,
202
+ sensors,
203
+ readings,
204
+ Sensor.TYPE_RELATIVE_HUMIDITY,
205
+ "relativeHumidity",
206
+ "percent",
207
+ "relativeHumidityPercent"
208
+ );
209
+ addSensorReading(sensorManager, sensorValues, sensors, readings, Sensor.TYPE_PRESSURE, "pressure", "hPa", "pressureHpa");
210
+ addSensorReading(sensorManager, sensorValues, sensors, readings, Sensor.TYPE_LIGHT, "light", "lux", "illuminanceLux");
211
+ addSensorReading(
212
+ sensorManager,
213
+ sensorValues,
214
+ sensors,
215
+ readings,
216
+ Sensor.TYPE_PROXIMITY,
217
+ "proximity",
218
+ "cm",
219
+ "proximityDistanceCm"
220
+ );
221
+ }
222
+
223
+ Double batteryTemperature = getBatteryTemperatureCelsius(context);
224
+ if (batteryTemperature != null) {
225
+ sensors.put("batteryTemperatureCelsius", batteryTemperature);
226
+ readings.put(readingObject("batteryTemperature", "celsius", batteryTemperature, "Battery temperature"));
227
+ }
228
+
229
+ sensors.put("availableSensors", availableSensors);
230
+ sensors.put("readings", readings);
231
+ return sensors;
232
+ }
233
+
234
+ private int[] commonEnvironmentalSensorTypes() {
235
+ return new int[] {
236
+ Sensor.TYPE_AMBIENT_TEMPERATURE,
237
+ Sensor.TYPE_RELATIVE_HUMIDITY,
238
+ Sensor.TYPE_PRESSURE,
239
+ Sensor.TYPE_LIGHT,
240
+ Sensor.TYPE_PROXIMITY
241
+ };
242
+ }
243
+
244
+ private Map<Integer, Double> readSensorValues(SensorManager sensorManager, int[] sensorTypes) {
245
+ Map<Integer, Double> values = new HashMap<>();
246
+ List<Sensor> sensors = new ArrayList<>();
247
+
248
+ for (int sensorType : sensorTypes) {
249
+ Sensor sensor = sensorManager.getDefaultSensor(sensorType);
250
+ if (sensor != null) {
251
+ sensors.add(sensor);
252
+ }
253
+ }
254
+
255
+ if (sensors.isEmpty()) {
256
+ return values;
257
+ }
258
+
259
+ CountDownLatch latch = new CountDownLatch(sensors.size());
260
+ HandlerThread handlerThread = new HandlerThread("CapgoDeviceInfoSensors");
261
+ handlerThread.start();
262
+ Handler handler = new Handler(handlerThread.getLooper());
263
+ List<SensorEventListener> listeners = new ArrayList<>();
264
+
265
+ try {
266
+ for (Sensor sensor : sensors) {
267
+ SensorEventListener listener = new SensorEventListener() {
268
+ private boolean recorded;
269
+
270
+ @Override
271
+ public void onSensorChanged(SensorEvent event) {
272
+ if (recorded || event.values.length == 0) {
273
+ return;
274
+ }
275
+
276
+ recorded = true;
277
+ synchronized (values) {
278
+ values.put(sensor.getType(), (double) event.values[0]);
279
+ }
280
+ sensorManager.unregisterListener(this);
281
+ latch.countDown();
282
+ }
283
+
284
+ @Override
285
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
286
+ };
287
+
288
+ listeners.add(listener);
289
+ if (!sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL, handler)) {
290
+ sensorManager.unregisterListener(listener);
291
+ latch.countDown();
292
+ }
293
+ }
294
+
295
+ if (!latch.await(180, TimeUnit.MILLISECONDS)) {
296
+ return values;
297
+ }
298
+ } catch (InterruptedException ignored) {
299
+ Thread.currentThread().interrupt();
300
+ } finally {
301
+ for (SensorEventListener listener : listeners) {
302
+ sensorManager.unregisterListener(listener);
303
+ }
304
+ handlerThread.quitSafely();
305
+ }
306
+
307
+ return values;
308
+ }
309
+
310
+ private void addSensorReading(
311
+ SensorManager sensorManager,
312
+ Map<Integer, Double> sensorValues,
313
+ JSObject sensors,
314
+ JSONArray readings,
315
+ int sensorType,
316
+ String type,
317
+ String unit,
318
+ String fieldName
319
+ ) {
320
+ Double value = sensorValues.get(sensorType);
321
+ if (value == null) {
322
+ return;
323
+ }
324
+
325
+ Sensor sensor = sensorManager.getDefaultSensor(sensorType);
326
+ sensors.put(fieldName, value);
327
+ readings.put(readingObject(type, unit, value, sensor != null ? sensor.getName() : null));
328
+ }
329
+
330
+ private JSObject sensorDescriptor(Sensor sensor) {
331
+ JSObject descriptor = new JSObject();
332
+ descriptor.put("type", sensorTypeLabel(sensor.getType()));
333
+ descriptor.put("platformType", sensor.getType());
334
+ descriptor.put("name", sensor.getName());
335
+ descriptor.put("vendor", sensor.getVendor());
336
+ descriptor.put("maximumRange", (double) sensor.getMaximumRange());
337
+ descriptor.put("resolution", (double) sensor.getResolution());
338
+ descriptor.put("powerMilliamp", (double) sensor.getPower());
339
+ descriptor.put("minDelayMicroseconds", sensor.getMinDelay());
340
+ descriptor.put("wakeUp", sensor.isWakeUpSensor());
341
+ return descriptor;
342
+ }
343
+
344
+ private JSObject readingObject(String type, String unit, Double value, String name) {
345
+ JSObject reading = new JSObject();
346
+ reading.put("type", type);
347
+ reading.put("unit", unit);
348
+ reading.put("value", value);
349
+ reading.put("timestamp", currentTimestamp());
350
+ if (name != null && !name.isEmpty()) {
351
+ reading.put("name", name);
352
+ }
353
+ return reading;
354
+ }
355
+
356
+ private String sensorTypeLabel(int sensorType) {
357
+ switch (sensorType) {
358
+ case Sensor.TYPE_ACCELEROMETER:
359
+ return "accelerometer";
360
+ case Sensor.TYPE_AMBIENT_TEMPERATURE:
361
+ return "ambientTemperature";
362
+ case Sensor.TYPE_GAME_ROTATION_VECTOR:
363
+ return "gameRotationVector";
364
+ case Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR:
365
+ return "geomagneticRotationVector";
366
+ case Sensor.TYPE_GRAVITY:
367
+ return "gravity";
368
+ case Sensor.TYPE_GYROSCOPE:
369
+ return "gyroscope";
370
+ case Sensor.TYPE_LIGHT:
371
+ return "light";
372
+ case Sensor.TYPE_LINEAR_ACCELERATION:
373
+ return "linearAcceleration";
374
+ case Sensor.TYPE_MAGNETIC_FIELD:
375
+ return "magneticField";
376
+ case Sensor.TYPE_PRESSURE:
377
+ return "pressure";
378
+ case Sensor.TYPE_PROXIMITY:
379
+ return "proximity";
380
+ case Sensor.TYPE_RELATIVE_HUMIDITY:
381
+ return "relativeHumidity";
382
+ case Sensor.TYPE_ROTATION_VECTOR:
383
+ return "rotationVector";
384
+ case Sensor.TYPE_SIGNIFICANT_MOTION:
385
+ return "significantMotion";
386
+ case Sensor.TYPE_STEP_COUNTER:
387
+ return "stepCounter";
388
+ case Sensor.TYPE_STEP_DETECTOR:
389
+ return "stepDetector";
390
+ default:
391
+ return "android_" + sensorType;
392
+ }
393
+ }
394
+
395
+ private Double getBatteryTemperatureCelsius(Context context) {
396
+ Intent intent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
397
+ if (intent == null) {
398
+ return null;
399
+ }
400
+
401
+ int temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, Integer.MIN_VALUE);
402
+ if (temperature == Integer.MIN_VALUE) {
403
+ return null;
404
+ }
405
+
406
+ return temperature / 10.0;
407
+ }
408
+
409
+ private Double getCpuTemperatureCelsius() {
410
+ return getThermalZoneTemperature("cpu", "soc", "ap", "tsens");
411
+ }
412
+
413
+ private Double getGpuTemperatureCelsius() {
414
+ return getThermalZoneTemperature("gpu", "gpuss");
415
+ }
416
+
417
+ private Double getThermalZoneTemperature(String... typeHints) {
418
+ File thermalRoot = new File("/sys/class/thermal");
419
+ File[] zones = thermalRoot.listFiles((dir, name) -> name.startsWith("thermal_zone"));
420
+ if (zones == null) {
421
+ return null;
422
+ }
423
+
424
+ for (File zone : zones) {
425
+ String type = readFirstString(new File(zone, "type"));
426
+ if (!matchesThermalType(type, typeHints)) {
427
+ continue;
428
+ }
429
+
430
+ Double temperature = normalizeThermalTemperature(readFirstString(new File(zone, "temp")));
431
+ if (temperature != null) {
432
+ return temperature;
433
+ }
434
+ }
435
+
436
+ return null;
437
+ }
438
+
439
+ private String getArchitecture() {
440
+ if (Build.SUPPORTED_ABIS != null && Build.SUPPORTED_ABIS.length > 0) {
441
+ return Build.SUPPORTED_ABIS[0];
442
+ }
443
+ return "unknown";
444
+ }
445
+
446
+ private String getCpuModel() {
447
+ if (cachedCpuModel != null) {
448
+ return cachedCpuModel;
449
+ }
450
+
451
+ cachedCpuModel = readCpuModel();
452
+ return cachedCpuModel;
453
+ }
454
+
455
+ private String readCpuModel() {
456
+ try (BufferedReader reader = new BufferedReader(new FileReader("/proc/cpuinfo"))) {
457
+ String line;
458
+ while ((line = reader.readLine()) != null) {
459
+ String lower = line.toLowerCase(Locale.US);
460
+ if (lower.startsWith("hardware") || lower.startsWith("processor") || lower.startsWith("model name")) {
461
+ int separator = line.indexOf(':');
462
+ if (separator >= 0 && separator + 1 < line.length()) {
463
+ String value = line.substring(separator + 1).trim();
464
+ if (!value.isEmpty()) {
465
+ return value;
466
+ }
467
+ }
468
+ }
469
+ }
470
+ } catch (IOException ignored) {
471
+ // Some devices restrict /proc/cpuinfo. Other CPU fields still work.
472
+ }
473
+ return null;
474
+ }
475
+
476
+ private synchronized Double getCpuUsagePercent() {
477
+ CpuTicks currentTicks = readCpuTicks();
478
+ if (currentTicks == null) {
479
+ return null;
480
+ }
481
+
482
+ try {
483
+ if (previousCpuTicks == null) {
484
+ return null;
485
+ }
486
+
487
+ long totalDelta = currentTicks.total - previousCpuTicks.total;
488
+ long idleDelta = currentTicks.idle - previousCpuTicks.idle;
489
+ if (totalDelta <= 0) {
490
+ return null;
491
+ }
492
+
493
+ return clampPercent(((double) (totalDelta - idleDelta) / (double) totalDelta) * 100.0);
494
+ } finally {
495
+ previousCpuTicks = currentTicks;
496
+ }
497
+ }
498
+
499
+ private CpuTicks readCpuTicks() {
500
+ try (BufferedReader reader = new BufferedReader(new FileReader("/proc/stat"))) {
501
+ String line = reader.readLine();
502
+ if (line == null || !line.startsWith("cpu ")) {
503
+ return null;
504
+ }
505
+
506
+ String[] parts = line.trim().split("\\s+");
507
+ long total = 0;
508
+ for (int i = 1; i < parts.length; i++) {
509
+ total += parseLong(parts[i]);
510
+ }
511
+
512
+ long idle = parts.length > 4 ? parseLong(parts[4]) : 0;
513
+ long ioWait = parts.length > 5 ? parseLong(parts[5]) : 0;
514
+ return new CpuTicks(total, idle + ioWait);
515
+ } catch (IOException ignored) {
516
+ return null;
517
+ }
518
+ }
519
+
520
+ private Long getMaxCpuFrequencyHz() {
521
+ if (cachedMaxCpuFrequencyHz != null) {
522
+ return cachedMaxCpuFrequencyHz;
523
+ }
524
+
525
+ long maxKHz = 0;
526
+ int cores = Math.max(Runtime.getRuntime().availableProcessors(), 1);
527
+ for (int index = 0; index < cores; index++) {
528
+ File file = new File("/sys/devices/system/cpu/cpu" + index + "/cpufreq/cpuinfo_max_freq");
529
+ Long value = readFirstLong(file);
530
+ if (value != null) {
531
+ maxKHz = Math.max(maxKHz, value);
532
+ }
533
+ }
534
+
535
+ cachedMaxCpuFrequencyHz = maxKHz > 0 ? maxKHz * 1000L : null;
536
+ return cachedMaxCpuFrequencyHz;
537
+ }
538
+
539
+ private Long readFirstLong(File file) {
540
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
541
+ return parseLong(reader.readLine());
542
+ } catch (IOException ignored) {
543
+ return null;
544
+ }
545
+ }
546
+
547
+ private String readFirstString(File file) {
548
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
549
+ return reader.readLine();
550
+ } catch (IOException ignored) {
551
+ return null;
552
+ }
553
+ }
554
+
555
+ private GpuStrings readGpuStrings() {
556
+ EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
557
+ if (display == EGL14.EGL_NO_DISPLAY) {
558
+ return new GpuStrings();
559
+ }
560
+
561
+ EGLContext context = EGL14.EGL_NO_CONTEXT;
562
+ EGLSurface surface = EGL14.EGL_NO_SURFACE;
563
+
564
+ try {
565
+ int[] version = new int[2];
566
+ if (!EGL14.eglInitialize(display, version, 0, version, 1)) {
567
+ return new GpuStrings();
568
+ }
569
+
570
+ int[] configAttributes = {
571
+ EGL14.EGL_RENDERABLE_TYPE,
572
+ EGL14.EGL_OPENGL_ES2_BIT,
573
+ EGL14.EGL_SURFACE_TYPE,
574
+ EGL14.EGL_PBUFFER_BIT,
575
+ EGL14.EGL_RED_SIZE,
576
+ 8,
577
+ EGL14.EGL_GREEN_SIZE,
578
+ 8,
579
+ EGL14.EGL_BLUE_SIZE,
580
+ 8,
581
+ EGL14.EGL_NONE
582
+ };
583
+ EGLConfig[] configs = new EGLConfig[1];
584
+ int[] configCount = new int[1];
585
+ if (!EGL14.eglChooseConfig(display, configAttributes, 0, configs, 0, configs.length, configCount, 0) || configCount[0] == 0) {
586
+ return new GpuStrings();
587
+ }
588
+
589
+ int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
590
+ context = EGL14.eglCreateContext(display, configs[0], EGL14.EGL_NO_CONTEXT, contextAttributes, 0);
591
+ if (context == EGL14.EGL_NO_CONTEXT) {
592
+ return new GpuStrings();
593
+ }
594
+
595
+ int[] surfaceAttributes = {EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE};
596
+ surface = EGL14.eglCreatePbufferSurface(display, configs[0], surfaceAttributes, 0);
597
+ if (surface == EGL14.EGL_NO_SURFACE) {
598
+ return new GpuStrings();
599
+ }
600
+
601
+ if (!EGL14.eglMakeCurrent(display, surface, surface, context)) {
602
+ return new GpuStrings();
603
+ }
604
+
605
+ GpuStrings strings = new GpuStrings();
606
+ strings.vendor = GLES20.glGetString(GLES20.GL_VENDOR);
607
+ strings.renderer = GLES20.glGetString(GLES20.GL_RENDERER);
608
+ strings.version = GLES20.glGetString(GLES20.GL_VERSION);
609
+ int[] maxTextureSize = new int[1];
610
+ GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
611
+ strings.maxTextureSize = maxTextureSize[0];
612
+ return strings;
613
+ } finally {
614
+ EGL14.eglMakeCurrent(display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
615
+ if (surface != EGL14.EGL_NO_SURFACE) {
616
+ EGL14.eglDestroySurface(display, surface);
617
+ }
618
+ if (context != EGL14.EGL_NO_CONTEXT) {
619
+ EGL14.eglDestroyContext(display, context);
620
+ }
621
+ EGL14.eglTerminate(display);
622
+ }
623
+ }
624
+
625
+ private String formatGlEsVersion(int reqGlEsVersion) {
626
+ int major = (reqGlEsVersion & 0xffff0000) >> 16;
627
+ int minor = reqGlEsVersion & 0x0000ffff;
628
+ return "OpenGL ES " + major + "." + minor;
629
+ }
630
+
631
+ private String memoryPressure(ActivityManager.MemoryInfo memoryInfo) {
632
+ if (memoryInfo.lowMemory) {
633
+ return "critical";
634
+ }
635
+ if (memoryInfo.threshold > 0 && memoryInfo.availMem < memoryInfo.threshold * 2) {
636
+ return "warning";
637
+ }
638
+ return "normal";
639
+ }
640
+
641
+ private String getThermalState(Context context) {
642
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
643
+ return "unknown";
644
+ }
645
+
646
+ PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
647
+ if (powerManager == null) {
648
+ return "unknown";
649
+ }
650
+
651
+ switch (powerManager.getCurrentThermalStatus()) {
652
+ case PowerManager.THERMAL_STATUS_NONE:
653
+ return "nominal";
654
+ case PowerManager.THERMAL_STATUS_LIGHT:
655
+ case PowerManager.THERMAL_STATUS_MODERATE:
656
+ return "fair";
657
+ case PowerManager.THERMAL_STATUS_SEVERE:
658
+ return "serious";
659
+ case PowerManager.THERMAL_STATUS_CRITICAL:
660
+ case PowerManager.THERMAL_STATUS_EMERGENCY:
661
+ case PowerManager.THERMAL_STATUS_SHUTDOWN:
662
+ return "critical";
663
+ default:
664
+ return "unknown";
665
+ }
666
+ }
667
+
668
+ private boolean isLowPowerMode(Context context) {
669
+ PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
670
+ return powerManager != null && powerManager.isPowerSaveMode();
671
+ }
672
+
673
+ private double currentTimestamp() {
674
+ return (double) System.currentTimeMillis();
675
+ }
676
+
677
+ private long parseLong(String value) {
678
+ if (value == null) {
679
+ return 0;
680
+ }
681
+ try {
682
+ return Long.parseLong(value.trim());
683
+ } catch (NumberFormatException ignored) {
684
+ return 0;
685
+ }
686
+ }
687
+
688
+ private Double normalizeThermalTemperature(String value) {
689
+ if (value == null) {
690
+ return null;
691
+ }
692
+
693
+ try {
694
+ double temperature = Double.parseDouble(value.trim());
695
+ double absolute = Math.abs(temperature);
696
+ if (absolute > 1000) {
697
+ temperature /= 1000.0;
698
+ } else if (absolute > 150) {
699
+ temperature /= 10.0;
700
+ }
701
+
702
+ return temperature >= -50 && temperature <= 150 ? temperature : null;
703
+ } catch (NumberFormatException ignored) {
704
+ return null;
705
+ }
706
+ }
707
+
708
+ private boolean matchesThermalType(String type, String... hints) {
709
+ if (type == null) {
710
+ return false;
711
+ }
712
+
713
+ String normalized = type.toLowerCase(Locale.US);
714
+ for (String hint : hints) {
715
+ if (normalized.contains(hint)) {
716
+ return true;
717
+ }
718
+ }
719
+ return false;
720
+ }
721
+
722
+ private void putIfNotNull(JSObject object, String key, Object value) {
723
+ if (value != null) {
724
+ object.put(key, value);
725
+ }
726
+ }
727
+
728
+ private JSObject copyObject(JSObject object) {
729
+ try {
730
+ return new JSObject(object.toString());
731
+ } catch (JSONException ignored) {
732
+ return object;
733
+ }
734
+ }
735
+
736
+ private double percent(long used, long total) {
737
+ if (total <= 0) {
738
+ return 0;
739
+ }
740
+ return clampPercent(((double) used / (double) total) * 100.0);
741
+ }
742
+
743
+ private double clampPercent(double value) {
744
+ return Math.min(Math.max(value, 0), 100);
745
+ }
746
+
747
+ private static class CpuTicks {
748
+
749
+ final long total;
750
+ final long idle;
751
+
752
+ CpuTicks(long total, long idle) {
753
+ this.total = total;
754
+ this.idle = idle;
755
+ }
756
+ }
757
+
758
+ private static class GpuStrings {
759
+
760
+ String vendor;
761
+ String renderer;
762
+ String version;
763
+ int maxTextureSize;
764
+ }
765
+ }