GeckoAppShell.java (59834B)
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 package org.mozilla.gecko; 7 8 import android.annotation.SuppressLint; 9 import android.app.ActivityManager; 10 import android.app.Service; 11 import android.content.ComponentCallbacks; 12 import android.content.Context; 13 import android.content.Intent; 14 import android.content.SharedPreferences; 15 import android.content.pm.ActivityInfo; 16 import android.content.pm.ApplicationInfo; 17 import android.content.pm.PackageManager; 18 import android.content.pm.ResolveInfo; 19 import android.content.res.Configuration; 20 import android.content.res.TypedArray; 21 import android.graphics.Bitmap; 22 import android.graphics.Canvas; 23 import android.graphics.PixelFormat; 24 import android.graphics.Point; 25 import android.graphics.Rect; 26 import android.graphics.drawable.BitmapDrawable; 27 import android.graphics.drawable.Drawable; 28 import android.hardware.Sensor; 29 import android.hardware.SensorEvent; 30 import android.hardware.SensorEventListener; 31 import android.hardware.SensorManager; 32 import android.hardware.display.DisplayManager; 33 import android.location.Criteria; 34 import android.location.Location; 35 import android.location.LocationListener; 36 import android.location.LocationManager; 37 import android.media.AudioManager; 38 import android.net.ConnectivityManager; 39 import android.net.LinkProperties; 40 import android.net.Network; 41 import android.net.NetworkInfo; 42 import android.os.Build; 43 import android.os.Bundle; 44 import android.os.Debug; 45 import android.os.LocaleList; 46 import android.os.Looper; 47 import android.os.PowerManager; 48 import android.os.VibrationEffect; 49 import android.os.Vibrator; 50 import android.provider.Settings; 51 import android.util.DisplayMetrics; 52 import android.util.Log; 53 import android.view.ContextThemeWrapper; 54 import android.view.Display; 55 import android.view.InputDevice; 56 import android.view.WindowManager; 57 import android.view.inputmethod.InputMethodManager; 58 import android.view.inputmethod.InputMethodSubtype; 59 import android.webkit.MimeTypeMap; 60 import androidx.annotation.Nullable; 61 import androidx.annotation.RequiresApi; 62 import androidx.collection.SimpleArrayMap; 63 import androidx.core.content.res.ResourcesCompat; 64 import java.net.InetSocketAddress; 65 import java.net.Proxy; 66 import java.nio.ByteBuffer; 67 import java.util.List; 68 import java.util.StringTokenizer; 69 import org.jetbrains.annotations.NotNull; 70 import org.mozilla.gecko.annotation.RobocopTarget; 71 import org.mozilla.gecko.annotation.WrapForJNI; 72 import org.mozilla.gecko.util.HardwareUtils; 73 import org.mozilla.gecko.util.InputDeviceUtils; 74 import org.mozilla.gecko.util.ProxySelector; 75 import org.mozilla.gecko.util.ThreadUtils; 76 import org.mozilla.geckoview.BuildConfig; 77 import org.mozilla.geckoview.CrashHandler; 78 import org.mozilla.geckoview.R; 79 80 public class GeckoAppShell { 81 private static final String LOGTAG = "GeckoAppShell"; 82 83 /* 84 * Keep these values consistent with |SensorType| in HalSensor.h 85 */ 86 public static final int SENSOR_ORIENTATION = 0; 87 public static final int SENSOR_ACCELERATION = 1; 88 public static final int SENSOR_PROXIMITY = 2; 89 public static final int SENSOR_LINEAR_ACCELERATION = 3; 90 public static final int SENSOR_GYROSCOPE = 4; 91 public static final int SENSOR_LIGHT = 5; 92 public static final int SENSOR_ROTATION_VECTOR = 6; 93 public static final int SENSOR_GAME_ROTATION_VECTOR = 7; 94 95 // We have static members only. 96 private GeckoAppShell() {} 97 98 // Name for app-scoped prefs 99 public static final String APP_PREFS_NAME = "GeckoApp"; 100 101 private static class GeckoCrashHandler extends CrashHandler { 102 103 public GeckoCrashHandler(final Class<? extends Service> handlerService) { 104 super(handlerService); 105 } 106 107 @Override 108 public String getAppPackageName() { 109 final Context appContext = getAppContext(); 110 if (appContext == null) { 111 return "<unknown>"; 112 } 113 return appContext.getPackageName(); 114 } 115 116 @Override 117 public Context getAppContext() { 118 return getApplicationContext(); 119 } 120 121 @SuppressLint("ApplySharedPref") 122 @Override 123 public boolean reportException(final Thread thread, final Throwable exc) { 124 try { 125 if (exc instanceof OutOfMemoryError) { 126 final SharedPreferences prefs = 127 getApplicationContext().getSharedPreferences(APP_PREFS_NAME, 0); 128 final SharedPreferences.Editor editor = prefs.edit(); 129 editor.putBoolean(PREFS_OOM_EXCEPTION, true); 130 131 // Synchronously write to disk so we know it's done before we 132 // shutdown 133 editor.commit(); 134 } 135 136 reportJavaCrash(exc, getExceptionStackTrace(exc)); 137 138 } catch (final Throwable e) { 139 } 140 141 // reportJavaCrash should have caused us to hard crash. If we're still here, 142 // it probably means Gecko is not loaded, and we should do something else. 143 if (BuildConfig.MOZ_CRASHREPORTER && BuildConfig.MOZILLA_OFFICIAL) { 144 // Only use Java crash reporter if enabled on official build. 145 return super.reportException(thread, exc); 146 } 147 return false; 148 } 149 } 150 151 private static String sAppNotes; 152 private static CrashHandler sCrashHandler; 153 154 public static synchronized CrashHandler ensureCrashHandling( 155 final Class<? extends Service> handler) { 156 if (sCrashHandler == null) { 157 sCrashHandler = new GeckoCrashHandler(handler); 158 } 159 160 return sCrashHandler; 161 } 162 163 private static Class<? extends Service> sCrashHandlerService; 164 165 public static synchronized void setCrashHandlerService( 166 final Class<? extends Service> handlerService) { 167 sCrashHandlerService = handlerService; 168 } 169 170 public static synchronized Class<? extends Service> getCrashHandlerService() { 171 return sCrashHandlerService; 172 } 173 174 @WrapForJNI(exceptionMode = "ignore") 175 public static synchronized String getAppNotes() { 176 return sAppNotes; 177 } 178 179 public static synchronized void appendAppNotesToCrashReport(final String notes) { 180 if (sAppNotes == null) { 181 sAppNotes = notes; 182 } else { 183 sAppNotes += '\n' + notes; 184 } 185 } 186 187 private static volatile boolean locationHighAccuracyEnabled; 188 private static volatile boolean locationListeningRequested = false; 189 private static volatile boolean locationPaused = false; 190 191 // See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB. 192 private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768; 193 194 /* 195 * Device RAM threshold requirement for adding additional headers. 196 * Keep in sync with RAM_THRESHOLD_MEGABYTES defined in 197 * https://searchfox.org/mozilla-central/rev/55944eaee1e358b5443eaedc8adcd37e3fd23fd3/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt#120 198 */ 199 private static final int ADDITIONAL_SEARCH_HEADER_RAM_THRESHOLD_MEGABYTES = 1024; 200 201 private static int sScreenDepth; 202 private static boolean sUseMaxScreenDepth; 203 private static Float sScreenRefreshRate; 204 205 /* 206 * Time (in System.nanoTime() units) when the currently-playing vibration 207 * is scheduled to end. This value could be zero when the vibration is 208 * cancelled. `System.nanoTime() > sVibrationEndTime` means there is not a 209 * playing vibration now. 210 */ 211 private static long sVibrationEndTime; 212 213 private static Sensor gAccelerometerSensor; 214 private static Sensor gLinearAccelerometerSensor; 215 private static Sensor gGyroscopeSensor; 216 private static Sensor gOrientationSensor; 217 private static Sensor gLightSensor; 218 private static Sensor gRotationVectorSensor; 219 private static Sensor gGameRotationVectorSensor; 220 221 /* 222 * Keep in sync with constants found here: 223 * http://searchfox.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl 224 */ 225 public static final int WPL_STATE_START = 0x00000001; 226 public static final int WPL_STATE_STOP = 0x00000010; 227 public static final int WPL_STATE_IS_DOCUMENT = 0x00020000; 228 public static final int WPL_STATE_IS_NETWORK = 0x00040000; 229 230 /* Keep in sync with constants found here: 231 http://searchfox.org/mozilla-central/source/netwerk/base/nsINetworkLinkService.idl 232 */ 233 public static final int LINK_TYPE_UNKNOWN = 0; 234 public static final int LINK_TYPE_ETHERNET = 1; 235 public static final int LINK_TYPE_USB = 2; 236 public static final int LINK_TYPE_WIFI = 3; 237 public static final int LINK_TYPE_WIMAX = 4; 238 public static final int LINK_TYPE_MOBILE = 9; 239 240 public static final String PREFS_OOM_EXCEPTION = "OOMException"; 241 242 /* The Android-side API: API methods that Android calls */ 243 244 // helper methods 245 @WrapForJNI 246 /* package */ static native void reportJavaCrash(Throwable exc, String stackTrace); 247 248 private static Rect sScreenSizeOverride; 249 private static int sDensityDpiOverride; 250 private static Float sDensityOverride; 251 252 @WrapForJNI(stubName = "NotifyObservers", dispatchTo = "gecko") 253 private static native void nativeNotifyObservers(String topic, String data); 254 255 @WrapForJNI(stubName = "AppendAppNotesToCrashReport", dispatchTo = "gecko") 256 public static native void nativeAppendAppNotesToCrashReport(final String notes); 257 258 @RobocopTarget 259 public static void notifyObservers(final String topic, final String data) { 260 notifyObservers(topic, data, GeckoThread.State.RUNNING); 261 } 262 263 public static void notifyObservers( 264 final String topic, final String data, final GeckoThread.State state) { 265 if (GeckoThread.isStateAtLeast(state)) { 266 nativeNotifyObservers(topic, data); 267 } else { 268 GeckoThread.queueNativeCallUntil( 269 state, 270 GeckoAppShell.class, 271 "nativeNotifyObservers", 272 String.class, 273 topic, 274 String.class, 275 data); 276 } 277 } 278 279 /* 280 * The Gecko-side API: API methods that Gecko calls 281 */ 282 283 @WrapForJNI(exceptionMode = "ignore") 284 private static String getExceptionStackTrace(final Throwable e) { 285 return CrashHandler.getExceptionStackTrace(CrashHandler.getRootException(e)); 286 } 287 288 @WrapForJNI(exceptionMode = "ignore") 289 private static synchronized void handleUncaughtException(final Throwable e) { 290 if (sCrashHandler != null) { 291 sCrashHandler.uncaughtException(null, e); 292 } 293 } 294 295 private static float getLocationAccuracy(final Location location) { 296 final float radius = location.getAccuracy(); 297 return (location.hasAccuracy() && radius > 0) ? radius : 1001; 298 } 299 300 private static Location determineReliableLocation( 301 @NotNull final Location locA, @NotNull final Location locB) { 302 // The 6 seconds were chosen arbitrarily 303 final long closeTime = 6000000000L; 304 final boolean isNearSameTime = 305 Math.abs((locA.getElapsedRealtimeNanos() - locB.getElapsedRealtimeNanos())) <= closeTime; 306 final boolean isAMoreAccurate = getLocationAccuracy(locA) < getLocationAccuracy(locB); 307 final boolean isAMoreRecent = locA.getElapsedRealtimeNanos() > locB.getElapsedRealtimeNanos(); 308 if (isNearSameTime) { 309 return isAMoreAccurate ? locA : locB; 310 } 311 return isAMoreRecent ? locA : locB; 312 } 313 314 // Permissions are explicitly checked when requesting content permission. 315 @SuppressLint("MissingPermission") 316 private static @Nullable Location getLastKnownLocation(final LocationManager lm) { 317 Location lastKnownLocation = null; 318 final List<String> providers = lm.getAllProviders(); 319 320 for (final String provider : providers) { 321 final Location location = lm.getLastKnownLocation(provider); 322 if (location == null) { 323 continue; 324 } 325 326 if (lastKnownLocation == null) { 327 lastKnownLocation = location; 328 continue; 329 } 330 lastKnownLocation = determineReliableLocation(lastKnownLocation, location); 331 } 332 return lastKnownLocation; 333 } 334 335 // Toggles the location listeners on/off, which will then provide/stop location information 336 @WrapForJNI(calledFrom = "gecko") 337 private static synchronized boolean enableLocationUpdates(final boolean enable) { 338 locationListeningRequested = enable; 339 final boolean canListen = updateLocationListeners(); 340 // canListen will be true even if paused. During paused, we keep requesting status. 341 if (!canListen && locationListeningRequested) { 342 // Didn't successfully start listener when requested 343 locationListeningRequested = false; 344 } 345 return canListen; 346 } 347 348 // Permissions are explicitly checked when requesting content permission. 349 @SuppressLint("MissingPermission") 350 private static synchronized boolean updateLocationListeners() { 351 final boolean shouldListen = locationListeningRequested && !locationPaused; 352 final LocationManager lm = getLocationManager(getApplicationContext()); 353 if (lm == null) { 354 return false; 355 } 356 357 if (!shouldListen) { 358 if (!locationListeningRequested) { 359 // We are paused, so stop listening. 360 lm.removeUpdates(sAndroidListeners); 361 } 362 return true; 363 } 364 365 if (!lm.isProviderEnabled(LocationManager.GPS_PROVIDER) 366 && !lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { 367 return false; 368 } 369 370 final Location lastKnownLocation = getLastKnownLocation(lm); 371 if (lastKnownLocation != null) { 372 sAndroidListeners.onLocationChanged(lastKnownLocation); 373 } 374 375 final Criteria criteria = new Criteria(); 376 criteria.setSpeedRequired(false); 377 criteria.setBearingRequired(false); 378 criteria.setAltitudeRequired(false); 379 if (locationHighAccuracyEnabled) { 380 criteria.setAccuracy(Criteria.ACCURACY_FINE); 381 } else { 382 criteria.setAccuracy(Criteria.ACCURACY_COARSE); 383 } 384 385 final String provider = lm.getBestProvider(criteria, true); 386 if (provider == null) { 387 return false; 388 } 389 390 final Looper l = Looper.getMainLooper(); 391 lm.requestLocationUpdates(provider, 100, 0.5f, sAndroidListeners, l); 392 return true; 393 } 394 395 public static void pauseLocation() { 396 locationPaused = true; 397 updateLocationListeners(); 398 } 399 400 public static void resumeLocation() { 401 locationPaused = false; 402 updateLocationListeners(); 403 } 404 405 private static LocationManager getLocationManager(final Context context) { 406 try { 407 return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 408 } catch (final NoSuchFieldError e) { 409 // Some Tegras throw exceptions about missing the CONTROL_LOCATION_UPDATES permission, 410 // which allows enabling/disabling location update notifications from the cell radio. 411 // CONTROL_LOCATION_UPDATES is not for use by normal applications, but we might be 412 // hitting this problem if the Tegras are confused about missing cell radios. 413 Log.e(LOGTAG, "LOCATION_SERVICE not found?!", e); 414 return null; 415 } 416 } 417 418 @WrapForJNI(calledFrom = "gecko") 419 private static void enableLocationHighAccuracy(final boolean enable) { 420 locationHighAccuracyEnabled = enable; 421 } 422 423 @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") 424 /* package */ static native void onSensorChanged( 425 int halType, float x, float y, float z, float w, long time); 426 427 @WrapForJNI(calledFrom = "any", dispatchTo = "gecko") 428 /* package */ static native void onLocationChanged( 429 double latitude, 430 double longitude, 431 double altitude, 432 float accuracy, 433 float altitudeAccuracy, 434 float heading, 435 float speed); 436 437 private static class AndroidListeners implements SensorEventListener, LocationListener { 438 @Override 439 public void onAccuracyChanged(final Sensor sensor, final int accuracy) {} 440 441 @Override 442 public void onSensorChanged(final SensorEvent s) { 443 final int sensorType = s.sensor.getType(); 444 int halType = 0; 445 float x = 0.0f, y = 0.0f, z = 0.0f, w = 0.0f; 446 // SensorEvent timestamp is in nanoseconds, Gecko expects microseconds. 447 final long time = s.timestamp / 1000; 448 449 switch (sensorType) { 450 case Sensor.TYPE_ACCELEROMETER: 451 case Sensor.TYPE_LINEAR_ACCELERATION: 452 case Sensor.TYPE_ORIENTATION: 453 if (sensorType == Sensor.TYPE_ACCELEROMETER) { 454 halType = SENSOR_ACCELERATION; 455 } else if (sensorType == Sensor.TYPE_LINEAR_ACCELERATION) { 456 halType = SENSOR_LINEAR_ACCELERATION; 457 } else { 458 halType = SENSOR_ORIENTATION; 459 } 460 x = s.values[0]; 461 y = s.values[1]; 462 z = s.values[2]; 463 break; 464 465 case Sensor.TYPE_GYROSCOPE: 466 halType = SENSOR_GYROSCOPE; 467 x = (float) Math.toDegrees(s.values[0]); 468 y = (float) Math.toDegrees(s.values[1]); 469 z = (float) Math.toDegrees(s.values[2]); 470 break; 471 472 case Sensor.TYPE_LIGHT: 473 halType = SENSOR_LIGHT; 474 x = s.values[0]; 475 break; 476 477 case Sensor.TYPE_ROTATION_VECTOR: 478 case Sensor.TYPE_GAME_ROTATION_VECTOR: // API >= 18 479 halType = 480 (sensorType == Sensor.TYPE_ROTATION_VECTOR 481 ? SENSOR_ROTATION_VECTOR 482 : SENSOR_GAME_ROTATION_VECTOR); 483 x = s.values[0]; 484 y = s.values[1]; 485 z = s.values[2]; 486 if (s.values.length >= 4) { 487 w = s.values[3]; 488 } else { 489 // s.values[3] was optional in API <= 18, so we need to compute it 490 // The values form a unit quaternion, so we can compute the angle of 491 // rotation purely based on the given 3 values. 492 w = 493 1.0f 494 - s.values[0] * s.values[0] 495 - s.values[1] * s.values[1] 496 - s.values[2] * s.values[2]; 497 w = (w > 0.0f) ? (float) Math.sqrt(w) : 0.0f; 498 } 499 break; 500 } 501 502 GeckoAppShell.onSensorChanged(halType, x, y, z, w, time); 503 } 504 505 // Geolocation. 506 @Override 507 public void onLocationChanged(final Location location) { 508 // No logging here: user-identifying information. 509 510 final double altitude = location.hasAltitude() ? location.getAltitude() : Double.NaN; 511 512 final float accuracy = location.hasAccuracy() ? location.getAccuracy() : Float.NaN; 513 514 final float altitudeAccuracy = 515 location.hasVerticalAccuracy() ? location.getVerticalAccuracyMeters() : Float.NaN; 516 517 final float speed = location.hasSpeed() ? location.getSpeed() : Float.NaN; 518 519 final float heading = location.hasBearing() ? location.getBearing() : Float.NaN; 520 521 // nsGeoPositionCoords will convert NaNs to null for optional 522 // properties of the JavaScript Coordinates object. 523 GeckoAppShell.onLocationChanged( 524 location.getLatitude(), 525 location.getLongitude(), 526 altitude, 527 accuracy, 528 altitudeAccuracy, 529 heading, 530 speed); 531 } 532 533 @Override 534 public void onProviderDisabled(final String provider) {} 535 536 @Override 537 public void onProviderEnabled(final String provider) {} 538 539 @Override 540 public void onStatusChanged(final String provider, final int status, final Bundle extras) {} 541 } 542 543 private static final AndroidListeners sAndroidListeners = new AndroidListeners(); 544 545 private static SimpleArrayMap<String, PowerManager.WakeLock> sWakeLocks; 546 547 /** Wake-lock for the CPU. */ 548 static final String WAKE_LOCK_CPU = "cpu"; 549 550 /** Wake-lock for the screen. */ 551 static final String WAKE_LOCK_SCREEN = "screen"; 552 553 /** Wake-lock for the audio-playing, eqaul to LOCK_CPU. */ 554 static final String WAKE_LOCK_AUDIO_PLAYING = "audio-playing"; 555 556 /** Wake-lock for the video-playing, eqaul to LOCK_SCREEN.. */ 557 static final String WAKE_LOCK_VIDEO_PLAYING = "video-playing"; 558 559 static final int WAKE_LOCKS_COUNT = 2; 560 561 /** No one holds the wake-lock. */ 562 static final int WAKE_LOCK_STATE_UNLOCKED = 0; 563 564 /** The wake-lock is held by a foreground window. */ 565 static final int WAKE_LOCK_STATE_LOCKED_FOREGROUND = 1; 566 567 /** The wake-lock is held by a background window. */ 568 static final int WAKE_LOCK_STATE_LOCKED_BACKGROUND = 2; 569 570 @SuppressLint("Wakelock") // We keep the wake lock independent from the function 571 // scope, so we need to suppress the linter warning. 572 private static void setWakeLockState(final String lock, final int state) { 573 if (sWakeLocks == null) { 574 sWakeLocks = new SimpleArrayMap<>(WAKE_LOCKS_COUNT); 575 } 576 577 PowerManager.WakeLock wl = sWakeLocks.get(lock); 578 579 // we should still hold the lock for background audio. 580 if (WAKE_LOCK_AUDIO_PLAYING.equals(lock) && state == WAKE_LOCK_STATE_LOCKED_BACKGROUND) { 581 return; 582 } 583 584 if (state == WAKE_LOCK_STATE_LOCKED_FOREGROUND && wl == null) { 585 final PowerManager pm = 586 (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE); 587 588 if (WAKE_LOCK_CPU.equals(lock) || WAKE_LOCK_AUDIO_PLAYING.equals(lock)) { 589 wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lock); 590 } else if (WAKE_LOCK_SCREEN.equals(lock) || WAKE_LOCK_VIDEO_PLAYING.equals(lock)) { 591 // ON_AFTER_RELEASE is set, the user activity timer will be reset when the 592 // WakeLock is released, causing the illumination to remain on a bit longer. 593 wl = 594 pm.newWakeLock( 595 PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, lock); 596 } else { 597 Log.w(LOGTAG, "Unsupported wake-lock: " + lock); 598 return; 599 } 600 601 wl.acquire(); 602 sWakeLocks.put(lock, wl); 603 } else if (state != WAKE_LOCK_STATE_LOCKED_FOREGROUND && wl != null) { 604 wl.release(); 605 sWakeLocks.remove(lock); 606 } 607 } 608 609 @SuppressWarnings("fallthrough") 610 @WrapForJNI(calledFrom = "gecko") 611 private static void enableSensor(final int aSensortype) { 612 final SensorManager sm = 613 (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE); 614 615 switch (aSensortype) { 616 case SENSOR_GAME_ROTATION_VECTOR: 617 if (gGameRotationVectorSensor == null) { 618 gGameRotationVectorSensor = sm.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR); 619 } 620 if (gGameRotationVectorSensor != null) { 621 sm.registerListener( 622 sAndroidListeners, gGameRotationVectorSensor, SensorManager.SENSOR_DELAY_FASTEST); 623 } 624 if (gGameRotationVectorSensor != null) { 625 break; 626 } 627 // Fallthrough 628 629 case SENSOR_ROTATION_VECTOR: 630 if (gRotationVectorSensor == null) { 631 gRotationVectorSensor = sm.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); 632 } 633 if (gRotationVectorSensor != null) { 634 sm.registerListener( 635 sAndroidListeners, gRotationVectorSensor, SensorManager.SENSOR_DELAY_FASTEST); 636 } 637 if (gRotationVectorSensor != null) { 638 break; 639 } 640 // Fallthrough 641 642 case SENSOR_ORIENTATION: 643 if (gOrientationSensor == null) { 644 gOrientationSensor = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION); 645 } 646 if (gOrientationSensor != null) { 647 sm.registerListener( 648 sAndroidListeners, gOrientationSensor, SensorManager.SENSOR_DELAY_FASTEST); 649 } 650 break; 651 652 case SENSOR_ACCELERATION: 653 if (gAccelerometerSensor == null) { 654 gAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 655 } 656 if (gAccelerometerSensor != null) { 657 sm.registerListener( 658 sAndroidListeners, gAccelerometerSensor, SensorManager.SENSOR_DELAY_FASTEST); 659 } 660 break; 661 662 case SENSOR_LIGHT: 663 if (gLightSensor == null) { 664 gLightSensor = sm.getDefaultSensor(Sensor.TYPE_LIGHT); 665 } 666 if (gLightSensor != null) { 667 sm.registerListener(sAndroidListeners, gLightSensor, SensorManager.SENSOR_DELAY_NORMAL); 668 } 669 break; 670 671 case SENSOR_LINEAR_ACCELERATION: 672 if (gLinearAccelerometerSensor == null) { 673 gLinearAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION); 674 } 675 if (gLinearAccelerometerSensor != null) { 676 sm.registerListener( 677 sAndroidListeners, gLinearAccelerometerSensor, SensorManager.SENSOR_DELAY_FASTEST); 678 } 679 break; 680 681 case SENSOR_GYROSCOPE: 682 if (gGyroscopeSensor == null) { 683 gGyroscopeSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE); 684 } 685 if (gGyroscopeSensor != null) { 686 sm.registerListener( 687 sAndroidListeners, gGyroscopeSensor, SensorManager.SENSOR_DELAY_FASTEST); 688 } 689 break; 690 691 default: 692 Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " + aSensortype); 693 } 694 } 695 696 @SuppressWarnings("fallthrough") 697 @WrapForJNI(calledFrom = "gecko") 698 private static void disableSensor(final int aSensortype) { 699 final SensorManager sm = 700 (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE); 701 702 switch (aSensortype) { 703 case SENSOR_GAME_ROTATION_VECTOR: 704 if (gGameRotationVectorSensor != null) { 705 sm.unregisterListener(sAndroidListeners, gGameRotationVectorSensor); 706 break; 707 } 708 // Fallthrough 709 710 case SENSOR_ROTATION_VECTOR: 711 if (gRotationVectorSensor != null) { 712 sm.unregisterListener(sAndroidListeners, gRotationVectorSensor); 713 break; 714 } 715 // Fallthrough 716 717 case SENSOR_ORIENTATION: 718 if (gOrientationSensor != null) { 719 sm.unregisterListener(sAndroidListeners, gOrientationSensor); 720 } 721 break; 722 723 case SENSOR_ACCELERATION: 724 if (gAccelerometerSensor != null) { 725 sm.unregisterListener(sAndroidListeners, gAccelerometerSensor); 726 } 727 break; 728 729 case SENSOR_LIGHT: 730 if (gLightSensor != null) { 731 sm.unregisterListener(sAndroidListeners, gLightSensor); 732 } 733 break; 734 735 case SENSOR_LINEAR_ACCELERATION: 736 if (gLinearAccelerometerSensor != null) { 737 sm.unregisterListener(sAndroidListeners, gLinearAccelerometerSensor); 738 } 739 break; 740 741 case SENSOR_GYROSCOPE: 742 if (gGyroscopeSensor != null) { 743 sm.unregisterListener(sAndroidListeners, gGyroscopeSensor); 744 } 745 break; 746 default: 747 Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype); 748 } 749 } 750 751 @WrapForJNI(calledFrom = "gecko") 752 private static void moveTaskToBack() { 753 final Context applicationContext = getApplicationContext(); 754 final Intent intent = new Intent(Intent.ACTION_MAIN); 755 intent.addCategory(Intent.CATEGORY_HOME); 756 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 757 applicationContext.startActivity(intent); 758 } 759 760 @WrapForJNI(calledFrom = "gecko") 761 public static String getExtensionFromMimeType(final String aMimeType) { 762 return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType); 763 } 764 765 @WrapForJNI(calledFrom = "gecko") 766 public static String getMimeTypeFromExtensions(final String aFileExt) { 767 final StringTokenizer st = new StringTokenizer(aFileExt, ".,; "); 768 String type = null; 769 String subType = null; 770 while (st.hasMoreElements()) { 771 final String ext = st.nextToken(); 772 final String mt = getMimeTypeFromExtension(ext); 773 if (mt == null) continue; 774 final int slash = mt.indexOf('/'); 775 final String tmpType = mt.substring(0, slash); 776 if (!tmpType.equalsIgnoreCase(type)) type = type == null ? tmpType : "*"; 777 final String tmpSubType = mt.substring(slash + 1); 778 if (!tmpSubType.equalsIgnoreCase(subType)) subType = subType == null ? tmpSubType : "*"; 779 } 780 if (type == null) type = "*"; 781 if (subType == null) subType = "*"; 782 return type + "/" + subType; 783 } 784 785 @WrapForJNI(dispatchTo = "gecko") 786 private static native void notifyAlertListener( 787 String name, String topic, String action, String origin); 788 789 /** Called by the NotificationListener to notify Gecko that a notification has shown. */ 790 public static void onNotificationShow( 791 final String name, final String cookie, @NotNull final String origin) { 792 if (GeckoThread.isRunning()) { 793 notifyAlertListener(name, "alertshow", cookie, origin); 794 } 795 } 796 797 /** 798 * Called by the NotificationListener to notify Gecko that a previously shown notification has 799 * been closed. 800 */ 801 public static void onNotificationClose(@NotNull final String name, @NotNull final String origin) { 802 if (GeckoThread.isRunning()) { 803 notifyAlertListener(name, "alertfinished", null, origin); 804 } 805 } 806 807 /** 808 * Called by the NotificationListener to notify Gecko that a previously shown notification has 809 * been clicked on. 810 */ 811 public static void onNotificationClick( 812 @NotNull final String name, @Nullable final String action, @NotNull final String origin) { 813 if (GeckoThread.isRunning()) { 814 notifyAlertListener(name, "alertclickcallback", action, origin); 815 } else { 816 GeckoThread.queueNativeCallUntil( 817 GeckoThread.State.PROFILE_READY, 818 GeckoAppShell.class, 819 "notifyAlertListener", 820 name, 821 "alertclickcallback", 822 String.class, 823 action, 824 origin); 825 } 826 } 827 828 public static synchronized void setDisplayDpiOverride(@Nullable final Integer dpi) { 829 if (dpi == null) { 830 return; 831 } 832 if (sDensityDpiOverride != 0) { 833 Log.e(LOGTAG, "Tried to override screen DPI after it's already been set"); 834 return; 835 } 836 sDensityDpiOverride = dpi; 837 } 838 839 @WrapForJNI(calledFrom = "gecko") 840 private static synchronized int getDpi() { 841 if (sDensityDpiOverride != 0) { 842 return sDensityDpiOverride; 843 } 844 return sScreenCompat.getDensityDpi(); 845 } 846 847 public static synchronized void setDisplayDensityOverride(@Nullable final Float density) { 848 if (density == null) { 849 return; 850 } 851 if (sDensityOverride != null) { 852 Log.e(LOGTAG, "Tried to override screen density after it's already been set"); 853 return; 854 } 855 sDensityOverride = density; 856 } 857 858 @WrapForJNI(calledFrom = "gecko") 859 private static synchronized float getDensity() { 860 if (sDensityOverride != null) { 861 return sDensityOverride; 862 } 863 864 return sScreenCompat.getDensity(); 865 } 866 867 private static int sTotalRam; 868 869 private static int getTotalRam(final Context context) { 870 if (sTotalRam == 0) { 871 final ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); 872 final ActivityManager am = 873 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 874 am.getMemoryInfo(memInfo); // `getMemoryInfo()` returns a value in B. Convert to MB. 875 sTotalRam = (int) (memInfo.totalMem / (1024 * 1024)); 876 Log.d(LOGTAG, "System memory: " + sTotalRam + "MB."); 877 } 878 879 return sTotalRam; 880 } 881 882 @WrapForJNI(calledFrom = "gecko") 883 private static synchronized boolean isDeviceRamThresholdOkay() { 884 final Context applicationContext = getApplicationContext(); 885 return getTotalRam(applicationContext) > ADDITIONAL_SEARCH_HEADER_RAM_THRESHOLD_MEGABYTES; 886 } 887 888 private static boolean isHighMemoryDevice(final Context context) { 889 return getTotalRam(context) > HIGH_MEMORY_DEVICE_THRESHOLD_MB; 890 } 891 892 public static synchronized void useMaxScreenDepth(final boolean enable) { 893 sUseMaxScreenDepth = enable; 894 } 895 896 /** Returns the colour depth of the default screen. This will either be 32, 24 or 16. */ 897 @WrapForJNI(calledFrom = "gecko") 898 public static synchronized int getScreenDepth() { 899 if (sScreenDepth == 0) { 900 sScreenDepth = 16; 901 final Context applicationContext = getApplicationContext(); 902 final PixelFormat info = new PixelFormat(); 903 final WindowManager wm = 904 (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE); 905 PixelFormat.getPixelFormatInfo(wm.getDefaultDisplay().getPixelFormat(), info); 906 if (info.bitsPerPixel >= 24 && isHighMemoryDevice(applicationContext)) { 907 sScreenDepth = sUseMaxScreenDepth ? info.bitsPerPixel : 24; 908 } 909 } 910 911 return sScreenDepth; 912 } 913 914 @WrapForJNI(calledFrom = "gecko") 915 public static synchronized float getScreenRefreshRate() { 916 if (sScreenRefreshRate != null) { 917 return sScreenRefreshRate; 918 } 919 920 final WindowManager wm = 921 (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE); 922 final float refreshRate = wm.getDefaultDisplay().getRefreshRate(); 923 // Android 11+ supports multiple refresh rate. So we have to get refresh rate per call. 924 // https://source.android.com/docs/core/graphics/multiple-refresh-rate 925 if (Build.VERSION.SDK_INT < 30) { 926 // Until Android 10, refresh rate is fixed, so we can cache it. 927 sScreenRefreshRate = Float.valueOf(refreshRate); 928 } 929 return refreshRate; 930 } 931 932 @WrapForJNI(calledFrom = "gecko") 933 private static boolean hasHDRScreen() { 934 final Display display = 935 ((DisplayManager) getApplicationContext().getSystemService(Context.DISPLAY_SERVICE)) 936 .getDisplay(Display.DEFAULT_DISPLAY); 937 return display != null && display.isHdr(); 938 } 939 940 @WrapForJNI(calledFrom = "gecko") 941 private static void performHapticFeedback(final boolean aIsLongPress) { 942 // Don't perform haptic feedback if a vibration is currently playing, 943 // because the haptic feedback will nuke the vibration. 944 if (System.nanoTime() >= sVibrationEndTime) { 945 final VibrationEffect effect; 946 if (Build.VERSION.SDK_INT >= 29) { 947 // API level 29 introduces pre-defined vibration effects for better 948 // haptic feedback, prefer to use them. 949 if (aIsLongPress) { 950 effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK); 951 } else { 952 effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); 953 } 954 } else { 955 if (aIsLongPress) { 956 effect = VibrationEffect.createWaveform(new long[] {0, 1, 20, 21}, -1); 957 } else { 958 effect = VibrationEffect.createWaveform(new long[] {0, 10, 20, 30}, -1); 959 } 960 } 961 vibrateOnHapticFeedbackEnabled(effect); 962 } 963 } 964 965 private static Vibrator vibrator() { 966 return (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); 967 } 968 969 // Vibrate only if haptic feedback is enabled. 970 @SuppressLint("MissingPermission") 971 private static void vibrateOnHapticFeedbackEnabled(final VibrationEffect effect) { 972 if (Settings.System.getInt( 973 getApplicationContext().getContentResolver(), 974 Settings.System.HAPTIC_FEEDBACK_ENABLED, 975 0) 976 > 0) { 977 // Here, sVibrationEndTime is not set. Compared to other kinds of 978 // vibration, haptic feedbacks are usually shorter and less important, 979 // which means it's ok to "nuke" them. 980 try { 981 vibrator().vibrate(effect); 982 } catch (final SecurityException ignore) { 983 Log.w(LOGTAG, "No VIBRATE permission"); 984 } 985 } 986 } 987 988 @SuppressLint("MissingPermission") 989 @WrapForJNI(calledFrom = "gecko") 990 private static void vibrate(final long milliseconds) { 991 sVibrationEndTime = System.nanoTime() + milliseconds * 1000000; 992 try { 993 vibrator().vibrate(milliseconds); 994 } catch (final SecurityException ignore) { 995 Log.w(LOGTAG, "No VIBRATE permission"); 996 } 997 } 998 999 @SuppressLint("MissingPermission") 1000 @WrapForJNI(calledFrom = "gecko") 1001 private static void vibrate(final long[] pattern, final int repeat) { 1002 // If pattern.length is odd, the last element in the pattern is a 1003 // meaningless delay, so don't include it in vibrationDuration. 1004 long vibrationDuration = 0; 1005 final int iterLen = pattern.length & ~1; 1006 for (int i = 0; i < iterLen; i++) { 1007 vibrationDuration += pattern[i]; 1008 } 1009 1010 sVibrationEndTime = System.nanoTime() + vibrationDuration * 1000000; 1011 try { 1012 vibrator().vibrate(pattern, repeat); 1013 } catch (final SecurityException ignore) { 1014 Log.w(LOGTAG, "No VIBRATE permission"); 1015 } 1016 } 1017 1018 @SuppressLint("MissingPermission") 1019 @WrapForJNI(calledFrom = "gecko") 1020 private static void cancelVibrate() { 1021 sVibrationEndTime = 0; 1022 try { 1023 vibrator().cancel(); 1024 } catch (final SecurityException ignore) { 1025 Log.w(LOGTAG, "No VIBRATE permission"); 1026 } 1027 } 1028 1029 private static ConnectivityManager sConnectivityManager; 1030 1031 private static void ensureConnectivityManager() { 1032 if (sConnectivityManager == null) { 1033 sConnectivityManager = 1034 (ConnectivityManager) 1035 getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); 1036 } 1037 } 1038 1039 @WrapForJNI(calledFrom = "gecko") 1040 private static boolean isNetworkLinkUp() { 1041 ensureConnectivityManager(); 1042 try { 1043 final NetworkInfo info = sConnectivityManager.getActiveNetworkInfo(); 1044 if (info == null || !info.isConnected()) return false; 1045 } catch (final SecurityException se) { 1046 return false; 1047 } 1048 return true; 1049 } 1050 1051 @WrapForJNI(calledFrom = "gecko") 1052 private static boolean isNetworkLinkKnown() { 1053 ensureConnectivityManager(); 1054 try { 1055 if (sConnectivityManager.getActiveNetworkInfo() == null) return false; 1056 } catch (final SecurityException se) { 1057 return false; 1058 } 1059 return true; 1060 } 1061 1062 @WrapForJNI(calledFrom = "gecko") 1063 private static int getNetworkLinkType() { 1064 ensureConnectivityManager(); 1065 final NetworkInfo info = sConnectivityManager.getActiveNetworkInfo(); 1066 if (info == null) { 1067 return LINK_TYPE_UNKNOWN; 1068 } 1069 1070 switch (info.getType()) { 1071 case ConnectivityManager.TYPE_ETHERNET: 1072 return LINK_TYPE_ETHERNET; 1073 case ConnectivityManager.TYPE_WIFI: 1074 return LINK_TYPE_WIFI; 1075 case ConnectivityManager.TYPE_WIMAX: 1076 return LINK_TYPE_WIMAX; 1077 case ConnectivityManager.TYPE_MOBILE: 1078 return LINK_TYPE_MOBILE; 1079 default: 1080 Log.w(LOGTAG, "Ignoring the current network type."); 1081 return LINK_TYPE_UNKNOWN; 1082 } 1083 } 1084 1085 @WrapForJNI(calledFrom = "gecko", exceptionMode = "nsresult") 1086 private static String getDNSDomains() { 1087 ensureConnectivityManager(); 1088 final Network net = sConnectivityManager.getActiveNetwork(); 1089 if (net == null) { 1090 return ""; 1091 } 1092 1093 final LinkProperties lp = sConnectivityManager.getLinkProperties(net); 1094 if (lp == null) { 1095 return ""; 1096 } 1097 1098 return lp.getDomains(); 1099 } 1100 1101 @SuppressLint("ResourceType") 1102 @WrapForJNI(calledFrom = "gecko") 1103 private static int[] getSystemColors() { 1104 // attrsAppearance[] must correspond to AndroidSystemColors structure in android/nsLookAndFeel.h 1105 final int[] attrsAppearance = { 1106 android.R.attr.textColorPrimary, 1107 android.R.attr.textColorPrimaryInverse, 1108 android.R.attr.textColorSecondary, 1109 android.R.attr.textColorSecondaryInverse, 1110 android.R.attr.textColorTertiary, 1111 android.R.attr.textColorTertiaryInverse, 1112 android.R.attr.textColorHighlight, 1113 android.R.attr.colorForeground, 1114 android.R.attr.colorBackground, 1115 android.R.attr.panelColorForeground, 1116 android.R.attr.panelColorBackground, 1117 android.R.attr.colorAccent, 1118 }; 1119 1120 final int[] result = new int[attrsAppearance.length]; 1121 1122 final ContextThemeWrapper contextThemeWrapper = 1123 new ContextThemeWrapper(getApplicationContext(), android.R.style.TextAppearance); 1124 1125 final TypedArray appearance = contextThemeWrapper.obtainStyledAttributes(attrsAppearance); 1126 1127 if (appearance != null) { 1128 for (int i = 0; i < appearance.getIndexCount(); i++) { 1129 final int idx = appearance.getIndex(i); 1130 final int color = appearance.getColor(idx, 0); 1131 result[idx] = color; 1132 } 1133 appearance.recycle(); 1134 } 1135 1136 return result; 1137 } 1138 1139 @WrapForJNI(calledFrom = "gecko") 1140 private static String getKeyboardLayout() { 1141 final InputMethodManager imm = 1142 (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE); 1143 final InputMethodSubtype ims = imm.getCurrentInputMethodSubtype(); 1144 if (ims == null) { 1145 return null; 1146 } 1147 1148 // TODO(m_kato): 1149 // Android 16 will have `layout related APIs such as setLayoutLabelNonLocalized 1150 // to get keyboard layout label. 1151 return ims.getLanguageTag(); 1152 } 1153 1154 @WrapForJNI(calledFrom = "gecko") 1155 private static byte[] getIconForExtension(final String aExt, final int iconSize) { 1156 try { 1157 int resolvedIconSize = iconSize; 1158 if (iconSize <= 0) { 1159 resolvedIconSize = 16; 1160 } 1161 1162 String resolvedExt = aExt; 1163 if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.') { 1164 resolvedExt = aExt.substring(1); 1165 } 1166 1167 final PackageManager pm = getApplicationContext().getPackageManager(); 1168 Drawable icon = getDrawableForExtension(pm, resolvedExt); 1169 if (icon == null) { 1170 // Use a generic icon. 1171 icon = 1172 ResourcesCompat.getDrawable( 1173 getApplicationContext().getResources(), 1174 R.drawable.ic_generic_file, 1175 getApplicationContext().getTheme()); 1176 } 1177 1178 Bitmap bitmap = getBitmapFromDrawable(icon); 1179 if (bitmap.getWidth() != resolvedIconSize || bitmap.getHeight() != resolvedIconSize) { 1180 bitmap = Bitmap.createScaledBitmap(bitmap, resolvedIconSize, resolvedIconSize, true); 1181 } 1182 1183 final ByteBuffer buf = ByteBuffer.allocate(resolvedIconSize * resolvedIconSize * 4); 1184 bitmap.copyPixelsToBuffer(buf); 1185 1186 return buf.array(); 1187 } catch (final Exception e) { 1188 Log.w(LOGTAG, "getIconForExtension failed.", e); 1189 return null; 1190 } 1191 } 1192 1193 private static Bitmap getBitmapFromDrawable(final Drawable drawable) { 1194 if (drawable instanceof BitmapDrawable) { 1195 return ((BitmapDrawable) drawable).getBitmap(); 1196 } 1197 1198 int width = drawable.getIntrinsicWidth(); 1199 width = width > 0 ? width : 1; 1200 int height = drawable.getIntrinsicHeight(); 1201 height = height > 0 ? height : 1; 1202 1203 final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 1204 final Canvas canvas = new Canvas(bitmap); 1205 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 1206 drawable.draw(canvas); 1207 1208 return bitmap; 1209 } 1210 1211 public static String getMimeTypeFromExtension(final String ext) { 1212 final MimeTypeMap mtm = MimeTypeMap.getSingleton(); 1213 return mtm.getMimeTypeFromExtension(ext); 1214 } 1215 1216 private static Drawable getDrawableForExtension(final PackageManager pm, final String aExt) { 1217 final Intent intent = new Intent(Intent.ACTION_VIEW); 1218 final String mimeType = getMimeTypeFromExtension(aExt); 1219 if (mimeType != null && mimeType.length() > 0) intent.setType(mimeType); 1220 else return null; 1221 1222 final List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); 1223 if (list.size() == 0) return null; 1224 1225 final ResolveInfo resolveInfo = list.get(0); 1226 1227 if (resolveInfo == null) return null; 1228 1229 final ActivityInfo activityInfo = resolveInfo.activityInfo; 1230 1231 return activityInfo.loadIcon(pm); 1232 } 1233 1234 @WrapForJNI(calledFrom = "gecko") 1235 private static boolean getShowPasswordSetting() { 1236 try { 1237 final int showPassword = 1238 Settings.System.getInt( 1239 getApplicationContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1); 1240 return (showPassword > 0); 1241 } catch (final Exception e) { 1242 return true; 1243 } 1244 } 1245 1246 private static Context sApplicationContext; 1247 private static Boolean sIs24HourFormat = true; 1248 1249 @WrapForJNI 1250 public static Context getApplicationContext() { 1251 return sApplicationContext; 1252 } 1253 1254 public static void setApplicationContext(final Context context) { 1255 sApplicationContext = context; 1256 } 1257 1258 /* 1259 * Battery API related methods. 1260 */ 1261 @WrapForJNI(calledFrom = "gecko") 1262 private static void enableBatteryNotifications() { 1263 GeckoBatteryManager.enableNotifications(); 1264 } 1265 1266 @WrapForJNI(calledFrom = "gecko") 1267 private static void disableBatteryNotifications() { 1268 GeckoBatteryManager.disableNotifications(); 1269 } 1270 1271 @WrapForJNI(calledFrom = "gecko") 1272 private static double[] getCurrentBatteryInformation() { 1273 return GeckoBatteryManager.getCurrentInformation(getApplicationContext()); 1274 } 1275 1276 /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */ 1277 @WrapForJNI(calledFrom = "gecko") 1278 @RobocopTarget 1279 public static boolean isTablet() { 1280 return HardwareUtils.isTablet(getApplicationContext()); 1281 } 1282 1283 @WrapForJNI(calledFrom = "gecko") 1284 private static double[] getCurrentNetworkInformation() { 1285 return GeckoNetworkManager.getInstance().getCurrentInformation(); 1286 } 1287 1288 @WrapForJNI(calledFrom = "gecko") 1289 private static void enableNetworkNotifications() { 1290 ThreadUtils.runOnUiThread(() -> GeckoNetworkManager.getInstance().enableNotifications()); 1291 } 1292 1293 @WrapForJNI(calledFrom = "gecko") 1294 private static void disableNetworkNotifications() { 1295 ThreadUtils.runOnUiThread( 1296 new Runnable() { 1297 @Override 1298 public void run() { 1299 GeckoNetworkManager.getInstance().disableNotifications(); 1300 } 1301 }); 1302 } 1303 1304 @WrapForJNI(calledFrom = "gecko") 1305 private static short getScreenOrientation() { 1306 return GeckoScreenOrientation.getInstance().getScreenOrientation().value; 1307 } 1308 1309 /* package */ static int getRotation() { 1310 return sScreenCompat.getRotation(); 1311 } 1312 1313 @WrapForJNI(calledFrom = "gecko") 1314 private static int getScreenAngle() { 1315 return GeckoScreenOrientation.getInstance().getAngle(); 1316 } 1317 1318 @WrapForJNI(calledFrom = "gecko") 1319 private static void notifyWakeLockChanged(final String topic, final String state) { 1320 final int intState; 1321 if ("unlocked".equals(state)) { 1322 intState = WAKE_LOCK_STATE_UNLOCKED; 1323 } else if ("locked-foreground".equals(state)) { 1324 intState = WAKE_LOCK_STATE_LOCKED_FOREGROUND; 1325 } else if ("locked-background".equals(state)) { 1326 intState = WAKE_LOCK_STATE_LOCKED_BACKGROUND; 1327 } else { 1328 throw new IllegalArgumentException(); 1329 } 1330 setWakeLockState(topic, intState); 1331 } 1332 1333 @WrapForJNI(calledFrom = "gecko") 1334 private static String getProxyForURI( 1335 final String spec, final String scheme, final String host, final int port) { 1336 final ProxySelector ps = new ProxySelector(); 1337 1338 final Proxy proxy = ps.select(scheme, host); 1339 if (Proxy.NO_PROXY.equals(proxy)) { 1340 return "DIRECT"; 1341 } 1342 1343 final InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address(); 1344 final String proxyString = proxyAddress.getHostString() + ":" + proxyAddress.getPort(); 1345 1346 switch (proxy.type()) { 1347 case HTTP: 1348 return "PROXY " + proxyString; 1349 case SOCKS: 1350 return "SOCKS " + proxyString; 1351 } 1352 1353 return "DIRECT"; 1354 } 1355 1356 @WrapForJNI(calledFrom = "gecko") 1357 private static int getMaxTouchPoints() { 1358 final PackageManager pm = getApplicationContext().getPackageManager(); 1359 if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND)) { 1360 // at least, 5+ fingers. 1361 return 5; 1362 } else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) { 1363 // at least, 2+ fingers. 1364 return 2; 1365 } else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) { 1366 // 2 fingers 1367 return 2; 1368 } else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) { 1369 // 1 finger 1370 return 1; 1371 } 1372 return 0; 1373 } 1374 1375 /* 1376 * Keep in sync with PointerCapabilities in ServoTypes.h 1377 */ 1378 private static final int NO_POINTER = 0x00000000; 1379 private static final int COARSE_POINTER = 0x00000001; 1380 private static final int FINE_POINTER = 0x00000002; 1381 private static final int HOVER_CAPABLE_POINTER = 0x00000004; 1382 1383 private static int getPointerCapabilities(final InputDevice inputDevice) { 1384 int result = NO_POINTER; 1385 final int sources = inputDevice.getSources(); 1386 1387 // Blink checks fine pointer at first, then it check coarse pointer. 1388 // So, we should use same order for compatibility. 1389 // Also, if using Chrome OS, source may be SOURCE_MOUSE | SOURCE_TOUCHSCREEN | SOURCE_STYLUS 1390 // even if no touch screen. So we shouldn't check TOUCHSCREEN at first. 1391 1392 if (hasInputDeviceSource(sources, InputDevice.SOURCE_MOUSE) 1393 || hasInputDeviceSource(sources, InputDevice.SOURCE_STYLUS) 1394 || hasInputDeviceSource(sources, InputDevice.SOURCE_TOUCHPAD) 1395 || hasInputDeviceSource(sources, InputDevice.SOURCE_TRACKBALL)) { 1396 result |= FINE_POINTER; 1397 } else if (hasInputDeviceSource(sources, InputDevice.SOURCE_TOUCHSCREEN) 1398 || hasInputDeviceSource(sources, InputDevice.SOURCE_JOYSTICK)) { 1399 result |= COARSE_POINTER; 1400 } 1401 1402 if (hasInputDeviceSource(sources, InputDevice.SOURCE_MOUSE) 1403 || hasInputDeviceSource(sources, InputDevice.SOURCE_TOUCHPAD) 1404 || hasInputDeviceSource(sources, InputDevice.SOURCE_TRACKBALL) 1405 || hasInputDeviceSource(sources, InputDevice.SOURCE_JOYSTICK)) { 1406 result |= HOVER_CAPABLE_POINTER; 1407 } 1408 1409 return result; 1410 } 1411 1412 // For any-pointer and any-hover media queries features. 1413 /* package */ static int getAllPointerCapabilities() { 1414 int result = NO_POINTER; 1415 1416 for (final int deviceId : InputDevice.getDeviceIds()) { 1417 final InputDevice inputDevice = InputDevice.getDevice(deviceId); 1418 if (inputDevice == null || !InputDeviceUtils.isPointerTypeDevice(inputDevice)) { 1419 continue; 1420 } 1421 1422 result |= getPointerCapabilities(inputDevice); 1423 } 1424 1425 return result; 1426 } 1427 1428 /* 1429 * Keep in sync with PointingDevices in LookAndFeel.h 1430 */ 1431 private static final int POINTING_DEVICE_NONE = 0x00000000; 1432 private static final int POINTING_DEVICE_MOUSE = 0x00000001; 1433 private static final int POINTING_DEVICE_TOUCH = 0x00000002; 1434 private static final int POINTING_DEVICE_PEN = 0x00000004; 1435 1436 private static int getPointingDeviceKinds(final InputDevice inputDevice) { 1437 int result = POINTING_DEVICE_NONE; 1438 final int sources = inputDevice.getSources(); 1439 1440 // TODO(krosylight): For now this code is for telemetry purpose, but ultimately we want to 1441 // replace the capabilities code above and move the capabilities computation into layout. We'll 1442 // then have to add all the extra devices too that are not mouse/touch/pen. (Bug 1918207) 1443 // We don't treat other devices properly for pointerType after all: 1444 // https://searchfox.org/mozilla-central/rev/3b59c739df66574d94022a684596845cd05e7c65/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PanZoomController.java#749-761 1445 1446 if (hasInputDeviceSource(sources, InputDevice.SOURCE_MOUSE)) { 1447 result |= POINTING_DEVICE_MOUSE; 1448 } 1449 if (hasInputDeviceSource(sources, InputDevice.SOURCE_TOUCHSCREEN)) { 1450 result |= POINTING_DEVICE_TOUCH; 1451 } 1452 if (hasInputDeviceSource(sources, InputDevice.SOURCE_STYLUS)) { 1453 result |= POINTING_DEVICE_PEN; 1454 } 1455 if (hasInputDeviceSource(sources, InputDevice.SOURCE_BLUETOOTH_STYLUS)) { 1456 result |= POINTING_DEVICE_PEN; 1457 } 1458 1459 return result; 1460 } 1461 1462 // For pointing devices telemetry. 1463 /* package */ static int getPointingDeviceKinds() { 1464 int result = POINTING_DEVICE_NONE; 1465 1466 for (final int deviceId : InputDevice.getDeviceIds()) { 1467 final InputDevice inputDevice = InputDevice.getDevice(deviceId); 1468 if (inputDevice == null || !InputDeviceUtils.isPointerTypeDevice(inputDevice)) { 1469 continue; 1470 } 1471 1472 result |= getPointingDeviceKinds(inputDevice); 1473 } 1474 1475 return result; 1476 } 1477 1478 private static boolean hasInputDeviceSource(final int sources, final int inputDeviceSource) { 1479 return (sources & inputDeviceSource) == inputDeviceSource; 1480 } 1481 1482 public static synchronized void setScreenSizeOverride(final Rect size) { 1483 sScreenSizeOverride = size; 1484 } 1485 1486 static final ScreenCompat sScreenCompat; 1487 1488 private interface ScreenCompat { 1489 Rect getScreenSize(); 1490 1491 int getRotation(); 1492 1493 int getDensityDpi(); 1494 1495 float getDensity(); 1496 } 1497 1498 private static class JellyBeanMR1ScreenCompat implements ScreenCompat { 1499 private int mDensityDpi = 0; 1500 private Float mDensity = null; 1501 1502 private static DisplayMetrics getDisplayMetrics() { 1503 return getApplicationContext().getResources().getDisplayMetrics(); 1504 } 1505 1506 @Override 1507 public Rect getScreenSize() { 1508 final WindowManager wm = 1509 (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE); 1510 final Display disp = wm.getDefaultDisplay(); 1511 final Point size = new Point(); 1512 disp.getRealSize(size); 1513 return new Rect(0, 0, size.x, size.y); 1514 } 1515 1516 @Override 1517 public int getRotation() { 1518 final WindowManager wm = 1519 (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE); 1520 return wm.getDefaultDisplay().getRotation(); 1521 } 1522 1523 @Override 1524 public int getDensityDpi() { 1525 if (mDensityDpi == 0) { 1526 mDensityDpi = getDisplayMetrics().densityDpi; 1527 } 1528 return mDensityDpi; 1529 } 1530 1531 @Override 1532 public float getDensity() { 1533 if (mDensity == null) { 1534 mDensity = getDisplayMetrics().density; 1535 } 1536 return mDensity; 1537 } 1538 } 1539 1540 @RequiresApi(Build.VERSION_CODES.S) 1541 private static class AndroidSScreenCompat implements ScreenCompat { 1542 @SuppressLint("StaticFieldLeak") 1543 private static Context sWindowContext; 1544 1545 private static Context getWindowContext() { 1546 if (sWindowContext == null) { 1547 final DisplayManager displayManager = 1548 (DisplayManager) getApplicationContext().getSystemService(Context.DISPLAY_SERVICE); 1549 final Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 1550 sWindowContext = 1551 getApplicationContext() 1552 .createWindowContext(display, WindowManager.LayoutParams.TYPE_APPLICATION, null); 1553 sWindowContext.registerComponentCallbacks( 1554 new ComponentCallbacks() { 1555 @Override 1556 public void onConfigurationChanged(final Configuration newConfig) { 1557 if (GeckoScreenOrientation.getInstance().update()) { 1558 // refreshScreenInfo is already called. 1559 return; 1560 } 1561 ScreenManagerHelper.refreshScreenInfo(); 1562 } 1563 1564 @Override 1565 public void onLowMemory() {} 1566 }); 1567 } 1568 return sWindowContext; 1569 } 1570 1571 private static DisplayMetrics getDisplayMetrics() { 1572 return getWindowContext().getResources().getDisplayMetrics(); 1573 } 1574 1575 @Override 1576 public Rect getScreenSize() { 1577 final WindowManager windowManager = getWindowContext().getSystemService(WindowManager.class); 1578 return windowManager.getCurrentWindowMetrics().getBounds(); 1579 } 1580 1581 @Override 1582 public int getRotation() { 1583 final WindowManager windowManager = getWindowContext().getSystemService(WindowManager.class); 1584 return windowManager.getDefaultDisplay().getRotation(); 1585 } 1586 1587 @Override 1588 public int getDensityDpi() { 1589 return getDisplayMetrics().densityDpi; 1590 } 1591 1592 @Override 1593 public float getDensity() { 1594 return getDisplayMetrics().density; 1595 } 1596 } 1597 1598 static { 1599 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 1600 sScreenCompat = new AndroidSScreenCompat(); 1601 } else { 1602 sScreenCompat = new JellyBeanMR1ScreenCompat(); 1603 } 1604 } 1605 1606 /* package */ static Rect getScreenSizeIgnoreOverride() { 1607 return sScreenCompat.getScreenSize(); 1608 } 1609 1610 @WrapForJNI(calledFrom = "gecko") 1611 private static synchronized Rect getScreenSize() { 1612 if (sScreenSizeOverride != null) { 1613 return sScreenSizeOverride; 1614 } 1615 1616 return getScreenSizeIgnoreOverride(); 1617 } 1618 1619 @WrapForJNI(calledFrom = "any") 1620 public static int getAudioOutputFramesPerBuffer() { 1621 if (BuildConfig.DEBUG_BUILD && isIsolatedProcess()) { 1622 // AudioManager.getProperty won't return on isolated process 1623 throw new UnsupportedOperationException( 1624 "getAudioOutputFramesPerBuffer is not supported in isolated processes"); 1625 } 1626 final int DEFAULT = 512; 1627 1628 final AudioManager am = 1629 (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE); 1630 if (am == null) { 1631 return DEFAULT; 1632 } 1633 final String prop = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 1634 if (prop == null) { 1635 return DEFAULT; 1636 } 1637 return Integer.parseInt(prop); 1638 } 1639 1640 @WrapForJNI(calledFrom = "any") 1641 public static int getAudioOutputSampleRate() { 1642 if (BuildConfig.DEBUG_BUILD && isIsolatedProcess()) { 1643 // AudioManager.getProperty won't return on isolated process 1644 throw new UnsupportedOperationException( 1645 "getAudioOutputSampleRate is not supported in isolated processes"); 1646 } 1647 final int DEFAULT = 44100; 1648 1649 final AudioManager am = 1650 (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE); 1651 if (am == null) { 1652 return DEFAULT; 1653 } 1654 final String prop = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 1655 if (prop == null) { 1656 return DEFAULT; 1657 } 1658 return Integer.parseInt(prop); 1659 } 1660 1661 @WrapForJNI(calledFrom = "any") 1662 public static void setCommunicationAudioModeOn(final boolean on) { 1663 final AudioManager am = 1664 (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE); 1665 if (am == null) { 1666 return; 1667 } 1668 1669 try { 1670 if (on) { 1671 Log.e(LOGTAG, "Setting communication mode ON"); 1672 // This shouldn't throw, but does throw NullPointerException on a very 1673 // small number of devices. 1674 am.startBluetoothSco(); 1675 am.setBluetoothScoOn(true); 1676 } else { 1677 Log.e(LOGTAG, "Setting communication mode OFF"); 1678 am.stopBluetoothSco(); 1679 am.setBluetoothScoOn(false); 1680 } 1681 } catch (final SecurityException | NullPointerException e) { 1682 Log.e(LOGTAG, "could not set communication mode", e); 1683 } 1684 } 1685 1686 @WrapForJNI 1687 public static String[] getDefaultLocales() { 1688 final LocaleList list = LocaleList.getDefault(); 1689 final int n = list.size(); 1690 final String[] locales = new String[n]; 1691 for (int i = 0; i < n; i++) { 1692 locales[i] = list.get(i).toLanguageTag(); 1693 } 1694 return locales; 1695 } 1696 1697 public static void setIs24HourFormat(final Boolean is24HourFormat) { 1698 sIs24HourFormat = is24HourFormat; 1699 } 1700 1701 @WrapForJNI 1702 public static boolean getIs24HourFormat() { 1703 return sIs24HourFormat; 1704 } 1705 1706 @WrapForJNI 1707 public static String getAppName() { 1708 final Context context = getApplicationContext(); 1709 final ApplicationInfo info = context.getApplicationInfo(); 1710 final int id = info.labelRes; 1711 return id == 0 ? info.nonLocalizedLabel.toString() : context.getString(id); 1712 } 1713 1714 @WrapForJNI(calledFrom = "gecko") 1715 private static int getMemoryUsage(final String stateName) { 1716 final Debug.MemoryInfo memInfo = new Debug.MemoryInfo(); 1717 Debug.getMemoryInfo(memInfo); 1718 final String usage = memInfo.getMemoryStat(stateName); 1719 if (usage == null) { 1720 return -1; 1721 } 1722 try { 1723 return Integer.parseInt(usage); 1724 } catch (final NumberFormatException e) { 1725 return -1; 1726 } 1727 } 1728 1729 @WrapForJNI 1730 private static void crashByUncaughtException() { 1731 final Thread crashThread = 1732 new Thread("UncaughtExceptionThread") { 1733 @Override 1734 public void run() { 1735 throw new IllegalStateException(); 1736 } 1737 }; 1738 crashThread.start(); 1739 } 1740 1741 @WrapForJNI 1742 public static native boolean isParentProcess(); 1743 1744 @WrapForJNI 1745 public static native boolean isGpuProcessEnabled(); 1746 1747 @WrapForJNI 1748 public static native boolean isInteractiveWidgetDefaultResizesVisual(); 1749 1750 @WrapForJNI 1751 @SuppressLint("NewApi") 1752 public static boolean isIsolatedProcess() { 1753 // This method was added in SDK 16 but remained hidden until SDK 28, meaning we are okay to call 1754 // this on any SDK level but must suppress the new API lint. 1755 return android.os.Process.isIsolated(); 1756 } 1757 1758 @WrapForJNI(dispatchTo = "gecko") 1759 public static native void onSystemLocaleChanged(); 1760 1761 @WrapForJNI(dispatchTo = "gecko") 1762 public static native void onTimezoneChanged(); 1763 1764 @WrapForJNI 1765 public static native void logGpuProcessLaunchFailure(String aMessage); 1766 }