/* -*- 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
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import android.annotation.SuppressLint;
import org.mozilla.gecko.annotation.JNITarget;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.PanZoomController;
import org.mozilla.gecko.permissions.Permissions;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoRequest;
import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSContainer;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ProxySelector;
import org.mozilla.gecko.util.ThreadUtils;

import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
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.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.Camera;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.os.SystemClock;
import android.os.Vibrator;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.widget.AbsoluteLayout;

public class GeckoAppShell
{
    private static final String LOGTAG = "GeckoAppShell";

    // We have static members only.
    private GeckoAppShell() { }

    private static final CrashHandler CRASH_HANDLER = new CrashHandler() {
        @Override
        protected String getAppPackageName() {
            return AppConstants.ANDROID_PACKAGE_NAME;
        }

        @Override
        protected Context getAppContext() {
            return sContextGetter != null ? getApplicationContext() : null;
        }

        @Override
        protected Bundle getCrashExtras(final Thread thread, final Throwable exc) {
            final Bundle extras = super.getCrashExtras(thread, exc);

            extras.putString("ProductName", AppConstants.MOZ_APP_BASENAME);
            extras.putString("ProductID", AppConstants.MOZ_APP_ID);
            extras.putString("Version", AppConstants.MOZ_APP_VERSION);
            extras.putString("BuildID", AppConstants.MOZ_APP_BUILDID);
            extras.putString("Vendor", AppConstants.MOZ_APP_VENDOR);
            extras.putString("ReleaseChannel", AppConstants.MOZ_UPDATE_CHANNEL);
            return extras;
        }

        @Override
        public void uncaughtException(final Thread thread, final Throwable exc) {
            if (GeckoThread.isState(GeckoThread.State.EXITING) ||
                    GeckoThread.isState(GeckoThread.State.EXITED)) {
                // We've called System.exit. All exceptions after this point are Android
                // berating us for being nasty to it.
                return;
            }

            super.uncaughtException(thread, exc);
        }

        @Override
        public boolean reportException(final Thread thread, final Throwable exc) {
            try {
                if (exc instanceof OutOfMemoryError) {
                    SharedPreferences prefs = getSharedPreferences();
                    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 (AppConstants.MOZ_CRASHREPORTER && AppConstants.MOZILLA_OFFICIAL) {
                // Only use Java crash reporter if enabled on official build.
                return super.reportException(thread, exc);
            }
            return false;
        }
    };

    public static CrashHandler ensureCrashHandling() {
        // Crash handling is automatically enabled when GeckoAppShell is loaded.
        return CRASH_HANDLER;
    }

    private static volatile boolean locationHighAccuracyEnabled;

    // See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB.
    private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;

    static private int sDensityDpi;
    static private int sScreenDepth;

    /* 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 gProximitySensor;
    private static Sensor gLightSensor;
    private static Sensor gRotationVectorSensor;
    private static Sensor gGameRotationVectorSensor;

    private static final String GECKOREQUEST_RESPONSE_KEY = "response";
    private static final String GECKOREQUEST_ERROR_KEY = "error";

    /*
     * Keep in sync with constants found here:
     * http://dxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
    */
    static public final int WPL_STATE_START = 0x00000001;
    static public final int WPL_STATE_STOP = 0x00000010;
    static public final int WPL_STATE_IS_DOCUMENT = 0x00020000;
    static public final int WPL_STATE_IS_NETWORK = 0x00040000;

    /* Keep in sync with constants found here:
      http://dxr.mozilla.org/mozilla-central/source/netwerk/base/nsINetworkLinkService.idl
    */
    static public final int LINK_TYPE_UNKNOWN = 0;
    static public final int LINK_TYPE_ETHERNET = 1;
    static public final int LINK_TYPE_USB = 2;
    static public final int LINK_TYPE_WIFI = 3;
    static public final int LINK_TYPE_WIMAX = 4;
    static public final int LINK_TYPE_2G = 5;
    static public final int LINK_TYPE_3G = 6;
    static public final int LINK_TYPE_4G = 7;

    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);

    @WrapForJNI(dispatchTo = "gecko")
    public static native void notifyUriVisited(String uri);

    private static LayerView sLayerView;
    private static Rect sScreenSize;

    public static void setLayerView(LayerView lv) {
        if (sLayerView == lv) {
            return;
        }
        sLayerView = lv;
    }

    @RobocopTarget
    public static LayerView getLayerView() {
        return sLayerView;
    }

    /**
     * Sends an asynchronous request to Gecko.
     *
     * The response data will be passed to {@link GeckoRequest#onResponse(NativeJSObject)} if the
     * request succeeds; otherwise, {@link GeckoRequest#onError()} will fire.
     *
     * It can be called from any thread. The GeckoRequest callbacks will be executed on the Gecko thread.
     *
     * @param request The request to dispatch. Cannot be null.
     */
    @RobocopTarget
    public static void sendRequestToGecko(final GeckoRequest request) {
        final String responseMessage = "Gecko:Request" + request.getId();

        EventDispatcher.getInstance().registerGeckoThreadListener(new NativeEventListener() {
            @Override
            public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
                EventDispatcher.getInstance().unregisterGeckoThreadListener(this, event);
                if (!message.has(GECKOREQUEST_RESPONSE_KEY)) {
                    request.onError(message.getObject(GECKOREQUEST_ERROR_KEY));
                    return;
                }
                request.onResponse(message.getObject(GECKOREQUEST_RESPONSE_KEY));
            }
        }, responseMessage);

        notifyObservers(request.getName(), request.getData());
    }

    // Synchronously notify a Gecko observer; must be called from Gecko thread.
    @WrapForJNI(calledFrom = "gecko")
    public static native void syncNotifyObservers(String topic, String data);

    @WrapForJNI(stubName = "NotifyObservers", dispatchTo = "gecko")
    private static native void nativeNotifyObservers(String topic, String data);

    @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(Throwable e) {
        return CrashHandler.getExceptionStackTrace(CrashHandler.getRootException(e));
    }

    @WrapForJNI(exceptionMode = "ignore")
    private static void handleUncaughtException(Throwable e) {
        CRASH_HANDLER.uncaughtException(null, e);
    }

    @WrapForJNI
    public static void openWindowForNotification() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);

        getApplicationContext().startActivity(intent);
    }

    private static float getLocationAccuracy(Location location) {
        float radius = location.getAccuracy();
        return (location.hasAccuracy() && radius > 0) ? radius : 1001;
    }

    @SuppressLint("MissingPermission") // Permissions are explicitly checked for in enableLocation()
    private static Location getLastKnownLocation(LocationManager lm) {
        Location lastKnownLocation = null;
        List<String> providers = lm.getAllProviders();

        for (String provider : providers) {
            Location location = lm.getLastKnownLocation(provider);
            if (location == null) {
                continue;
            }

            if (lastKnownLocation == null) {
                lastKnownLocation = location;
                continue;
            }

            long timeDiff = location.getTime() - lastKnownLocation.getTime();
            if (timeDiff > 0 ||
                (timeDiff == 0 &&
                 getLocationAccuracy(location) < getLocationAccuracy(lastKnownLocation))) {
                lastKnownLocation = location;
            }
        }

        return lastKnownLocation;
    }

    @WrapForJNI(calledFrom = "gecko")
    @SuppressLint("MissingPermission") // Permissions are explicitly checked for within this method
    private static void enableLocation(final boolean enable) {
        final Runnable requestLocation = new Runnable() {
            @Override
            public void run() {
                LocationManager lm = getLocationManager(getApplicationContext());
                if (lm == null) {
                    return;
                }

                if (!enable) {
                    lm.removeUpdates(getLocationListener());
                    return;
                }

                Location lastKnownLocation = getLastKnownLocation(lm);
                if (lastKnownLocation != null) {
                    getLocationListener().onLocationChanged(lastKnownLocation);
                }

                Criteria criteria = new Criteria();
                criteria.setSpeedRequired(false);
                criteria.setBearingRequired(false);
                criteria.setAltitudeRequired(false);
                if (locationHighAccuracyEnabled) {
                    criteria.setAccuracy(Criteria.ACCURACY_FINE);
                    criteria.setCostAllowed(true);
                    criteria.setPowerRequirement(Criteria.POWER_HIGH);
                } else {
                    criteria.setAccuracy(Criteria.ACCURACY_COARSE);
                    criteria.setCostAllowed(false);
                    criteria.setPowerRequirement(Criteria.POWER_LOW);
                }

                String provider = lm.getBestProvider(criteria, true);
                if (provider == null)
                    return;

                Looper l = Looper.getMainLooper();
                lm.requestLocationUpdates(provider, 100, 0.5f, getLocationListener(), l);
            }
        };

        Permissions
                .from((Activity) getContext())
                .withPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
                .onUIThread()
                .doNotPromptIf(!enable)
                .run(requestLocation);
    }

    private static LocationManager getLocationManager(Context context) {
        try {
            return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        } catch (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 = "gecko")
    private static boolean setAlarm(int aSeconds, int aNanoSeconds) {
        AlarmManager am = (AlarmManager)
            getApplicationContext().getSystemService(Context.ALARM_SERVICE);

        Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(
                getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        // AlarmManager only supports millisecond precision
        long time = ((long) aSeconds * 1000) + ((long) aNanoSeconds / 1_000_000L);
        am.setExact(AlarmManager.RTC_WAKEUP, time, pi);

        return true;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void disableAlarm() {
        AlarmManager am = (AlarmManager)
            getApplicationContext().getSystemService(Context.ALARM_SERVICE);

        Intent intent = new Intent(getApplicationContext(), AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(
                getApplicationContext(), 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        am.cancel(pi);
    }

    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
    /* package */ static native void onSensorChanged(int hal_type, float x, float y, float z,
                                                     float w, int accuracy, long time);

    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
    /* package */ static native void onLocationChanged(double latitude, double longitude,
                                                       double altitude, float accuracy,
                                                       float bearing, float speed, long time);

    private static class DefaultListeners
            implements SensorEventListener, LocationListener, NotificationListener {
        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }

        private static int HalSensorAccuracyFor(int androidAccuracy) {
            switch (androidAccuracy) {
            case SensorManager.SENSOR_STATUS_UNRELIABLE:
                return GeckoHalDefines.SENSOR_ACCURACY_UNRELIABLE;
            case SensorManager.SENSOR_STATUS_ACCURACY_LOW:
                return GeckoHalDefines.SENSOR_ACCURACY_LOW;
            case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM:
                return GeckoHalDefines.SENSOR_ACCURACY_MED;
            case SensorManager.SENSOR_STATUS_ACCURACY_HIGH:
                return GeckoHalDefines.SENSOR_ACCURACY_HIGH;
            }
            return GeckoHalDefines.SENSOR_ACCURACY_UNKNOWN;
        }

        @Override
        public void onSensorChanged(SensorEvent s) {
            int sensor_type = s.sensor.getType();
            int hal_type = 0;
            float x = 0.0f, y = 0.0f, z = 0.0f, w = 0.0f;
            final int accuracy = HalSensorAccuracyFor(s.accuracy);
            // SensorEvent timestamp is in nanoseconds, Gecko expects microseconds.
            final long time = s.timestamp / 1000;

            switch (sensor_type) {
            case Sensor.TYPE_ACCELEROMETER:
            case Sensor.TYPE_LINEAR_ACCELERATION:
            case Sensor.TYPE_ORIENTATION:
                if (sensor_type == Sensor.TYPE_ACCELEROMETER) {
                    hal_type = GeckoHalDefines.SENSOR_ACCELERATION;
                } else if (sensor_type == Sensor.TYPE_LINEAR_ACCELERATION) {
                    hal_type = GeckoHalDefines.SENSOR_LINEAR_ACCELERATION;
                } else {
                    hal_type = GeckoHalDefines.SENSOR_ORIENTATION;
                }
                x = s.values[0];
                y = s.values[1];
                z = s.values[2];
                break;

            case Sensor.TYPE_GYROSCOPE:
                hal_type = GeckoHalDefines.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_PROXIMITY:
                hal_type = GeckoHalDefines.SENSOR_PROXIMITY;
                x = s.values[0];
                z = s.sensor.getMaximumRange();
                break;

            case Sensor.TYPE_LIGHT:
                hal_type = GeckoHalDefines.SENSOR_LIGHT;
                x = s.values[0];
                break;

            case Sensor.TYPE_ROTATION_VECTOR:
            case Sensor.TYPE_GAME_ROTATION_VECTOR: // API >= 18
                hal_type = (sensor_type == Sensor.TYPE_ROTATION_VECTOR ?
                        GeckoHalDefines.SENSOR_ROTATION_VECTOR :
                        GeckoHalDefines.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(hal_type, x, y, z, w, accuracy, time);
        }

        // Geolocation.
        @Override
        public void onLocationChanged(Location location) {
            // No logging here: user-identifying information.
            GeckoAppShell.onLocationChanged(location.getLatitude(), location.getLongitude(),
                                            location.getAltitude(), location.getAccuracy(),
                                            location.getBearing(), location.getSpeed(),
                                            location.getTime());
        }

        @Override
        public void onProviderDisabled(String provider)
        {
        }

        @Override
        public void onProviderEnabled(String provider)
        {
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras)
        {
        }

        @Override // NotificationListener
        public void showNotification(String name, String cookie, String host,
                                     String title, String text, String imageUrl) {
            // Default is to not show the notification, and immediate send close message.
            GeckoAppShell.onNotificationClose(name, cookie);
        }

        @Override // NotificationListener
        public void showPersistentNotification(String name, String cookie, String host,
                                               String title, String text, String imageUrl,
                                               String data) {
            // Default is to not show the notification, and immediate send close message.
            GeckoAppShell.onNotificationClose(name, cookie);
        }

        @Override // NotificationListener
        public void closeNotification(String name) {
            // Do nothing.
        }
    }

    private static final DefaultListeners DEFAULT_LISTENERS = new DefaultListeners();
    private static SensorEventListener sSensorListener = DEFAULT_LISTENERS;
    private static LocationListener sLocationListener = DEFAULT_LISTENERS;
    private static NotificationListener sNotificationListener = DEFAULT_LISTENERS;

    public static SensorEventListener getSensorListener() {
        return sSensorListener;
    }

    public static void setSensorListener(final SensorEventListener listener) {
        sSensorListener = (listener != null) ? listener : DEFAULT_LISTENERS;
    }

    public static LocationListener getLocationListener() {
        return sLocationListener;
    }

    public static void setLocationListener(final LocationListener listener) {
        sLocationListener = (listener != null) ? listener : DEFAULT_LISTENERS;
    }

    public static NotificationListener getNotificationListener() {
        return sNotificationListener;
    }

    public static void setNotificationListener(final NotificationListener listener) {
        sNotificationListener = (listener != null) ? listener : DEFAULT_LISTENERS;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void enableSensor(int aSensortype) {
        GeckoInterface gi = getGeckoInterface();
        if (gi == null) {
            return;
        }
        SensorManager sm = (SensorManager)
            getApplicationContext().getSystemService(Context.SENSOR_SERVICE);

        switch (aSensortype) {
        case GeckoHalDefines.SENSOR_GAME_ROTATION_VECTOR:
            if (gGameRotationVectorSensor == null) {
                gGameRotationVectorSensor = sm.getDefaultSensor(15);
                    // sm.getDefaultSensor(
                    //     Sensor.TYPE_GAME_ROTATION_VECTOR); // API >= 18
            }
            if (gGameRotationVectorSensor != null) {
                sm.registerListener(getSensorListener(),
                                    gGameRotationVectorSensor,
                                    SensorManager.SENSOR_DELAY_FASTEST);
            }
            if (gGameRotationVectorSensor != null) {
              break;
            }
            // Fallthrough

        case GeckoHalDefines.SENSOR_ROTATION_VECTOR:
            if (gRotationVectorSensor == null) {
                gRotationVectorSensor = sm.getDefaultSensor(
                    Sensor.TYPE_ROTATION_VECTOR);
            }
            if (gRotationVectorSensor != null) {
                sm.registerListener(getSensorListener(),
                                    gRotationVectorSensor,
                                    SensorManager.SENSOR_DELAY_FASTEST);
            }
            if (gRotationVectorSensor != null) {
              break;
            }
            // Fallthrough

        case GeckoHalDefines.SENSOR_ORIENTATION:
            if (gOrientationSensor == null) {
                gOrientationSensor = sm.getDefaultSensor(
                    Sensor.TYPE_ORIENTATION);
            }
            if (gOrientationSensor != null) {
                sm.registerListener(getSensorListener(),
                                    gOrientationSensor,
                                    SensorManager.SENSOR_DELAY_FASTEST);
            }
            break;

        case GeckoHalDefines.SENSOR_ACCELERATION:
            if (gAccelerometerSensor == null) {
                gAccelerometerSensor = sm.getDefaultSensor(
                    Sensor.TYPE_ACCELEROMETER);
            }
            if (gAccelerometerSensor != null) {
                sm.registerListener(getSensorListener(),
                                    gAccelerometerSensor,
                                    SensorManager.SENSOR_DELAY_FASTEST);
            }
            break;

        case GeckoHalDefines.SENSOR_PROXIMITY:
            if (gProximitySensor == null) {
                gProximitySensor = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
            }
            if (gProximitySensor != null) {
                sm.registerListener(getSensorListener(),
                                    gProximitySensor,
                                    SensorManager.SENSOR_DELAY_NORMAL);
            }
            break;

        case GeckoHalDefines.SENSOR_LIGHT:
            if (gLightSensor == null) {
                gLightSensor = sm.getDefaultSensor(Sensor.TYPE_LIGHT);
            }
            if (gLightSensor != null) {
                sm.registerListener(getSensorListener(),
                                    gLightSensor,
                                    SensorManager.SENSOR_DELAY_NORMAL);
            }
            break;

        case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION:
            if (gLinearAccelerometerSensor == null) {
                gLinearAccelerometerSensor = sm.getDefaultSensor(
                    Sensor.TYPE_LINEAR_ACCELERATION);
            }
            if (gLinearAccelerometerSensor != null) {
                sm.registerListener(getSensorListener(),
                                    gLinearAccelerometerSensor,
                                    SensorManager.SENSOR_DELAY_FASTEST);
            }
            break;

        case GeckoHalDefines.SENSOR_GYROSCOPE:
            if (gGyroscopeSensor == null) {
                gGyroscopeSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
            }
            if (gGyroscopeSensor != null) {
                sm.registerListener(getSensorListener(),
                                    gGyroscopeSensor,
                                    SensorManager.SENSOR_DELAY_FASTEST);
            }
            break;

        default:
            Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " +
                  aSensortype);
        }
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void disableSensor(int aSensortype) {
        GeckoInterface gi = getGeckoInterface();
        if (gi == null)
            return;

        SensorManager sm = (SensorManager)
            getApplicationContext().getSystemService(Context.SENSOR_SERVICE);

        switch (aSensortype) {
        case GeckoHalDefines.SENSOR_GAME_ROTATION_VECTOR:
            if (gGameRotationVectorSensor != null) {
                sm.unregisterListener(getSensorListener(), gGameRotationVectorSensor);
              break;
            }
            // Fallthrough

        case GeckoHalDefines.SENSOR_ROTATION_VECTOR:
            if (gRotationVectorSensor != null) {
                sm.unregisterListener(getSensorListener(), gRotationVectorSensor);
              break;
            }
            // Fallthrough

        case GeckoHalDefines.SENSOR_ORIENTATION:
            if (gOrientationSensor != null) {
                sm.unregisterListener(getSensorListener(), gOrientationSensor);
            }
            break;

        case GeckoHalDefines.SENSOR_ACCELERATION:
            if (gAccelerometerSensor != null) {
                sm.unregisterListener(getSensorListener(), gAccelerometerSensor);
            }
            break;

        case GeckoHalDefines.SENSOR_PROXIMITY:
            if (gProximitySensor != null) {
                sm.unregisterListener(getSensorListener(), gProximitySensor);
            }
            break;

        case GeckoHalDefines.SENSOR_LIGHT:
            if (gLightSensor != null) {
                sm.unregisterListener(getSensorListener(), gLightSensor);
            }
            break;

        case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION:
            if (gLinearAccelerometerSensor != null) {
                sm.unregisterListener(getSensorListener(), gLinearAccelerometerSensor);
            }
            break;

        case GeckoHalDefines.SENSOR_GYROSCOPE:
            if (gGyroscopeSensor != null) {
                sm.unregisterListener(getSensorListener(), gGyroscopeSensor);
            }
            break;
        default:
            Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype);
        }
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void moveTaskToBack() {
        if (getGeckoInterface() != null)
            getGeckoInterface().getActivity().moveTaskToBack(true);
    }

    @WrapForJNI(calledFrom = "gecko")
    public static void scheduleRestart() {
        getGeckoInterface().doRestart();
    }

    // Creates a homescreen shortcut for a web page.
    // This is the entry point from nsIShellService.
    @WrapForJNI(calledFrom = "gecko")
    public static void createShortcut(final String aTitle, final String aURI) {
        final GeckoInterface geckoInterface = getGeckoInterface();
        if (geckoInterface == null) {
            return;
        }
        geckoInterface.createShortcut(aTitle, aURI);
    }

    @JNITarget
    static public int getPreferredIconSize() {
        ActivityManager am = (ActivityManager)
            getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        return am.getLauncherLargeIconSize();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static String[] getHandlersForMimeType(String aMimeType, String aAction) {
        final GeckoInterface geckoInterface = getGeckoInterface();
        if (geckoInterface == null) {
            return new String[] {};
        }
        return geckoInterface.getHandlersForMimeType(aMimeType, aAction);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static String[] getHandlersForURL(String aURL, String aAction) {
        final GeckoInterface geckoInterface = getGeckoInterface();
        if (geckoInterface == null) {
            return new String[] {};
        }
        return geckoInterface.getHandlersForURL(aURL, aAction);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static boolean getHWEncoderCapability() {
      return HardwareCodecCapabilityUtils.getHWEncoderCapability();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static boolean getHWDecoderCapability() {
      return HardwareCodecCapabilityUtils.getHWDecoderCapability();
    }

    static List<ResolveInfo> queryIntentActivities(Intent intent) {
        final PackageManager pm = getApplicationContext().getPackageManager();

        // Exclude any non-exported activities: we can't open them even if we want to!
        // Bug 1031569 has some details.
        final ArrayList<ResolveInfo> list = new ArrayList<>();
        for (ResolveInfo ri: pm.queryIntentActivities(intent, 0)) {
            if (ri.activityInfo.exported) {
                list.add(ri);
            }
        }

        return list;
    }

    @WrapForJNI(calledFrom = "gecko")
    public static String getExtensionFromMimeType(String aMimeType) {
        return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType);
    }

    @WrapForJNI(calledFrom = "gecko")
    public static String getMimeTypeFromExtensions(String aFileExt) {
        StringTokenizer st = new StringTokenizer(aFileExt, ".,; ");
        String type = null;
        String subType = null;
        while (st.hasMoreElements()) {
            String ext = st.nextToken();
            String mt = getMimeTypeFromExtension(ext);
            if (mt == null)
                continue;
            int slash = mt.indexOf('/');
            String tmpType = mt.substring(0, slash);
            if (!tmpType.equalsIgnoreCase(type))
                type = type == null ? tmpType : "*";
            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;
    }

    static boolean isUriSafeForScheme(Uri aUri) {
        // Bug 794034 - We don't want to pass MWI or USSD codes to the
        // dialer, and ensure the Uri class doesn't parse a URI
        // containing a fragment ('#')
        final String scheme = aUri.getScheme();
        if ("tel".equals(scheme) || "sms".equals(scheme)) {
            final String number = aUri.getSchemeSpecificPart();
            if (number.contains("#") || number.contains("*") || aUri.getFragment() != null) {
                return false;
            }
        }
        return true;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static boolean openUriExternal(String targetURI,
                                           String mimeType,
                                           String packageName,
                                           String className,
                                           String action,
                                           String title) {
        final GeckoInterface geckoInterface = getGeckoInterface();
        if (geckoInterface == null) {
            return false;
        }
        return geckoInterface.openUriExternal(targetURI, mimeType, packageName, className, action, title);
    }

    @WrapForJNI(dispatchTo = "gecko")
    private static native void notifyAlertListener(String name, String topic, String cookie);

    /**
     * Called by the NotificationListener to notify Gecko that a notification has been
     * shown.
     */
    public static void onNotificationShow(final String name, final String cookie) {
        if (GeckoThread.isRunning()) {
            notifyAlertListener(name, "alertshow", 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);
        }
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void showNotification(String name, String cookie, String title,
                                         String text, String host, String imageUrl,
                                         String persistentData) {
        if (persistentData == null) {
            getNotificationListener().showNotification(name, cookie, title, text, host, imageUrl);
            return;
        }

        getNotificationListener().showPersistentNotification(
                name, cookie, title, text, host, imageUrl, persistentData);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void closeNotification(String name) {
        getNotificationListener().closeNotification(name);
    }

    @WrapForJNI(calledFrom = "gecko")
    public static int getDpi() {
        if (sDensityDpi == 0) {
            sDensityDpi = getApplicationContext().getResources().getDisplayMetrics().densityDpi;
        }

        return sDensityDpi;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static float getDensity() {
        return getApplicationContext().getResources().getDisplayMetrics().density;
    }

    private static boolean isHighMemoryDevice() {
        return HardwareUtils.getMemSize() > HIGH_MEMORY_DEVICE_THRESHOLD_MB;
    }

    /**
     * Returns the colour depth of the default screen. This will either be
     * 24 or 16.
     */
    @WrapForJNI(calledFrom = "gecko")
    public static synchronized int getScreenDepth() {
        if (sScreenDepth == 0) {
            sScreenDepth = 16;
            PixelFormat info = new PixelFormat();
            final WindowManager wm = (WindowManager)
                    getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
            PixelFormat.getPixelFormatInfo(wm.getDefaultDisplay().getPixelFormat(), info);
            if (info.bitsPerPixel >= 24 && isHighMemoryDevice()) {
                sScreenDepth = 24;
            }
        }

        return sScreenDepth;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static synchronized void setScreenDepthOverride(int aScreenDepth) {
        if (sScreenDepth != 0) {
            Log.e(LOGTAG, "Tried to override screen depth after it's already been set");
            return;
        }

        sScreenDepth = aScreenDepth;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void setFullScreen(boolean fullscreen) {
        if (getGeckoInterface() != null)
            getGeckoInterface().setFullScreen(fullscreen);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void performHapticFeedback(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) {
            LayerView layerView = getLayerView();
            layerView.performHapticFeedback(aIsLongPress ?
                                            HapticFeedbackConstants.LONG_PRESS :
                                            HapticFeedbackConstants.VIRTUAL_KEY);
        }
    }

    private static Vibrator vibrator() {
        return (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
    }

    // Helper method to convert integer array to long array.
    private static long[] convertIntToLongArray(int[] input) {
        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.
    public static void vibrateOnHapticFeedbackEnabled(int[] milliseconds) {
        if (Settings.System.getInt(getApplicationContext().getContentResolver(),
                                   Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) > 0) {
            vibrate(convertIntToLongArray(milliseconds), -1);
        }
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void vibrate(long milliseconds) {
        sVibrationEndTime = System.nanoTime() + milliseconds * 1000000;
        sVibrationMaybePlaying = true;
        vibrator().vibrate(milliseconds);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void vibrate(long[] pattern, int repeat) {
        // If pattern.length is even, the last element in the pattern is a
        // meaningless delay, so don't include it in vibrationDuration.
        long vibrationDuration = 0;
        int iterLen = pattern.length - (pattern.length % 2 == 0 ? 1 : 0);
        for (int i = 0; i < iterLen; i++) {
          vibrationDuration += pattern[i];
        }

        sVibrationEndTime = System.nanoTime() + vibrationDuration * 1000000;
        sVibrationMaybePlaying = true;
        vibrator().vibrate(pattern, repeat);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void cancelVibrate() {
        sVibrationMaybePlaying = false;
        sVibrationEndTime = 0;
        vibrator().cancel();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void setKeepScreenOn(final boolean on) {
        ThreadUtils.postToUiThread(new Runnable() {
            @Override
            public void run() {
                // TODO
            }
        });
    }

    @WrapForJNI(calledFrom = "gecko")
    private static boolean isNetworkLinkUp() {
        ConnectivityManager cm = (ConnectivityManager)
           getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        try {
            NetworkInfo info = cm.getActiveNetworkInfo();
            if (info == null || !info.isConnected())
                return false;
        } catch (SecurityException se) {
            return false;
        }
        return true;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static boolean isNetworkLinkKnown() {
        ConnectivityManager cm = (ConnectivityManager)
            getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        try {
            if (cm.getActiveNetworkInfo() == null)
                return false;
        } catch (SecurityException se) {
            return false;
        }
        return true;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static int getNetworkLinkType() {
        ConnectivityManager cm = (ConnectivityManager)
            getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = cm.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:
                break; // We will handle sub-types after the switch.
            default:
                Log.w(LOGTAG, "Ignoring the current network type.");
                return LINK_TYPE_UNKNOWN;
        }

        TelephonyManager tm = (TelephonyManager)
            getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE);
        if (tm == null) {
            Log.e(LOGTAG, "Telephony service does not exist");
            return LINK_TYPE_UNKNOWN;
        }

        switch (tm.getNetworkType()) {
            case TelephonyManager.NETWORK_TYPE_IDEN:
            case TelephonyManager.NETWORK_TYPE_CDMA:
            case TelephonyManager.NETWORK_TYPE_GPRS:
                return LINK_TYPE_2G;
            case TelephonyManager.NETWORK_TYPE_1xRTT:
            case TelephonyManager.NETWORK_TYPE_EDGE:
                return LINK_TYPE_2G; // 2.5G
            case TelephonyManager.NETWORK_TYPE_UMTS:
            case TelephonyManager.NETWORK_TYPE_EVDO_0:
                return LINK_TYPE_3G;
            case TelephonyManager.NETWORK_TYPE_HSPA:
            case TelephonyManager.NETWORK_TYPE_HSDPA:
            case TelephonyManager.NETWORK_TYPE_HSUPA:
            case TelephonyManager.NETWORK_TYPE_EVDO_A:
            case TelephonyManager.NETWORK_TYPE_EVDO_B:
            case TelephonyManager.NETWORK_TYPE_EHRPD:
                return LINK_TYPE_3G; // 3.5G
            case TelephonyManager.NETWORK_TYPE_HSPAP:
                return LINK_TYPE_3G; // 3.75G
            case TelephonyManager.NETWORK_TYPE_LTE:
                return LINK_TYPE_4G; // 3.9G
            case TelephonyManager.NETWORK_TYPE_UNKNOWN:
            default:
                Log.w(LOGTAG, "Connected to an unknown mobile network!");
                return LINK_TYPE_UNKNOWN;
        }
    }

    @WrapForJNI(calledFrom = "gecko")
    private static int[] getSystemColors() {
        // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
        final int[] attrsAppearance = {
            android.R.attr.textColor,
            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
        };

        int[] result = new int[attrsAppearance.length];

        final ContextThemeWrapper contextThemeWrapper =
            new ContextThemeWrapper(getApplicationContext(), android.R.style.TextAppearance);

        final TypedArray appearance = contextThemeWrapper.getTheme().obtainStyledAttributes(attrsAppearance);

        if (appearance != null) {
            for (int i = 0; i < appearance.getIndexCount(); i++) {
                int idx = appearance.getIndex(i);
                int color = appearance.getColor(idx, 0);
                result[idx] = color;
            }
            appearance.recycle();
        }

        return result;
    }

    @WrapForJNI(calledFrom = "gecko")
    public static void killAnyZombies() {
        GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() {
            @Override
            public boolean callback(int pid) {
                if (pid != android.os.Process.myPid())
                    android.os.Process.killProcess(pid);
                return true;
            }
        };

        EnumerateGeckoProcesses(visitor);
    }

    interface GeckoProcessesVisitor {
        boolean callback(int pid);
    }

    private static void EnumerateGeckoProcesses(GeckoProcessesVisitor visiter) {
        int pidColumn = -1;
        int userColumn = -1;

        try {
            // run ps and parse its output
            java.lang.Process ps = Runtime.getRuntime().exec("ps");
            BufferedReader in = new BufferedReader(new InputStreamReader(ps.getInputStream()),
                                                   2048);

            String headerOutput = in.readLine();

            // figure out the column offsets.  We only care about the pid and user fields
            StringTokenizer st = new StringTokenizer(headerOutput);

            int tokenSoFar = 0;
            while (st.hasMoreTokens()) {
                String next = st.nextToken();
                if (next.equalsIgnoreCase("PID"))
                    pidColumn = tokenSoFar;
                else if (next.equalsIgnoreCase("USER"))
                    userColumn = tokenSoFar;
                tokenSoFar++;
            }

            // alright, the rest are process entries.
            String psOutput = null;
            while ((psOutput = in.readLine()) != null) {
                String[] split = psOutput.split("\\s+");
                if (split.length <= pidColumn || split.length <= userColumn)
                    continue;
                int uid = android.os.Process.getUidForName(split[userColumn]);
                if (uid == android.os.Process.myUid() &&
                    !split[split.length - 1].equalsIgnoreCase("ps")) {
                    int pid = Integer.parseInt(split[pidColumn]);
                    boolean keepGoing = visiter.callback(pid);
                    if (keepGoing == false)
                        break;
                }
            }
            in.close();
        }
        catch (Exception e) {
            Log.w(LOGTAG, "Failed to enumerate Gecko processes.",  e);
        }
    }

    public static String getAppNameByPID(int pid) {
        BufferedReader cmdlineReader = null;
        String path = "/proc/" + pid + "/cmdline";
        try {
            File cmdlineFile = new File(path);
            if (!cmdlineFile.exists())
                return "";
            cmdlineReader = new BufferedReader(new FileReader(cmdlineFile));
            return cmdlineReader.readLine().trim();
        } catch (Exception ex) {
            return "";
        } finally {
            if (null != cmdlineReader) {
                try {
                    cmdlineReader.close();
                } catch (Exception e) { }
            }
        }
    }

    public static void listOfOpenFiles() {
        int pidColumn = -1;
        int nameColumn = -1;

        try {
            String filter = GeckoProfile.get(getApplicationContext()).getDir().toString();
            Log.d(LOGTAG, "[OPENFILE] Filter: " + filter);

            // run lsof and parse its output
            java.lang.Process lsof = Runtime.getRuntime().exec("lsof");
            BufferedReader in = new BufferedReader(new InputStreamReader(lsof.getInputStream()), 2048);

            String headerOutput = in.readLine();
            StringTokenizer st = new StringTokenizer(headerOutput);
            int token = 0;
            while (st.hasMoreTokens()) {
                String next = st.nextToken();
                if (next.equalsIgnoreCase("PID"))
                    pidColumn = token;
                else if (next.equalsIgnoreCase("NAME"))
                    nameColumn = token;
                token++;
            }

            // alright, the rest are open file entries.
            Map<Integer, String> pidNameMap = new TreeMap<Integer, String>();
            String output = null;
            while ((output = in.readLine()) != null) {
                String[] split = output.split("\\s+");
                if (split.length <= pidColumn || split.length <= nameColumn)
                    continue;
                final Integer pid = Integer.valueOf(split[pidColumn]);
                String name = pidNameMap.get(pid);
                if (name == null) {
                    name = getAppNameByPID(pid.intValue());
                    pidNameMap.put(pid, name);
                }
                String file = split[nameColumn];
                if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter))
                    Log.d(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file);
            }
            in.close();
        } catch (Exception e) { }
    }

    @WrapForJNI(calledFrom = "gecko")
    private static byte[] getIconForExtension(String aExt, int iconSize) {
        try {
            if (iconSize <= 0)
                iconSize = 16;

            if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.')
                aExt = aExt.substring(1);

            PackageManager pm = getApplicationContext().getPackageManager();
            Drawable icon = getDrawableForExtension(pm, aExt);
            if (icon == null) {
                // Use a generic icon
                icon = pm.getDefaultActivityIcon();
            }

            Bitmap bitmap = ((BitmapDrawable)icon).getBitmap();
            if (bitmap.getWidth() != iconSize || bitmap.getHeight() != iconSize)
                bitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);

            ByteBuffer buf = ByteBuffer.allocate(iconSize * iconSize * 4);
            bitmap.copyPixelsToBuffer(buf);

            return buf.array();
        }
        catch (Exception e) {
            Log.w(LOGTAG, "getIconForExtension failed.",  e);
            return null;
        }
    }

    public static String getMimeTypeFromExtension(String ext) {
        final MimeTypeMap mtm = MimeTypeMap.getSingleton();
        return mtm.getMimeTypeFromExtension(ext);
    }

    private static Drawable getDrawableForExtension(PackageManager pm, String aExt) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        final String mimeType = getMimeTypeFromExtension(aExt);
        if (mimeType != null && mimeType.length() > 0)
            intent.setType(mimeType);
        else
            return null;

        List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
        if (list.size() == 0)
            return null;

        ResolveInfo resolveInfo = list.get(0);

        if (resolveInfo == null)
            return null;

        ActivityInfo activityInfo = resolveInfo.activityInfo;

        return activityInfo.loadIcon(pm);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static boolean getShowPasswordSetting() {
        try {
            int showPassword =
                Settings.System.getInt(getApplicationContext().getContentResolver(),
                                       Settings.System.TEXT_SHOW_PASSWORD, 1);
            return (showPassword > 0);
        }
        catch (Exception e) {
            return true;
        }
    }

    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
    public static native void onFullScreenPluginHidden(View view);

    @WrapForJNI(calledFrom = "gecko")
    private static void addFullScreenPluginView(View view) {
        if (getGeckoInterface() != null)
             getGeckoInterface().addPluginView(view);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void removeFullScreenPluginView(View view) {
        if (getGeckoInterface() != null)
            getGeckoInterface().removePluginView(view);
    }

    /**
     * A plugin that wish to be loaded in the WebView must provide this permission
     * in their AndroidManifest.xml.
     */
    public static final String PLUGIN_ACTION = "android.webkit.PLUGIN";
    public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN";

    private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/";

    private static final String PLUGIN_TYPE = "type";
    private static final String TYPE_NATIVE = "native";
    public static final ArrayList<PackageInfo> mPackageInfoCache = new ArrayList<>();

    // Returns null if plugins are blocked on the device.
    static String[] getPluginDirectories() {

        // Block on Pixel C.
        if ((new File("/system/lib/hw/power.dragon.so")).exists()) {
            Log.w(LOGTAG, "Blocking plugins because of Pixel C device (bug 1255122)");
            return null;
        }
        // An awful hack to detect Tegra devices. Easiest way to do it without spinning up a EGL context.
        boolean isTegra = (new File("/system/lib/hw/gralloc.tegra.so")).exists() ||
                          (new File("/system/lib/hw/gralloc.tegra3.so")).exists() ||
                          (new File("/sys/class/nvidia-gpu")).exists();
        if (isTegra) {
            // disable on KitKat (bug 957694)
            if (Versions.feature19Plus) {
                Log.w(LOGTAG, "Blocking plugins because of Tegra (bug 957694)");
                return null;
            }

            // disable Flash on Tegra ICS with CM9 and other custom firmware (bug 736421)
            final File vfile = new File("/proc/version");
            try {
                if (vfile.canRead()) {
                    final BufferedReader reader = new BufferedReader(new FileReader(vfile));
                    try {
                        final String version = reader.readLine();
                        if (version.indexOf("CM9") != -1 ||
                            version.indexOf("cyanogen") != -1 ||
                            version.indexOf("Nova") != -1) {
                            Log.w(LOGTAG, "Blocking plugins because of Tegra 2 + unofficial ICS bug (bug 736421)");
                            return null;
                        }
                    } finally {
                      reader.close();
                    }
                }
            } catch (IOException ex) {
                // Do nothing.
            }
        }

        ArrayList<String> directories = new ArrayList<String>();
        PackageManager pm = getApplicationContext().getPackageManager();
        List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
                PackageManager.GET_META_DATA);

        synchronized (mPackageInfoCache) {

            // clear the list of existing packageInfo objects
            mPackageInfoCache.clear();


            for (ResolveInfo info : plugins) {

                // retrieve the plugin's service information
                ServiceInfo serviceInfo = info.serviceInfo;
                if (serviceInfo == null) {
                    Log.w(LOGTAG, "Ignoring bad plugin.");
                    continue;
                }

                // Blacklist HTC's flash lite.
                // See bug #704516 - We're not quite sure what Flash Lite does,
                // but loading it causes Flash to give errors and fail to draw.
                if (serviceInfo.packageName.equals("com.htc.flashliteplugin")) {
                    Log.w(LOGTAG, "Skipping HTC's flash lite plugin");
                    continue;
                }


                // Retrieve information from the plugin's manifest.
                PackageInfo pkgInfo;
                try {
                    pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
                                    PackageManager.GET_PERMISSIONS
                                    | PackageManager.GET_SIGNATURES);
                } catch (Exception e) {
                    Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
                    continue;
                }

                if (pkgInfo == null) {
                    Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Could not load package information.");
                    continue;
                }

                /*
                 * find the location of the plugin's shared library. The default
                 * is to assume the app is either a user installed app or an
                 * updated system app. In both of these cases the library is
                 * stored in the app's data directory.
                 */
                String directory = pkgInfo.applicationInfo.dataDir + "/lib";
                final int appFlags = pkgInfo.applicationInfo.flags;
                final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM |
                                               ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;

                // preloaded system app with no user updates
                if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) {
                    directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
                }

                // check if the plugin has the required permissions
                String permissions[] = pkgInfo.requestedPermissions;
                if (permissions == null) {
                    Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission.");
                    continue;
                }
                boolean permissionOk = false;
                for (String permit : permissions) {
                    if (PLUGIN_PERMISSION.equals(permit)) {
                        permissionOk = true;
                        break;
                    }
                }
                if (!permissionOk) {
                    Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission (2).");
                    continue;
                }

                // check to ensure the plugin is properly signed
                Signature signatures[] = pkgInfo.signatures;
                if (signatures == null) {
                    Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Not signed.");
                    continue;
                }

                // determine the type of plugin from the manifest
                if (serviceInfo.metaData == null) {
                    Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no defined type.");
                    continue;
                }

                String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
                if (!TYPE_NATIVE.equals(pluginType)) {
                    Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
                    continue;
                }

                try {
                    Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);

                    //TODO implement any requirements of the plugin class here!
                    boolean classFound = true;

                    if (!classFound) {
                        Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
                        continue;
                    }

                } catch (NameNotFoundException e) {
                    Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
                    continue;
                } catch (ClassNotFoundException e) {
                    Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
                    continue;
                }

                // if all checks have passed then make the plugin available
                mPackageInfoCache.add(pkgInfo);
                directories.add(directory);
            }
        }

        return directories.toArray(new String[directories.size()]);
    }

    static String getPluginPackage(String pluginLib) {

        if (pluginLib == null || pluginLib.length() == 0) {
            return null;
        }

        synchronized (mPackageInfoCache) {
            for (PackageInfo pkgInfo : mPackageInfoCache) {
                if (pluginLib.contains(pkgInfo.packageName)) {
                    return pkgInfo.packageName;
                }
            }
        }

        return null;
    }

    static Class<?> getPluginClass(String packageName, String className)
            throws NameNotFoundException, ClassNotFoundException {
        Context pluginContext = getApplicationContext().createPackageContext(packageName,
                Context.CONTEXT_INCLUDE_CODE |
                Context.CONTEXT_IGNORE_SECURITY);
        ClassLoader pluginCL = pluginContext.getClassLoader();
        return pluginCL.loadClass(className);
    }

    @WrapForJNI
    private static Class<?> loadPluginClass(String className, String libName) {
        if (getGeckoInterface() == null)
            return null;
        try {
            final String packageName = getPluginPackage(libName);
            final int contextFlags = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY;
            final Context pluginContext = getApplicationContext().createPackageContext(
                    packageName, contextFlags);
            return pluginContext.getClassLoader().loadClass(className);
        } catch (java.lang.ClassNotFoundException cnfe) {
            Log.w(LOGTAG, "Couldn't find plugin class " + className, cnfe);
            return null;
        } catch (android.content.pm.PackageManager.NameNotFoundException nnfe) {
            Log.w(LOGTAG, "Couldn't find package.", nnfe);
            return null;
        }
    }

    private static Context sApplicationContext;
    private static ContextGetter sContextGetter;

    @Deprecated
    @WrapForJNI
    public static Context getContext() {
        return sContextGetter.getContext();
    }

    public static void setContextGetter(ContextGetter cg) {
        sContextGetter = cg;
    }

    @WrapForJNI
    public static Context getApplicationContext() {
        return sApplicationContext;
    }

    public static void setApplicationContext(final Context context) {
        sApplicationContext = context;
    }

    public static SharedPreferences getSharedPreferences() {
        if (sContextGetter == null) {
            throw new IllegalStateException("No ContextGetter; cannot fetch prefs.");
        }
        return sContextGetter.getSharedPreferences();
    }

    public interface AppStateListener {
        public void onPause();
        public void onResume();
        public void onOrientationChanged();
    }

    public interface GeckoInterface {
        public EventDispatcher getAppEventDispatcher();
        public GeckoProfile getProfile();
        public Activity getActivity();
        public String getDefaultUAString();
        public void doRestart();
        public void setFullScreen(boolean fullscreen);
        public void addPluginView(View view);
        public void removePluginView(final View view);
        public void enableOrientationListener();
        public void disableOrientationListener();
        public void addAppStateListener(AppStateListener listener);
        public void removeAppStateListener(AppStateListener listener);
        public void notifyWakeLockChanged(String topic, String state);
        public boolean areTabsShown();
        public AbsoluteLayout getPluginContainer();
        public void notifyCheckUpdateResult(String result);
        public void invalidateOptionsMenu();

        /**
         * Create a shortcut -- generally a home-screen icon -- linking the given title to the given URI.
         * <p>
         * This method is always invoked on the Gecko thread.
         *
         * @param title of URI to link to.
         * @param URI to link to.
         */
        public void createShortcut(String title, String URI);

        /**
         * Check if the given URI is visited.
         * <p/>
         * If it has been visited, call {@link GeckoAppShell#notifyUriVisited(String)}.  (If it
         * has not been visited, do nothing.)
         * <p/>
         * This method is always invoked on the Gecko thread.
         *
         * @param uri to check.
         */
        public void checkUriVisited(String uri);

        /**
         * Mark the given URI as visited in Gecko.
         * <p/>
         * Implementors may maintain some local store of visited URIs in order to be able to
         * answer {@link #checkUriVisited(String)} requests affirmatively.
         * <p/>
         * This method is always invoked on the Gecko thread.
         *
         * @param uri to mark.
         */
        public void markUriVisited(final String uri);

        /**
         * Set the title of the given URI, as determined by Gecko.
         * <p/>
         * This method is always invoked on the Gecko thread.
         *
         * @param uri given.
         * @param title to associate with the given URI.
         */
        public void setUriTitle(final String uri, final String title);

        public void setAccessibilityEnabled(boolean enabled);

        public boolean openUriExternal(String targetURI, String mimeType, String packageName, String className, String action, String title);

        public String[] getHandlersForMimeType(String mimeType, String action);
        public String[] getHandlersForURL(String url, String action);

        /**
         * URI of the underlying chrome window to be opened, or null to use the default GeckoView
         * XUL container <tt>chrome://browser/content/geckoview.xul</tt>.  See
         * <a href="https://developer.mozilla.org/en/docs/toolkit.defaultChromeURI">https://developer.mozilla.org/en/docs/toolkit.defaultChromeURI</a>
         *
         * @return URI or null.
         */
        String getDefaultChromeURI();
    };

    private static GeckoInterface sGeckoInterface;

    public static GeckoInterface getGeckoInterface() {
        return sGeckoInterface;
    }

    public static void setGeckoInterface(GeckoInterface aGeckoInterface) {
        sGeckoInterface = aGeckoInterface;
    }

    /* package */ static Camera sCamera;

    private static final int kPreferredFPS = 25;
    private static byte[] sCameraBuffer;

    private static class CameraCallback implements Camera.PreviewCallback {
        @WrapForJNI(calledFrom = "gecko")
        private static native void onFrameData(int camera, byte[] data);

        private final int mCamera;

        public CameraCallback(int camera) {
            mCamera = camera;
        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            onFrameData(mCamera, data);

            if (sCamera != null) {
                sCamera.addCallbackBuffer(sCameraBuffer);
            }
        }
    }

    @WrapForJNI(calledFrom = "gecko")
    private static int[] initCamera(String aContentType, int aCamera, int aWidth, int aHeight) {
        ThreadUtils.postToUiThread(new Runnable() {
                @Override
                public void run() {
                    try {
                        if (getGeckoInterface() != null)
                            getGeckoInterface().enableOrientationListener();
                    } catch (Exception e) { }
                }
            });

        // [0] = 0|1 (failure/success)
        // [1] = width
        // [2] = height
        // [3] = fps
        int[] result = new int[4];
        result[0] = 0;

        if (Camera.getNumberOfCameras() == 0) {
            return result;
        }

        try {
            sCamera = Camera.open(aCamera);

            Camera.Parameters params = sCamera.getParameters();
            params.setPreviewFormat(ImageFormat.NV21);

            // use the preview fps closest to 25 fps.
            int fpsDelta = 1000;
            try {
                Iterator<Integer> it = params.getSupportedPreviewFrameRates().iterator();
                while (it.hasNext()) {
                    int nFps = it.next();
                    if (Math.abs(nFps - kPreferredFPS) < fpsDelta) {
                        fpsDelta = Math.abs(nFps - kPreferredFPS);
                        params.setPreviewFrameRate(nFps);
                    }
                }
            } catch (Exception e) {
                params.setPreviewFrameRate(kPreferredFPS);
            }

            // set up the closest preview size available
            Iterator<Camera.Size> sit = params.getSupportedPreviewSizes().iterator();
            int sizeDelta = 10000000;
            int bufferSize = 0;
            while (sit.hasNext()) {
                Camera.Size size = sit.next();
                if (Math.abs(size.width * size.height - aWidth * aHeight) < sizeDelta) {
                    sizeDelta = Math.abs(size.width * size.height - aWidth * aHeight);
                    params.setPreviewSize(size.width, size.height);
                    bufferSize = size.width * size.height;
                }
            }

            sCamera.setParameters(params);
            sCameraBuffer = new byte[(bufferSize * 12) / 8];
            sCamera.addCallbackBuffer(sCameraBuffer);
            sCamera.setPreviewCallbackWithBuffer(new CameraCallback(aCamera));
            sCamera.startPreview();
            params = sCamera.getParameters();
            result[0] = 1;
            result[1] = params.getPreviewSize().width;
            result[2] = params.getPreviewSize().height;
            result[3] = params.getPreviewFrameRate();
        } catch (RuntimeException e) {
            Log.w(LOGTAG, "initCamera RuntimeException.", e);
            result[0] = result[1] = result[2] = result[3] = 0;
        }
        return result;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static synchronized void closeCamera() {
        ThreadUtils.postToUiThread(new Runnable() {
                @Override
                public void run() {
                    try {
                        if (getGeckoInterface() != null)
                            getGeckoInterface().disableOrientationListener();
                    } catch (Exception e) { }
                }
            });
        if (sCamera != null) {
            sCamera.stopPreview();
            sCamera.release();
            sCamera = null;
            sCameraBuffer = null;
        }
    }

    /*
     * Battery API related methods.
     */
    @WrapForJNI(calledFrom = "gecko")
    private static void enableBatteryNotifications() {
        GeckoBatteryManager.enableNotifications();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void handleGeckoMessage(final NativeJSContainer message) {
        boolean success = EventDispatcher.getInstance().dispatchEvent(message);
        if (getGeckoInterface() != null && getGeckoInterface().getAppEventDispatcher() != null) {
            success |= getGeckoInterface().getAppEventDispatcher().dispatchEvent(message);
        }

        if (!success) {
            final String type = message.optString("type", null);
            final String guid = message.optString(EventDispatcher.GUID, null);
            if (type != null && guid != null) {
                (new EventDispatcher.GeckoEventCallback(guid, type)).sendError("No listeners for request");
            }
        }
        message.disposeNative();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void disableBatteryNotifications() {
        GeckoBatteryManager.disableNotifications();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static double[] getCurrentBatteryInformation() {
        return GeckoBatteryManager.getCurrentInformation();
    }

    @WrapForJNI(stubName = "CheckURIVisited", calledFrom = "gecko")
    private static void checkUriVisited(String uri) {
        final GeckoInterface geckoInterface = getGeckoInterface();
        if (geckoInterface == null) {
            return;
        }
        geckoInterface.checkUriVisited(uri);
    }

    @WrapForJNI(stubName = "MarkURIVisited", calledFrom = "gecko")
    private static void markUriVisited(final String uri) {
        final GeckoInterface geckoInterface = getGeckoInterface();
        if (geckoInterface == null) {
            return;
        }
        geckoInterface.markUriVisited(uri);
    }

    @WrapForJNI(stubName = "SetURITitle", calledFrom = "gecko")
    private static void setUriTitle(final String uri, final String title) {
        final GeckoInterface geckoInterface = getGeckoInterface();
        if (geckoInterface == null) {
            return;
        }
        geckoInterface.setUriTitle(uri, title);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void hideProgressDialog() {
        // unused stub
    }

    /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */
    @WrapForJNI(calledFrom = "gecko")
    @RobocopTarget
    public static boolean isTablet() {
        return HardwareUtils.isTablet();
    }

    private static boolean sImeWasEnabledOnLastResize = false;
    public static void viewSizeChanged() {
        GeckoView v = (GeckoView) getLayerView();
        if (v == null) {
            return;
        }
        boolean imeIsEnabled = v.isIMEEnabled();
        if (imeIsEnabled && !sImeWasEnabledOnLastResize) {
            // The IME just came up after not being up, so let's scroll
            // to the focused input.
            notifyObservers("ScrollTo:FocusedInput", "");
        }
        sImeWasEnabledOnLastResize = imeIsEnabled;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static double[] getCurrentNetworkInformation() {
        return GeckoNetworkManager.getInstance().getCurrentInformation();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void enableNetworkNotifications() {
        ThreadUtils.postToUiThread(new Runnable() {
            @Override
            public void run() {
                GeckoNetworkManager.getInstance().enableNotifications();
            }
        });
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void disableNetworkNotifications() {
        ThreadUtils.postToUiThread(new Runnable() {
            @Override
            public void run() {
                GeckoNetworkManager.getInstance().disableNotifications();
            }
        });
    }

    @WrapForJNI(calledFrom = "gecko")
    private static short getScreenOrientation() {
        return GeckoScreenOrientation.getInstance().getScreenOrientation().value;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static int getScreenAngle() {
        return GeckoScreenOrientation.getInstance().getAngle();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void enableScreenOrientationNotifications() {
        GeckoScreenOrientation.getInstance().enableNotifications();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void disableScreenOrientationNotifications() {
        GeckoScreenOrientation.getInstance().disableNotifications();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void lockScreenOrientation(int aOrientation) {
        GeckoScreenOrientation.getInstance().lock(aOrientation);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void unlockScreenOrientation() {
        GeckoScreenOrientation.getInstance().unlock();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static void notifyWakeLockChanged(String topic, String state) {
        if (getGeckoInterface() != null)
            getGeckoInterface().notifyWakeLockChanged(topic, state);
    }

    @WrapForJNI(calledFrom = "gecko")
    private static boolean unlockProfile() {
        // Try to kill any zombie Fennec's that might be running
        GeckoAppShell.killAnyZombies();

        // Then force unlock this profile
        if (getGeckoInterface() != null) {
            GeckoProfile profile = getGeckoInterface().getProfile();
            File lock = profile.getFile(".parentlock");
            return lock.exists() && lock.delete();
        }
        return false;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static String getProxyForURI(String spec, String scheme, String host, int port) {
        final ProxySelector ps = new ProxySelector();

        Proxy proxy = ps.select(scheme, host);
        if (Proxy.NO_PROXY.equals(proxy)) {
            return "DIRECT";
        }

        switch (proxy.type()) {
            case HTTP:
                return "PROXY " + proxy.address().toString();
            case SOCKS:
                return "SOCKS " + proxy.address().toString();
        }

        return "DIRECT";
    }

    @WrapForJNI
    private static InputStream createInputStream(URLConnection connection) throws IOException {
        return connection.getInputStream();
    }

    private static class BitmapConnection extends URLConnection {
        private Bitmap bitmap;

        BitmapConnection(Bitmap b) throws MalformedURLException, IOException {
            super(null);
            bitmap = b;
        }

        @Override
        public void connect() {}

        @Override
        public InputStream getInputStream() throws IOException {
            return new BitmapInputStream();
        }

        @Override
        public String getContentType() {
            return "image/png";
        }

        private final class BitmapInputStream extends PipedInputStream {
            private boolean mHaveConnected = false;

            @Override
            public synchronized int read(byte[] buffer, int byteOffset, int byteCount)
                                    throws IOException {
                if (mHaveConnected) {
                    return super.read(buffer, byteOffset, byteCount);
                }

                final PipedOutputStream output = new PipedOutputStream();
                connect(output);
                ThreadUtils.postToBackgroundThread(
                    new Runnable() {
                        @Override
                        public void run() {
                            try {
                                bitmap.compress(Bitmap.CompressFormat.PNG, 100, output);
                                output.close();
                            } catch (IOException ioe) { }
                        }
                    });
                mHaveConnected = true;
                return super.read(buffer, byteOffset, byteCount);
            }
        }
    }

    @WrapForJNI
    private static URLConnection getConnection(String url) {
        try {
            String spec;
            if (url.startsWith("android://")) {
                spec = url.substring(10);
            } else {
                spec = url.substring(8);
            }

            // Check if we are loading a package icon.
            try {
                if (spec.startsWith("icon/")) {
                    String[] splits = spec.split("/");
                    if (splits.length != 2) {
                        return null;
                    }
                    final String pkg = splits[1];
                    final PackageManager pm = getApplicationContext().getPackageManager();
                    final Drawable d = pm.getApplicationIcon(pkg);
                    final Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(d);
                    return new BitmapConnection(bitmap);
                }
            } catch (Exception ex) {
                Log.e(LOGTAG, "error", ex);
            }

            // if the colon got stripped, put it back
            int colon = spec.indexOf(':');
            if (colon == -1 || colon > spec.indexOf('/')) {
                spec = spec.replaceFirst("/", ":/");
            }
        } catch (Exception ex) {
            return null;
        }
        return null;
    }

    @WrapForJNI
    private static String connectionGetMimeType(URLConnection connection) {
        return connection.getContentType();
    }

    @WrapForJNI(calledFrom = "gecko")
    private static int getMaxTouchPoints() {
        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;
    }

    public static synchronized void resetScreenSize() {
        sScreenSize = null;
    }

    @WrapForJNI(calledFrom = "gecko")
    private static synchronized Rect getScreenSize() {
        if (sScreenSize == null) {
            final WindowManager wm = (WindowManager)
                    getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
            final Display disp = wm.getDefaultDisplay();
            sScreenSize = new Rect(0, 0, disp.getWidth(), disp.getHeight());
        }
        return sScreenSize;
    }
}
