tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }