Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
package org.mozilla.gecko;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.LocaleList;
import android.os.Looper;
import android.os.PowerManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.InputDevice;
import android.view.WindowManager;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import androidx.collection.SimpleArrayMap;
import androidx.core.content.res.ResourcesCompat;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import org.jetbrains.annotations.NotNull;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.InputDeviceUtils;
import org.mozilla.gecko.util.ProxySelector;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.geckoview.BuildConfig;
import org.mozilla.geckoview.CrashHandler;
import org.mozilla.geckoview.R;
public class GeckoAppShell {
private static final String LOGTAG = "GeckoAppShell";
/*
* Keep these values consistent with |SensorType| in HalSensor.h
*/
public static final int SENSOR_ORIENTATION = 0;
public static final int SENSOR_ACCELERATION = 1;
public static final int SENSOR_PROXIMITY = 2;
public static final int SENSOR_LINEAR_ACCELERATION = 3;
public static final int SENSOR_GYROSCOPE = 4;
public static final int SENSOR_LIGHT = 5;
public static final int SENSOR_ROTATION_VECTOR = 6;
public static final int SENSOR_GAME_ROTATION_VECTOR = 7;
// We have static members only.
private GeckoAppShell() {}
// Name for app-scoped prefs
public static final String APP_PREFS_NAME = "GeckoApp";
private static class GeckoCrashHandler extends CrashHandler {
public GeckoCrashHandler(final Class<? extends Service> handlerService) {
super(handlerService);
}
@Override
public String getAppPackageName() {
final Context appContext = getAppContext();
if (appContext == null) {
return "<unknown>";
}
return appContext.getPackageName();
}
@Override
public Context getAppContext() {
return getApplicationContext();
}
@SuppressLint("ApplySharedPref")
@Override
public boolean reportException(final Thread thread, final Throwable exc) {
try {
if (exc instanceof OutOfMemoryError) {
final SharedPreferences prefs =
getApplicationContext().getSharedPreferences(APP_PREFS_NAME, 0);
final SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(PREFS_OOM_EXCEPTION, true);
// Synchronously write to disk so we know it's done before we
// shutdown
editor.commit();
}
reportJavaCrash(exc, getExceptionStackTrace(exc));
} catch (final Throwable e) {
}
// reportJavaCrash should have caused us to hard crash. If we're still here,
// it probably means Gecko is not loaded, and we should do something else.
if (BuildConfig.MOZ_CRASHREPORTER && BuildConfig.MOZILLA_OFFICIAL) {
// Only use Java crash reporter if enabled on official build.
return super.reportException(thread, exc);
}
return false;
}
}
private static String sAppNotes;
private static CrashHandler sCrashHandler;
public static synchronized CrashHandler ensureCrashHandling(
final Class<? extends Service> handler) {
if (sCrashHandler == null) {
sCrashHandler = new GeckoCrashHandler(handler);
}
return sCrashHandler;
}
private static Class<? extends Service> sCrashHandlerService;
public static synchronized void setCrashHandlerService(
final Class<? extends Service> handlerService) {
sCrashHandlerService = handlerService;
}
public static synchronized Class<? extends Service> getCrashHandlerService() {
return sCrashHandlerService;
}
@WrapForJNI(exceptionMode = "ignore")
public static synchronized String getAppNotes() {
return sAppNotes;
}
public static synchronized void appendAppNotesToCrashReport(final String notes) {
if (sAppNotes == null) {
sAppNotes = notes;
} else {
sAppNotes += '\n' + notes;
}
}
private static volatile boolean locationHighAccuracyEnabled;
private static volatile boolean locationListeningRequested = false;
private static volatile boolean locationPaused = false;
// See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB.
private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
/*
* Device RAM threshold requirement for adding additional headers.
* Keep in sync with RAM_THRESHOLD_MEGABYTES defined in
*/
private static final int ADDITIONAL_SEARCH_HEADER_RAM_THRESHOLD_MEGABYTES = 1024;
private static int sDensityDpi;
private static Float sDensity;
private static int sScreenDepth;
private static boolean sUseMaxScreenDepth;
private static Float sScreenRefreshRate;
/* Is the value in sVibrationEndTime valid? */
private static boolean sVibrationMaybePlaying;
/* Time (in System.nanoTime() units) when the currently-playing vibration
* is scheduled to end. This value is valid only when
* sVibrationMaybePlaying is true. */
private static long sVibrationEndTime;
private static Sensor gAccelerometerSensor;
private static Sensor gLinearAccelerometerSensor;
private static Sensor gGyroscopeSensor;
private static Sensor gOrientationSensor;
private static Sensor gLightSensor;
private static Sensor gRotationVectorSensor;
private static Sensor gGameRotationVectorSensor;
/*
* Keep in sync with constants found here:
*/
public static final int WPL_STATE_START = 0x00000001;
public static final int WPL_STATE_STOP = 0x00000010;
public static final int WPL_STATE_IS_DOCUMENT = 0x00020000;
public static final int WPL_STATE_IS_NETWORK = 0x00040000;
/* Keep in sync with constants found here:
*/
public static final int LINK_TYPE_UNKNOWN = 0;
public static final int LINK_TYPE_ETHERNET = 1;
public static final int LINK_TYPE_USB = 2;
public static final int LINK_TYPE_WIFI = 3;
public static final int LINK_TYPE_WIMAX = 4;
public static final int LINK_TYPE_MOBILE = 9;
public static final String PREFS_OOM_EXCEPTION = "OOMException";
/* The Android-side API: API methods that Android calls */
// helper methods
@WrapForJNI
/* package */ static native void reportJavaCrash(Throwable exc, String stackTrace);
private static Rect sScreenSizeOverride;
@WrapForJNI(stubName = "NotifyObservers", dispatchTo = "gecko")
private static native void nativeNotifyObservers(String topic, String data);
@WrapForJNI(stubName = "AppendAppNotesToCrashReport", dispatchTo = "gecko")
public static native void nativeAppendAppNotesToCrashReport(final String notes);
@RobocopTarget
public static void notifyObservers(final String topic, final String data) {
notifyObservers(topic, data, GeckoThread.State.RUNNING);
}
public static void notifyObservers(
final String topic, final String data, final GeckoThread.State state) {
if (GeckoThread.isStateAtLeast(state)) {
nativeNotifyObservers(topic, data);
} else {
GeckoThread.queueNativeCallUntil(
state,
GeckoAppShell.class,
"nativeNotifyObservers",
String.class,
topic,
String.class,
data);
}
}
/*
* The Gecko-side API: API methods that Gecko calls
*/
@WrapForJNI(exceptionMode = "ignore")
private static String getExceptionStackTrace(final Throwable e) {
return CrashHandler.getExceptionStackTrace(CrashHandler.getRootException(e));
}
@WrapForJNI(exceptionMode = "ignore")
private static synchronized void handleUncaughtException(final Throwable e) {
if (sCrashHandler != null) {
sCrashHandler.uncaughtException(null, e);
}
}
private static float getLocationAccuracy(final Location location) {
final float radius = location.getAccuracy();
return (location.hasAccuracy() && radius > 0) ? radius : 1001;
}
private static Location determineReliableLocation(
@NotNull final Location locA, @NotNull final Location locB) {
// The 6 seconds were chosen arbitrarily
final long closeTime = 6000000000L;
final boolean isNearSameTime =
Math.abs((locA.getElapsedRealtimeNanos() - locB.getElapsedRealtimeNanos())) <= closeTime;
final boolean isAMoreAccurate = getLocationAccuracy(locA) < getLocationAccuracy(locB);
final boolean isAMoreRecent = locA.getElapsedRealtimeNanos() > locB.getElapsedRealtimeNanos();
if (isNearSameTime) {
return isAMoreAccurate ? locA : locB;
}
return isAMoreRecent ? locA : locB;
}
// Permissions are explicitly checked when requesting content permission.
@SuppressLint("MissingPermission")
private static @Nullable Location getLastKnownLocation(final LocationManager lm) {
Location lastKnownLocation = null;
final List<String> providers = lm.getAllProviders();
for (final String provider : providers) {
final Location location = lm.getLastKnownLocation(provider);
if (location == null) {
continue;
}
if (lastKnownLocation == null) {
lastKnownLocation = location;
continue;
}
lastKnownLocation = determineReliableLocation(lastKnownLocation, location);
}
return lastKnownLocation;
}
// Toggles the location listeners on/off, which will then provide/stop location information
@WrapForJNI(calledFrom = "gecko")
private static synchronized boolean enableLocationUpdates(final boolean enable) {
locationListeningRequested = enable;
final boolean canListen = updateLocationListeners();
if (!canListen && locationListeningRequested) {
// Didn't successfully start listener when requested
locationListeningRequested = false;
}
return canListen;
}
// Permissions are explicitly checked when requesting content permission.
@SuppressLint("MissingPermission")
private static synchronized boolean updateLocationListeners() {
final boolean shouldListen = locationListeningRequested && !locationPaused;
final LocationManager lm = getLocationManager(getApplicationContext());
if (lm == null) {
return false;
}
if (!shouldListen) {
// Could not complete request, because paused
if (locationListeningRequested) {
return false;
}
lm.removeUpdates(sAndroidListeners);
return true;
}
if (!lm.isProviderEnabled(LocationManager.GPS_PROVIDER)
&& !lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
return false;
}
final Location lastKnownLocation = getLastKnownLocation(lm);
if (lastKnownLocation != null) {
sAndroidListeners.onLocationChanged(lastKnownLocation);
}
final Criteria criteria = new Criteria();
criteria.setSpeedRequired(false);
criteria.setBearingRequired(false);
criteria.setAltitudeRequired(false);
if (locationHighAccuracyEnabled) {
criteria.setAccuracy(Criteria.ACCURACY_FINE);
} else {
criteria.setAccuracy(Criteria.ACCURACY_COARSE);
}
final String provider = lm.getBestProvider(criteria, true);
if (provider == null) {
return false;
}
final Looper l = Looper.getMainLooper();
lm.requestLocationUpdates(provider, 100, 0.5f, sAndroidListeners, l);
return true;
}
public static void pauseLocation() {
locationPaused = true;
updateLocationListeners();
}
public static void resumeLocation() {
locationPaused = false;
updateLocationListeners();
}
private static LocationManager getLocationManager(final Context context) {
try {
return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
} catch (final NoSuchFieldError e) {
// Some Tegras throw exceptions about missing the CONTROL_LOCATION_UPDATES permission,
// which allows enabling/disabling location update notifications from the cell radio.
// CONTROL_LOCATION_UPDATES is not for use by normal applications, but we might be
// hitting this problem if the Tegras are confused about missing cell radios.
Log.e(LOGTAG, "LOCATION_SERVICE not found?!", e);
return null;
}
}
@WrapForJNI(calledFrom = "gecko")
private static void enableLocationHighAccuracy(final boolean enable) {
locationHighAccuracyEnabled = enable;
}
@WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
/* package */ static native void onSensorChanged(
int halType, float x, float y, float z, float w, long time);
@WrapForJNI(calledFrom = "any", dispatchTo = "gecko")
/* package */ static native void onLocationChanged(
double latitude,
double longitude,
double altitude,
float accuracy,
float altitudeAccuracy,
float heading,
float speed);
private static class AndroidListeners implements SensorEventListener, LocationListener {
@Override
public void onAccuracyChanged(final Sensor sensor, final int accuracy) {}
@Override
public void onSensorChanged(final SensorEvent s) {
final int sensorType = s.sensor.getType();
int halType = 0;
float x = 0.0f, y = 0.0f, z = 0.0f, w = 0.0f;
// SensorEvent timestamp is in nanoseconds, Gecko expects microseconds.
final long time = s.timestamp / 1000;
switch (sensorType) {
case Sensor.TYPE_ACCELEROMETER:
case Sensor.TYPE_LINEAR_ACCELERATION:
case Sensor.TYPE_ORIENTATION:
if (sensorType == Sensor.TYPE_ACCELEROMETER) {
halType = SENSOR_ACCELERATION;
} else if (sensorType == Sensor.TYPE_LINEAR_ACCELERATION) {
halType = SENSOR_LINEAR_ACCELERATION;
} else {
halType = SENSOR_ORIENTATION;
}
x = s.values[0];
y = s.values[1];
z = s.values[2];
break;
case Sensor.TYPE_GYROSCOPE:
halType = SENSOR_GYROSCOPE;
x = (float) Math.toDegrees(s.values[0]);
y = (float) Math.toDegrees(s.values[1]);
z = (float) Math.toDegrees(s.values[2]);
break;
case Sensor.TYPE_LIGHT:
halType = SENSOR_LIGHT;
x = s.values[0];
break;
case Sensor.TYPE_ROTATION_VECTOR:
case Sensor.TYPE_GAME_ROTATION_VECTOR: // API >= 18
halType =
(sensorType == Sensor.TYPE_ROTATION_VECTOR
? SENSOR_ROTATION_VECTOR
: SENSOR_GAME_ROTATION_VECTOR);
x = s.values[0];
y = s.values[1];
z = s.values[2];
if (s.values.length >= 4) {
w = s.values[3];
} else {
// s.values[3] was optional in API <= 18, so we need to compute it
// The values form a unit quaternion, so we can compute the angle of
// rotation purely based on the given 3 values.
w =
1.0f
- s.values[0] * s.values[0]
- s.values[1] * s.values[1]
- s.values[2] * s.values[2];
w = (w > 0.0f) ? (float) Math.sqrt(w) : 0.0f;
}
break;
}
GeckoAppShell.onSensorChanged(halType, x, y, z, w, time);
}
// Geolocation.
@Override
public void onLocationChanged(final Location location) {
// No logging here: user-identifying information.
final double altitude = location.hasAltitude() ? location.getAltitude() : Double.NaN;
final float accuracy = location.hasAccuracy() ? location.getAccuracy() : Float.NaN;
final float altitudeAccuracy =
Build.VERSION.SDK_INT >= 26 && location.hasVerticalAccuracy()
? location.getVerticalAccuracyMeters()
: Float.NaN;
final float speed = location.hasSpeed() ? location.getSpeed() : Float.NaN;
final float heading = location.hasBearing() ? location.getBearing() : Float.NaN;
// nsGeoPositionCoords will convert NaNs to null for optional
// properties of the JavaScript Coordinates object.
GeckoAppShell.onLocationChanged(
location.getLatitude(),
location.getLongitude(),
altitude,
accuracy,
altitudeAccuracy,
heading,
speed);
}
@Override
public void onProviderDisabled(final String provider) {}
@Override
public void onProviderEnabled(final String provider) {}
@Override
public void onStatusChanged(final String provider, final int status, final Bundle extras) {}
}
private static final AndroidListeners sAndroidListeners = new AndroidListeners();
private static SimpleArrayMap<String, PowerManager.WakeLock> sWakeLocks;
/** Wake-lock for the CPU. */
static final String WAKE_LOCK_CPU = "cpu";
/** Wake-lock for the screen. */
static final String WAKE_LOCK_SCREEN = "screen";
/** Wake-lock for the audio-playing, eqaul to LOCK_CPU. */
static final String WAKE_LOCK_AUDIO_PLAYING = "audio-playing";
/** Wake-lock for the video-playing, eqaul to LOCK_SCREEN.. */
static final String WAKE_LOCK_VIDEO_PLAYING = "video-playing";
static final int WAKE_LOCKS_COUNT = 2;
/** No one holds the wake-lock. */
static final int WAKE_LOCK_STATE_UNLOCKED = 0;
/** The wake-lock is held by a foreground window. */
static final int WAKE_LOCK_STATE_LOCKED_FOREGROUND = 1;
/** The wake-lock is held by a background window. */
static final int WAKE_LOCK_STATE_LOCKED_BACKGROUND = 2;
@SuppressLint("Wakelock") // We keep the wake lock independent from the function
// scope, so we need to suppress the linter warning.
private static void setWakeLockState(final String lock, final int state) {
if (sWakeLocks == null) {
sWakeLocks = new SimpleArrayMap<>(WAKE_LOCKS_COUNT);
}
PowerManager.WakeLock wl = sWakeLocks.get(lock);
// we should still hold the lock for background audio.
if (WAKE_LOCK_AUDIO_PLAYING.equals(lock) && state == WAKE_LOCK_STATE_LOCKED_BACKGROUND) {
return;
}
if (state == WAKE_LOCK_STATE_LOCKED_FOREGROUND && wl == null) {
final PowerManager pm =
(PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
if (WAKE_LOCK_CPU.equals(lock) || WAKE_LOCK_AUDIO_PLAYING.equals(lock)) {
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lock);
} else if (WAKE_LOCK_SCREEN.equals(lock) || WAKE_LOCK_VIDEO_PLAYING.equals(lock)) {
// ON_AFTER_RELEASE is set, the user activity timer will be reset when the
// WakeLock is released, causing the illumination to remain on a bit longer.
wl =
pm.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, lock);
} else {
Log.w(LOGTAG, "Unsupported wake-lock: " + lock);
return;
}
wl.acquire();
sWakeLocks.put(lock, wl);
} else if (state != WAKE_LOCK_STATE_LOCKED_FOREGROUND && wl != null) {
wl.release();
sWakeLocks.remove(lock);
}
}
@SuppressWarnings("fallthrough")
@WrapForJNI(calledFrom = "gecko")
private static void enableSensor(final int aSensortype) {
final SensorManager sm =
(SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
switch (aSensortype) {
case SENSOR_GAME_ROTATION_VECTOR:
if (gGameRotationVectorSensor == null) {
gGameRotationVectorSensor = sm.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);
}
if (gGameRotationVectorSensor != null) {
sm.registerListener(
sAndroidListeners, gGameRotationVectorSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
if (gGameRotationVectorSensor != null) {
break;
}
// Fallthrough
case SENSOR_ROTATION_VECTOR:
if (gRotationVectorSensor == null) {
gRotationVectorSensor = sm.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
}
if (gRotationVectorSensor != null) {
sm.registerListener(
sAndroidListeners, gRotationVectorSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
if (gRotationVectorSensor != null) {
break;
}
// Fallthrough
case SENSOR_ORIENTATION:
if (gOrientationSensor == null) {
gOrientationSensor = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);
}
if (gOrientationSensor != null) {
sm.registerListener(
sAndroidListeners, gOrientationSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
break;
case SENSOR_ACCELERATION:
if (gAccelerometerSensor == null) {
gAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
if (gAccelerometerSensor != null) {
sm.registerListener(
sAndroidListeners, gAccelerometerSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
break;
case SENSOR_LIGHT:
if (gLightSensor == null) {
gLightSensor = sm.getDefaultSensor(Sensor.TYPE_LIGHT);
}
if (gLightSensor != null) {
sm.registerListener(sAndroidListeners, gLightSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
break;
case SENSOR_LINEAR_ACCELERATION:
if (gLinearAccelerometerSensor == null) {
gLinearAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
}
if (gLinearAccelerometerSensor != null) {
sm.registerListener(
sAndroidListeners, gLinearAccelerometerSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
break;
case SENSOR_GYROSCOPE:
if (gGyroscopeSensor == null) {
gGyroscopeSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
}
if (gGyroscopeSensor != null) {
sm.registerListener(
sAndroidListeners, gGyroscopeSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
break;
default:
Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " + aSensortype);
}
}
@SuppressWarnings("fallthrough")
@WrapForJNI(calledFrom = "gecko")
private static void disableSensor(final int aSensortype) {
final SensorManager sm =
(SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
switch (aSensortype) {
case SENSOR_GAME_ROTATION_VECTOR:
if (gGameRotationVectorSensor != null) {
sm.unregisterListener(sAndroidListeners, gGameRotationVectorSensor);
break;
}
// Fallthrough
case SENSOR_ROTATION_VECTOR:
if (gRotationVectorSensor != null) {
sm.unregisterListener(sAndroidListeners, gRotationVectorSensor);
break;
}
// Fallthrough
case SENSOR_ORIENTATION:
if (gOrientationSensor != null) {
sm.unregisterListener(sAndroidListeners, gOrientationSensor);
}
break;
case SENSOR_ACCELERATION:
if (gAccelerometerSensor != null) {
sm.unregisterListener(sAndroidListeners, gAccelerometerSensor);
}
break;
case SENSOR_LIGHT:
if (gLightSensor != null) {
sm.unregisterListener(sAndroidListeners, gLightSensor);
}
break;
case SENSOR_LINEAR_ACCELERATION:
if (gLinearAccelerometerSensor != null) {
sm.unregisterListener(sAndroidListeners, gLinearAccelerometerSensor);
}
break;
case SENSOR_GYROSCOPE:
if (gGyroscopeSensor != null) {
sm.unregisterListener(sAndroidListeners, gGyroscopeSensor);
}
break;
default:
Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype);
}
}
@WrapForJNI(calledFrom = "gecko")
private static void moveTaskToBack() {
// This is a vestige, to be removed as full-screen support for GeckoView is implemented.
}
@WrapForJNI(calledFrom = "gecko")
private static boolean hasHWVP8Encoder() {
return HardwareCodecCapabilityUtils.hasHWVP8(true /* aIsEncoder */);
}
@WrapForJNI(calledFrom = "gecko")
private static boolean hasHWVP8Decoder() {
return HardwareCodecCapabilityUtils.hasHWVP8(false /* aIsEncoder */);
}
@WrapForJNI(calledFrom = "gecko")
public static String getMimeTypeFromExtensions(final String aFileExt) {
final StringTokenizer st = new StringTokenizer(aFileExt, ".,; ");
String type = null;
String subType = null;
while (st.hasMoreElements()) {
final String ext = st.nextToken();
final String mt = getMimeTypeFromExtension(ext);
if (mt == null) continue;
final int slash = mt.indexOf('/');
final String tmpType = mt.substring(0, slash);
if (!tmpType.equalsIgnoreCase(type)) type = type == null ? tmpType : "*";
final String tmpSubType = mt.substring(slash + 1);
if (!tmpSubType.equalsIgnoreCase(subType)) subType = subType == null ? tmpSubType : "*";
}
if (type == null) type = "*";
if (subType == null) subType = "*";
return type + "/" + subType;
}
@WrapForJNI(dispatchTo = "gecko")
private static native void notifyAlertListener(String name, String topic, String cookie);
/**
* Called by the NotificationListener to notify Gecko that a previously shown notification has
* been closed.
*/
public static void onNotificationClose(final String name, final String cookie) {
if (GeckoThread.isRunning()) {
notifyAlertListener(name, "alertfinished", cookie);
}
}
/**
* Called by the NotificationListener to notify Gecko that a previously shown notification has
* been clicked on.
*/
public static void onNotificationClick(final String name, final String cookie) {
if (GeckoThread.isRunning()) {
notifyAlertListener(name, "alertclickcallback", cookie);
} else {
GeckoThread.queueNativeCallUntil(
GeckoThread.State.PROFILE_READY,
GeckoAppShell.class,
"notifyAlertListener",
name,
"alertclickcallback",
cookie);
}
}
public static synchronized void setDisplayDpiOverride(@Nullable final Integer dpi) {
if (dpi == null) {
return;
}
if (sDensityDpi != 0) {
Log.e(LOGTAG, "Tried to override screen DPI after it's already been set");
return;
}
sDensityDpi = dpi;
}
@WrapForJNI(calledFrom = "gecko")
public static synchronized int getDpi() {
if (sDensityDpi == 0) {
sDensityDpi = getApplicationContext().getResources().getDisplayMetrics().densityDpi;
}
return sDensityDpi;
}
public static synchronized void setDisplayDensityOverride(@Nullable final Float density) {
if (density == null) {
return;
}
if (sDensity != null) {
Log.e(LOGTAG, "Tried to override screen density after it's already been set");
return;
}
sDensity = density;
}
@WrapForJNI(calledFrom = "gecko")
private static synchronized float getDensity() {
if (sDensity == null) {
sDensity = Float.valueOf(getApplicationContext().getResources().getDisplayMetrics().density);
}
return sDensity;
}
private static int sTotalRam;
private static int getTotalRam(final Context context) {
if (sTotalRam == 0) {
final ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
final ActivityManager am =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
am.getMemoryInfo(memInfo); // `getMemoryInfo()` returns a value in B. Convert to MB.
sTotalRam = (int) (memInfo.totalMem / (1024 * 1024));
Log.d(LOGTAG, "System memory: " + sTotalRam + "MB.");
}
return sTotalRam;
}
@WrapForJNI(calledFrom = "gecko")
private static synchronized boolean isDeviceRamThresholdOkay() {
final Context applicationContext = getApplicationContext();
return getTotalRam(applicationContext) > ADDITIONAL_SEARCH_HEADER_RAM_THRESHOLD_MEGABYTES;
}
private static boolean isHighMemoryDevice(final Context context) {
return getTotalRam(context) > HIGH_MEMORY_DEVICE_THRESHOLD_MB;
}
public static synchronized void useMaxScreenDepth(final boolean enable) {
sUseMaxScreenDepth = enable;
}
/** Returns the colour depth of the default screen. This will either be 32, 24 or 16. */
@WrapForJNI(calledFrom = "gecko")
public static synchronized int getScreenDepth() {
if (sScreenDepth == 0) {
sScreenDepth = 16;
final Context applicationContext = getApplicationContext();
final PixelFormat info = new PixelFormat();
final WindowManager wm =
(WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE);
PixelFormat.getPixelFormatInfo(wm.getDefaultDisplay().getPixelFormat(), info);
if (info.bitsPerPixel >= 24 && isHighMemoryDevice(applicationContext)) {
sScreenDepth = sUseMaxScreenDepth ? info.bitsPerPixel : 24;
}
}
return sScreenDepth;
}
@WrapForJNI(calledFrom = "gecko")
public static synchronized float getScreenRefreshRate() {
if (sScreenRefreshRate != null) {
return sScreenRefreshRate;
}
final WindowManager wm =
(WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
final float refreshRate = wm.getDefaultDisplay().getRefreshRate();
// Android 11+ supports multiple refresh rate. So we have to get refresh rate per call.
if (Build.VERSION.SDK_INT < 30) {
// Until Android 10, refresh rate is fixed, so we can cache it.
sScreenRefreshRate = Float.valueOf(refreshRate);
}
return refreshRate;
}
@WrapForJNI(calledFrom = "gecko")
private static boolean hasHDRScreen() {
if (Build.VERSION.SDK_INT < 24) {
return false;
}
final WindowManager wm =
(WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
final Display display = wm.getDefaultDisplay();
if (Build.VERSION.SDK_INT >= 26) {
return display.isHdr();
}
final Display.HdrCapabilities hdrCapabilities = display.getHdrCapabilities();
if (hdrCapabilities == null) {
return false;
}
final int[] supportedHdrTypes = hdrCapabilities.getSupportedHdrTypes();
for (final int type : supportedHdrTypes) {
if (type == Display.HdrCapabilities.HDR_TYPE_HDR10
|| type == Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS
|| type == Display.HdrCapabilities.HDR_TYPE_HLG
|| type == Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION) {
return true;
}
}
return false;
}
@WrapForJNI(calledFrom = "gecko")
private static void performHapticFeedback(final boolean aIsLongPress) {
// Don't perform haptic feedback if a vibration is currently playing,
// because the haptic feedback will nuke the vibration.
if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) {
final int[] pattern;
if (aIsLongPress) {
pattern = new int[] {0, 1, 20, 21};
} else {
pattern = new int[] {0, 10, 20, 30};
}
vibrateOnHapticFeedbackEnabled(pattern);
sVibrationMaybePlaying = false;
sVibrationEndTime = 0;
}
}
private static Vibrator vibrator() {
return (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
}
// Helper method to convert integer array to long array.
private static long[] convertIntToLongArray(final int[] input) {
final long[] output = new long[input.length];
for (int i = 0; i < input.length; i++) {
output[i] = input[i];
}
return output;
}
// Vibrate only if haptic feedback is enabled.
private static void vibrateOnHapticFeedbackEnabled(final int[] milliseconds) {
if (Settings.System.getInt(
getApplicationContext().getContentResolver(),
Settings.System.HAPTIC_FEEDBACK_ENABLED,
0)
> 0) {
if (milliseconds.length == 1) {
vibrate(milliseconds[0]);
} else {
vibrate(convertIntToLongArray(milliseconds), -1);
}
}
}
@SuppressLint("MissingPermission")
@WrapForJNI(calledFrom = "gecko")
private static void vibrate(final long milliseconds) {
sVibrationEndTime = System.nanoTime() + milliseconds * 1000000;
sVibrationMaybePlaying = true;
try {
vibrator().vibrate(milliseconds);
} catch (final SecurityException ignore) {
Log.w(LOGTAG, "No VIBRATE permission");
}
}
@SuppressLint("MissingPermission")
@WrapForJNI(calledFrom = "gecko")
private static void vibrate(final long[] pattern, final int repeat) {
// If pattern.length is odd, the last element in the pattern is a
// meaningless delay, so don't include it in vibrationDuration.
long vibrationDuration = 0;
final int iterLen = pattern.length & ~1;
for (int i = 0; i < iterLen; i++) {
vibrationDuration += pattern[i];
}
sVibrationEndTime = System.nanoTime() + vibrationDuration * 1000000;
sVibrationMaybePlaying = true;
try {
vibrator().vibrate(pattern, repeat);
} catch (final SecurityException ignore) {
Log.w(LOGTAG, "No VIBRATE permission");
}
}
@SuppressLint("MissingPermission")
@WrapForJNI(calledFrom = "gecko")
private static void cancelVibrate() {
sVibrationMaybePlaying = false;
sVibrationEndTime = 0;
try {
vibrator().cancel();
} catch (final SecurityException ignore) {
Log.w(LOGTAG, "No VIBRATE permission");
}
}
private static ConnectivityManager sConnectivityManager;
private static void ensureConnectivityManager() {
if (sConnectivityManager == null) {
sConnectivityManager =
(ConnectivityManager)
getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
}
}
@WrapForJNI(calledFrom = "gecko")
private static boolean isNetworkLinkUp() {
ensureConnectivityManager();
try {
final NetworkInfo info = sConnectivityManager.getActiveNetworkInfo();
if (info == null || !info.isConnected()) return false;
} catch (final SecurityException se) {
return false;
}
return true;
}
@WrapForJNI(calledFrom = "gecko")
private static boolean isNetworkLinkKnown() {
ensureConnectivityManager();
try {
if (sConnectivityManager.getActiveNetworkInfo() == null) return false;
} catch (final SecurityException se) {
return false;
}
return true;
}
@WrapForJNI(calledFrom = "gecko")
private static int getNetworkLinkType() {
ensureConnectivityManager();
final NetworkInfo info = sConnectivityManager.getActiveNetworkInfo();
if (info == null) {
return LINK_TYPE_UNKNOWN;
}
switch (info.getType()) {
case ConnectivityManager.TYPE_ETHERNET:
return LINK_TYPE_ETHERNET;
case ConnectivityManager.TYPE_WIFI:
return LINK_TYPE_WIFI;
case ConnectivityManager.TYPE_WIMAX:
return LINK_TYPE_WIMAX;
case ConnectivityManager.TYPE_MOBILE:
return LINK_TYPE_MOBILE;
default:
Log.w(LOGTAG, "Ignoring the current network type.");
return LINK_TYPE_UNKNOWN;
}
}
@WrapForJNI(calledFrom = "gecko", exceptionMode = "nsresult")
private static String getDNSDomains() {
if (Build.VERSION.SDK_INT < 23) {
return "";
}
ensureConnectivityManager();
final Network net = sConnectivityManager.getActiveNetwork();
if (net == null) {
return "";
}
final LinkProperties lp = sConnectivityManager.getLinkProperties(net);
if (lp == null) {
return "";
}
return lp.getDomains();
}
@SuppressLint("ResourceType")
@WrapForJNI(calledFrom = "gecko")
private static int[] getSystemColors() {
// attrsAppearance[] must correspond to AndroidSystemColors structure in android/nsLookAndFeel.h
final int[] attrsAppearance = {
android.R.attr.textColorPrimary,
android.R.attr.textColorPrimaryInverse,
android.R.attr.textColorSecondary,
android.R.attr.textColorSecondaryInverse,
android.R.attr.textColorTertiary,
android.R.attr.textColorTertiaryInverse,
android.R.attr.textColorHighlight,
android.R.attr.colorForeground,
android.R.attr.colorBackground,
android.R.attr.panelColorForeground,
android.R.attr.panelColorBackground,
android.R.attr.colorAccent,
};
final int[] result = new int[attrsAppearance.length];
final ContextThemeWrapper contextThemeWrapper =
new ContextThemeWrapper(getApplicationContext(), android.R.style.TextAppearance);
final TypedArray appearance = contextThemeWrapper.obtainStyledAttributes(attrsAppearance);
if (appearance != null) {
for (int i = 0; i < appearance.getIndexCount(); i++) {
final int idx = appearance.getIndex(i);
final int color = appearance.getColor(idx, 0);
result[idx] = color;
}
appearance.recycle();
}
return result;
}
@WrapForJNI(calledFrom = "gecko")
private static byte[] getIconForExtension(final String aExt, final int iconSize) {
try {
int resolvedIconSize = iconSize;
if (iconSize <= 0) {
resolvedIconSize = 16;
}
String resolvedExt = aExt;
if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.') {
resolvedExt = aExt.substring(1);
}
final PackageManager pm = getApplicationContext().getPackageManager();
Drawable icon = getDrawableForExtension(pm, resolvedExt);
if (icon == null) {
// Use a generic icon.
icon =
ResourcesCompat.getDrawable(
getApplicationContext().getResources(),
R.drawable.ic_generic_file,
getApplicationContext().getTheme());
}
Bitmap bitmap = getBitmapFromDrawable(icon);
if (bitmap.getWidth() != resolvedIconSize || bitmap.getHeight() != resolvedIconSize) {
bitmap = Bitmap.createScaledBitmap(bitmap, resolvedIconSize, resolvedIconSize, true);
}
final ByteBuffer buf = ByteBuffer.allocate(resolvedIconSize * resolvedIconSize * 4);
bitmap.copyPixelsToBuffer(buf);
return buf.array();
} catch (final Exception e) {
Log.w(LOGTAG, "getIconForExtension failed.", e);
return null;
}
}
private static Bitmap getBitmapFromDrawable(final Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
int width = drawable.getIntrinsicWidth();
width = width > 0 ? width : 1;
int height = drawable.getIntrinsicHeight();
height = height > 0 ? height : 1;
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public static String getMimeTypeFromExtension(final String ext) {
final MimeTypeMap mtm = MimeTypeMap.getSingleton();
return mtm.getMimeTypeFromExtension(ext);
}
private static Drawable getDrawableForExtension(final PackageManager pm, final String aExt) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
final String mimeType = getMimeTypeFromExtension(aExt);
if (mimeType != null && mimeType.length() > 0) intent.setType(mimeType);
else return null;
final List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
if (list.size() == 0) return null;
final ResolveInfo resolveInfo = list.get(0);
if (resolveInfo == null) return null;
final ActivityInfo activityInfo = resolveInfo.activityInfo;
return activityInfo.loadIcon(pm);
}
@WrapForJNI(calledFrom = "gecko")
private static boolean getShowPasswordSetting() {
try {
final int showPassword =
Settings.System.getInt(
getApplicationContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1);
return (showPassword > 0);
} catch (final Exception e) {
return true;
}
}
private static Context sApplicationContext;
private static Boolean sIs24HourFormat = true;
@WrapForJNI
public static Context getApplicationContext() {
return sApplicationContext;
}
public static void setApplicationContext(final Context context) {
sApplicationContext = context;
}
/*
* Battery API related methods.
*/
@WrapForJNI(calledFrom = "gecko")
private static void enableBatteryNotifications() {
GeckoBatteryManager.enableNotifications();
}
@WrapForJNI(calledFrom = "gecko")
private static void disableBatteryNotifications() {
GeckoBatteryManager.disableNotifications();
}
@WrapForJNI(calledFrom = "gecko")
private static double[] getCurrentBatteryInformation() {
return GeckoBatteryManager.getCurrentInformation(getApplicationContext());
}
/* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */
@WrapForJNI(calledFrom = "gecko")
@RobocopTarget
public static boolean isTablet() {
return HardwareUtils.isTablet(getApplicationContext());
}
@WrapForJNI(calledFrom = "gecko")
private static double[] getCurrentNetworkInformation() {
return GeckoNetworkManager.getInstance().getCurrentInformation();
}
@WrapForJNI(calledFrom = "gecko")
private static void enableNetworkNotifications() {
ThreadUtils.runOnUiThread(() -> GeckoNetworkManager.getInstance().enableNotifications());
}
@WrapForJNI(calledFrom = "gecko")
private static void disableNetworkNotifications() {
ThreadUtils.runOnUiThread(
new Runnable() {
@Override
public void run() {
GeckoNetworkManager.getInstance().disableNotifications();
}
});
}
@WrapForJNI(calledFrom = "gecko")
private static short getScreenOrientation() {
return GeckoScreenOrientation.getInstance().getScreenOrientation().value;
}
/* package */ static int getRotation() {
return sScreenCompat.getRotation();
}
@WrapForJNI(calledFrom = "gecko")
private static int getScreenAngle() {
return GeckoScreenOrientation.getInstance().getAngle();
}
@WrapForJNI(calledFrom = "gecko")
private static void notifyWakeLockChanged(final String topic, final String state) {
final int intState;
if ("unlocked".equals(state)) {
intState = WAKE_LOCK_STATE_UNLOCKED;
} else if ("locked-foreground".equals(state)) {
intState = WAKE_LOCK_STATE_LOCKED_FOREGROUND;
} else if ("locked-background".equals(state)) {
intState = WAKE_LOCK_STATE_LOCKED_BACKGROUND;
} else {
throw new IllegalArgumentException();
}
setWakeLockState(topic, intState);
}
@WrapForJNI(calledFrom = "gecko")
private static String getProxyForURI(
final String spec, final String scheme, final String host, final int port) {
final ProxySelector ps = new ProxySelector();
final Proxy proxy = ps.select(scheme, host);
if (Proxy.NO_PROXY.equals(proxy)) {
return "DIRECT";
}
final InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
final String proxyString = proxyAddress.getHostString() + ":" + proxyAddress.getPort();
switch (proxy.type()) {
case HTTP:
return "PROXY " + proxyString;
case SOCKS:
return "SOCKS " + proxyString;
}
return "DIRECT";
}
@WrapForJNI(calledFrom = "gecko")
private static int getMaxTouchPoints() {
final PackageManager pm = getApplicationContext().getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND)) {
// at least, 5+ fingers.
return 5;
} else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
// at least, 2+ fingers.
return 2;
} else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) {
// 2 fingers
return 2;
} else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
// 1 finger
return 1;
}
return 0;
}
/*
* Keep in sync with PointerCapabilities in ServoTypes.h
*/
private static final int NO_POINTER = 0x00000000;
private static final int COARSE_POINTER = 0x00000001;
private static final int FINE_POINTER = 0x00000002;
private static final int HOVER_CAPABLE_POINTER = 0x00000004;
private static int getPointerCapabilities(final InputDevice inputDevice) {
int result = NO_POINTER;
final int sources = inputDevice.getSources();
// Blink checks fine pointer at first, then it check coarse pointer.
// So, we should use same order for compatibility.
// Also, if using Chrome OS, source may be SOURCE_MOUSE | SOURCE_TOUCHSCREEN | SOURCE_STYLUS
// even if no touch screen. So we shouldn't check TOUCHSCREEN at first.
if (hasInputDeviceSource(sources, InputDevice.SOURCE_MOUSE)
|| hasInputDeviceSource(sources, InputDevice.SOURCE_STYLUS)
|| hasInputDeviceSource(sources, InputDevice.SOURCE_TOUCHPAD)
|| hasInputDeviceSource(sources, InputDevice.SOURCE_TRACKBALL)) {
result |= FINE_POINTER;
} else if (hasInputDeviceSource(sources, InputDevice.SOURCE_TOUCHSCREEN)
|| hasInputDeviceSource(sources, InputDevice.SOURCE_JOYSTICK)) {
result |= COARSE_POINTER;
}
if (hasInputDeviceSource(sources, InputDevice.SOURCE_MOUSE)
|| hasInputDeviceSource(sources, InputDevice.SOURCE_TOUCHPAD)
|| hasInputDeviceSource(sources, InputDevice.SOURCE_TRACKBALL)
|| hasInputDeviceSource(sources, InputDevice.SOURCE_JOYSTICK)) {
result |= HOVER_CAPABLE_POINTER;
}
return result;
}
@WrapForJNI(calledFrom = "gecko")
// For any-pointer and any-hover media queries features.
private static int getAllPointerCapabilities() {
int result = NO_POINTER;
for (final int deviceId : InputDevice.getDeviceIds()) {
final InputDevice inputDevice = InputDevice.getDevice(deviceId);
if (inputDevice == null || !InputDeviceUtils.isPointerTypeDevice(inputDevice)) {
continue;
}
result |= getPointerCapabilities(inputDevice);
}
return result;
}
/*
* Keep in sync with PointingDevices in LookAndFeel.h
*/
private static final int POINTING_DEVICE_NONE = 0x00000000;
private static final int POINTING_DEVICE_MOUSE = 0x00000001;
private static final int POINTING_DEVICE_TOUCH = 0x00000002;
private static final int POINTING_DEVICE_PEN = 0x00000004;
private static int getPointingDeviceKinds(final InputDevice inputDevice) {
int result = POINTING_DEVICE_NONE;
final int sources = inputDevice.getSources();
// TODO(krosylight): For now this code is for telemetry purpose, but ultimately we want to
// replace the capabilities code above and move the capabilities computation into layout. We'll
// We don't treat other devices properly for pointerType after all:
if (hasInputDeviceSource(sources, InputDevice.SOURCE_MOUSE)) {
result |= POINTING_DEVICE_MOUSE;
}
if (hasInputDeviceSource(sources, InputDevice.SOURCE_TOUCHSCREEN)) {
result |= POINTING_DEVICE_TOUCH;
}
if (hasInputDeviceSource(sources, InputDevice.SOURCE_STYLUS)) {
result |= POINTING_DEVICE_PEN;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& hasInputDeviceSource(sources, InputDevice.SOURCE_BLUETOOTH_STYLUS)) {
result |= POINTING_DEVICE_PEN;
}
return result;
}
@WrapForJNI(calledFrom = "gecko")
// For pointing devices telemetry.
private static int getPointingDeviceKinds() {
int result = POINTING_DEVICE_NONE;
for (final int deviceId : InputDevice.getDeviceIds()) {
final InputDevice inputDevice = InputDevice.getDevice(deviceId);
if (inputDevice == null || !InputDeviceUtils.isPointerTypeDevice(inputDevice)) {
continue;
}
result |= getPointingDeviceKinds(inputDevice);
}
return result;
}
private static boolean hasInputDeviceSource(final int sources, final int inputDeviceSource) {
return (sources & inputDeviceSource) == inputDeviceSource;
}
public static synchronized void setScreenSizeOverride(final Rect size) {
sScreenSizeOverride = size;
}
static final ScreenCompat sScreenCompat;
private interface ScreenCompat {
Rect getScreenSize();
int getRotation();
}
private static class JellyBeanMR1ScreenCompat implements ScreenCompat {
public Rect getScreenSize() {
final WindowManager wm =
(WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
final Display disp = wm.getDefaultDisplay();
final Point size = new Point();
disp.getRealSize(size);
return new Rect(0, 0, size.x, size.y);
}
public int getRotation() {
final WindowManager wm =
(WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
return wm.getDefaultDisplay().getRotation();
}
}
@TargetApi(Build.VERSION_CODES.S)
private static class AndroidSScreenCompat implements ScreenCompat {
@SuppressLint("StaticFieldLeak")
private static Context sWindowContext;
private static Context getWindowContext() {
if (sWindowContext == null) {
final DisplayManager displayManager =
(DisplayManager) getApplicationContext().getSystemService(Context.DISPLAY_SERVICE);
final Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
sWindowContext =
getApplicationContext()
.createWindowContext(display, WindowManager.LayoutParams.TYPE_APPLICATION, null);
}
return sWindowContext;
}
public Rect getScreenSize() {
final WindowManager windowManager = getWindowContext().getSystemService(WindowManager.class);
return windowManager.getCurrentWindowMetrics().getBounds();
}
public int getRotation() {
final WindowManager windowManager = getWindowContext().getSystemService(WindowManager.class);
return windowManager.getDefaultDisplay().getRotation();
}
}
static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
sScreenCompat = new AndroidSScreenCompat();
} else {
sScreenCompat = new JellyBeanMR1ScreenCompat();
}
}
/* package */ static Rect getScreenSizeIgnoreOverride() {
return sScreenCompat.getScreenSize();
}
@WrapForJNI(calledFrom = "gecko")
private static synchronized Rect getScreenSize() {
if (sScreenSizeOverride != null) {
return sScreenSizeOverride;
}
return getScreenSizeIgnoreOverride();
}
@WrapForJNI(calledFrom = "any")
public static int getAudioOutputFramesPerBuffer() {
final int DEFAULT = 512;
final AudioManager am =
(AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
if (am == null) {
return DEFAULT;
}
final String prop = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
if (prop == null) {
return DEFAULT;
}
return Integer.parseInt(prop);
}
@WrapForJNI(calledFrom = "any")
public static int getAudioOutputSampleRate() {
final int DEFAULT = 44100;
final AudioManager am =
(AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
if (am == null) {
return DEFAULT;
}
final String prop = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
if (prop == null) {
return DEFAULT;
}
return Integer.parseInt(prop);
}
@WrapForJNI(calledFrom = "any")
public static void setCommunicationAudioModeOn(final boolean on) {
final AudioManager am =
(AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
if (am == null) {
return;
}
try {
if (on) {
Log.e(LOGTAG, "Setting communication mode ON");
// This shouldn't throw, but does throw NullPointerException on a very
// small number of devices.
am.startBluetoothSco();
am.setBluetoothScoOn(true);
} else {
Log.e(LOGTAG, "Setting communication mode OFF");
am.stopBluetoothSco();
am.setBluetoothScoOn(false);
}
} catch (final SecurityException | NullPointerException e) {
Log.e(LOGTAG, "could not set communication mode", e);
}
}
@WrapForJNI
public static String[] getDefaultLocales() {
// XXX We may have to convert some language codes such as "id" vs "in".
if (Build.VERSION.SDK_INT >= 24) {
final LocaleList localeList = LocaleList.getDefault();
final String[] locales = new String[localeList.size()];
for (int i = 0; i < localeList.size(); i++) {
locales[i] = localeList.get(i).toLanguageTag();
}
return locales;
}
final String[] locales = new String[1];
final Locale locale = Locale.getDefault();
locales[0] = locale.toLanguageTag();
return locales;
}
public static void setIs24HourFormat(final Boolean is24HourFormat) {
sIs24HourFormat = is24HourFormat;
}
@WrapForJNI
public static boolean getIs24HourFormat() {
return sIs24HourFormat;
}
@WrapForJNI
public static String getAppName() {
final Context context = getApplicationContext();
final ApplicationInfo info = context.getApplicationInfo();
final int id = info.labelRes;
return id == 0 ? info.nonLocalizedLabel.toString() : context.getString(id);
}
@WrapForJNI(calledFrom = "gecko")
private static int getMemoryUsage(final String stateName) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// No API to get Java heap usages.
return -1;
}
final Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(memInfo);
final String usage = memInfo.getMemoryStat(stateName);
if (usage == null) {
return -1;
}
try {
return Integer.parseInt(usage);
} catch (final NumberFormatException e) {
return -1;
}
}
@WrapForJNI
public static native boolean isParentProcess();
@WrapForJNI
public static native boolean isGpuProcessEnabled();
@WrapForJNI
public static native boolean isInteractiveWidgetDefaultResizesVisual();
@SuppressLint("NewApi")
public static boolean isIsolatedProcess() {
// This method was added in SDK 16 but remained hidden until SDK 28, meaning we are okay to call
// this on any SDK level but must suppress the new API lint.
return android.os.Process.isIsolated();
}
@WrapForJNI(dispatchTo = "gecko")
public static native void onSystemLocaleChanged();
@WrapForJNI(dispatchTo = "gecko")
public static native void onTimezoneChanged();
}