mobile/android/base/GeckoAppShell.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/GeckoAppShell.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2704 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko;
    1.10 +
    1.11 +import java.io.BufferedReader;
    1.12 +import java.io.Closeable;
    1.13 +import java.io.File;
    1.14 +import java.io.FileReader;
    1.15 +import java.io.FileOutputStream;
    1.16 +import java.io.IOException;
    1.17 +import java.io.InputStream;
    1.18 +import java.io.InputStreamReader;
    1.19 +import java.io.OutputStream;
    1.20 +import java.io.PrintWriter;
    1.21 +import java.io.StringWriter;
    1.22 +import java.net.Proxy;
    1.23 +import java.net.URL;
    1.24 +import java.nio.ByteBuffer;
    1.25 +import java.util.ArrayList;
    1.26 +import java.util.Iterator;
    1.27 +import java.util.List;
    1.28 +import java.util.Locale;
    1.29 +import java.util.Map;
    1.30 +import java.util.NoSuchElementException;
    1.31 +import java.util.Queue;
    1.32 +import java.util.StringTokenizer;
    1.33 +import java.util.TreeMap;
    1.34 +import java.util.concurrent.ConcurrentHashMap;
    1.35 +import java.util.concurrent.ConcurrentLinkedQueue;
    1.36 +
    1.37 +import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
    1.38 +import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
    1.39 +import org.mozilla.gecko.gfx.BitmapUtils;
    1.40 +import org.mozilla.gecko.gfx.LayerView;
    1.41 +import org.mozilla.gecko.gfx.PanZoomController;
    1.42 +import org.mozilla.gecko.mozglue.GeckoLoader;
    1.43 +import org.mozilla.gecko.mozglue.JNITarget;
    1.44 +import org.mozilla.gecko.mozglue.RobocopTarget;
    1.45 +import org.mozilla.gecko.mozglue.generatorannotations.OptionalGeneratedParameter;
    1.46 +import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
    1.47 +import org.mozilla.gecko.prompts.PromptService;
    1.48 +import org.mozilla.gecko.util.GeckoEventListener;
    1.49 +import org.mozilla.gecko.util.HardwareUtils;
    1.50 +import org.mozilla.gecko.util.NativeJSContainer;
    1.51 +import org.mozilla.gecko.util.ProxySelector;
    1.52 +import org.mozilla.gecko.util.ThreadUtils;
    1.53 +import org.mozilla.gecko.webapp.Allocator;
    1.54 +
    1.55 +import android.app.Activity;
    1.56 +import android.app.ActivityManager;
    1.57 +import android.app.PendingIntent;
    1.58 +import android.content.ActivityNotFoundException;
    1.59 +import android.content.Context;
    1.60 +import android.content.Intent;
    1.61 +import android.content.SharedPreferences;
    1.62 +import android.content.pm.ActivityInfo;
    1.63 +import android.content.pm.ApplicationInfo;
    1.64 +import android.content.pm.PackageInfo;
    1.65 +import android.content.pm.PackageManager;
    1.66 +import android.content.pm.PackageManager.NameNotFoundException;
    1.67 +import android.content.pm.ResolveInfo;
    1.68 +import android.content.pm.ServiceInfo;
    1.69 +import android.content.pm.Signature;
    1.70 +import android.content.res.TypedArray;
    1.71 +import android.graphics.Bitmap;
    1.72 +import android.graphics.Canvas;
    1.73 +import android.graphics.Color;
    1.74 +import android.graphics.ImageFormat;
    1.75 +import android.graphics.Paint;
    1.76 +import android.graphics.PixelFormat;
    1.77 +import android.graphics.Rect;
    1.78 +import android.graphics.RectF;
    1.79 +import android.graphics.SurfaceTexture;
    1.80 +import android.graphics.drawable.BitmapDrawable;
    1.81 +import android.graphics.drawable.Drawable;
    1.82 +import android.hardware.Sensor;
    1.83 +import android.hardware.SensorEventListener;
    1.84 +import android.hardware.SensorManager;
    1.85 +import android.location.Criteria;
    1.86 +import android.location.Location;
    1.87 +import android.location.LocationListener;
    1.88 +import android.location.LocationManager;
    1.89 +import android.media.MediaScannerConnection;
    1.90 +import android.media.MediaScannerConnection.MediaScannerConnectionClient;
    1.91 +import android.net.ConnectivityManager;
    1.92 +import android.net.NetworkInfo;
    1.93 +import android.net.Uri;
    1.94 +import android.os.Build;
    1.95 +import android.os.Handler;
    1.96 +import android.os.Looper;
    1.97 +import android.os.Message;
    1.98 +import android.os.MessageQueue;
    1.99 +import android.os.SystemClock;
   1.100 +import android.os.Vibrator;
   1.101 +import android.provider.Settings;
   1.102 +import android.telephony.TelephonyManager;
   1.103 +import android.text.TextUtils;
   1.104 +import android.util.Base64;
   1.105 +import android.util.DisplayMetrics;
   1.106 +import android.util.Log;
   1.107 +import android.view.ContextThemeWrapper;
   1.108 +import android.view.HapticFeedbackConstants;
   1.109 +import android.view.Surface;
   1.110 +import android.view.SurfaceView;
   1.111 +import android.view.TextureView;
   1.112 +import android.view.View;
   1.113 +import android.view.inputmethod.InputMethodManager;
   1.114 +import android.webkit.MimeTypeMap;
   1.115 +import android.webkit.URLUtil;
   1.116 +import android.widget.AbsoluteLayout;
   1.117 +import android.widget.Toast;
   1.118 +
   1.119 +public class GeckoAppShell
   1.120 +{
   1.121 +    private static final String LOGTAG = "GeckoAppShell";
   1.122 +    private static final boolean LOGGING = false;
   1.123 +
   1.124 +    // We have static members only.
   1.125 +    private GeckoAppShell() { }
   1.126 +
   1.127 +    private static boolean restartScheduled = false;
   1.128 +    private static GeckoEditableListener editableListener = null;
   1.129 +
   1.130 +    private static final Queue<GeckoEvent> PENDING_EVENTS = new ConcurrentLinkedQueue<GeckoEvent>();
   1.131 +    private static final Map<String, String> ALERT_COOKIES = new ConcurrentHashMap<String, String>();
   1.132 +
   1.133 +    private static volatile boolean locationHighAccuracyEnabled;
   1.134 +
   1.135 +    // Accessed by NotificationHelper. This should be encapsulated.
   1.136 +    /* package */ static NotificationClient notificationClient;
   1.137 +
   1.138 +    // See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB.
   1.139 +    private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
   1.140 +
   1.141 +    public static final String SHORTCUT_TYPE_WEBAPP = "webapp";
   1.142 +    public static final String SHORTCUT_TYPE_BOOKMARK = "bookmark";
   1.143 +
   1.144 +    static private int sDensityDpi = 0;
   1.145 +    static private int sScreenDepth = 0;
   1.146 +
   1.147 +    private static final EventDispatcher sEventDispatcher = new EventDispatcher();
   1.148 +
   1.149 +    /* Default colors. */
   1.150 +    private static final float[] DEFAULT_LAUNCHER_ICON_HSV = { 32.0f, 1.0f, 1.0f };
   1.151 +
   1.152 +    /* Is the value in sVibrationEndTime valid? */
   1.153 +    private static boolean sVibrationMaybePlaying = false;
   1.154 +
   1.155 +    /* Time (in System.nanoTime() units) when the currently-playing vibration
   1.156 +     * is scheduled to end.  This value is valid only when
   1.157 +     * sVibrationMaybePlaying is true. */
   1.158 +    private static long sVibrationEndTime = 0;
   1.159 +
   1.160 +    /* Default value of how fast we should hint the Android sensors. */
   1.161 +    private static int sDefaultSensorHint = 100;
   1.162 +
   1.163 +    private static Sensor gAccelerometerSensor = null;
   1.164 +    private static Sensor gLinearAccelerometerSensor = null;
   1.165 +    private static Sensor gGyroscopeSensor = null;
   1.166 +    private static Sensor gOrientationSensor = null;
   1.167 +    private static Sensor gProximitySensor = null;
   1.168 +    private static Sensor gLightSensor = null;
   1.169 +
   1.170 +    /*
   1.171 +     * Keep in sync with constants found here:
   1.172 +     * http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
   1.173 +    */
   1.174 +    static public final int WPL_STATE_START = 0x00000001;
   1.175 +    static public final int WPL_STATE_STOP = 0x00000010;
   1.176 +    static public final int WPL_STATE_IS_DOCUMENT = 0x00020000;
   1.177 +    static public final int WPL_STATE_IS_NETWORK = 0x00040000;
   1.178 +
   1.179 +    /* Keep in sync with constants found here:
   1.180 +      http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsINetworkLinkService.idl
   1.181 +    */
   1.182 +    static public final int LINK_TYPE_UNKNOWN = 0;
   1.183 +    static public final int LINK_TYPE_ETHERNET = 1;
   1.184 +    static public final int LINK_TYPE_USB = 2;
   1.185 +    static public final int LINK_TYPE_WIFI = 3;
   1.186 +    static public final int LINK_TYPE_WIMAX = 4;
   1.187 +    static public final int LINK_TYPE_2G = 5;
   1.188 +    static public final int LINK_TYPE_3G = 6;
   1.189 +    static public final int LINK_TYPE_4G = 7;
   1.190 +
   1.191 +    /* The Android-side API: API methods that Android calls */
   1.192 +
   1.193 +    // Initialization methods
   1.194 +    public static native void nativeInit();
   1.195 +
   1.196 +    // helper methods
   1.197 +    //    public static native void setSurfaceView(GeckoSurfaceView sv);
   1.198 +    public static native void setLayerClient(Object client);
   1.199 +    public static native void onResume();
   1.200 +    public static void callObserver(String observerKey, String topic, String data) {
   1.201 +        sendEventToGecko(GeckoEvent.createCallObserverEvent(observerKey, topic, data));
   1.202 +    }
   1.203 +    public static void removeObserver(String observerKey) {
   1.204 +        sendEventToGecko(GeckoEvent.createRemoveObserverEvent(observerKey));
   1.205 +    }
   1.206 +    public static native Message getNextMessageFromQueue(MessageQueue queue);
   1.207 +    public static native void onSurfaceTextureFrameAvailable(Object surfaceTexture, int id);
   1.208 +    public static native void dispatchMemoryPressure();
   1.209 +
   1.210 +    public static void registerGlobalExceptionHandler() {
   1.211 +        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
   1.212 +            @Override
   1.213 +            public void uncaughtException(Thread thread, Throwable e) {
   1.214 +                handleUncaughtException(thread, e);
   1.215 +            }
   1.216 +        });
   1.217 +    }
   1.218 +
   1.219 +    private static String getStackTraceString(Throwable e) {
   1.220 +        StringWriter sw = new StringWriter();
   1.221 +        PrintWriter pw = new PrintWriter(sw);
   1.222 +        e.printStackTrace(pw);
   1.223 +        pw.flush();
   1.224 +        return sw.toString();
   1.225 +    }
   1.226 +
   1.227 +    private static native void reportJavaCrash(String stackTrace);
   1.228 +
   1.229 +    public static void notifyUriVisited(String uri) {
   1.230 +        sendEventToGecko(GeckoEvent.createVisitedEvent(uri));
   1.231 +    }
   1.232 +
   1.233 +    public static native void processNextNativeEvent(boolean mayWait);
   1.234 +
   1.235 +    public static native void notifyBatteryChange(double aLevel, boolean aCharging, double aRemainingTime);
   1.236 +
   1.237 +    public static native void scheduleComposite();
   1.238 +
   1.239 +    // Resuming the compositor is a synchronous request, so be
   1.240 +    // careful of possible deadlock. Resuming the compositor will also cause
   1.241 +    // a composition, so there is no need to schedule a composition after
   1.242 +    // resuming.
   1.243 +    public static native void scheduleResumeComposition(int width, int height);
   1.244 +
   1.245 +    public static native float computeRenderIntegrity();
   1.246 +
   1.247 +    public static native SurfaceBits getSurfaceBits(Surface surface);
   1.248 +
   1.249 +    public static native void onFullScreenPluginHidden(View view);
   1.250 +
   1.251 +    public static class CreateShortcutFaviconLoadedListener implements OnFaviconLoadedListener {
   1.252 +        private final String title;
   1.253 +        private final String url;
   1.254 +
   1.255 +        public CreateShortcutFaviconLoadedListener(final String url, final String title) {
   1.256 +            this.url = url;
   1.257 +            this.title = title;
   1.258 +        }
   1.259 +
   1.260 +        @Override
   1.261 +        public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) {
   1.262 +            GeckoAppShell.createShortcut(title, url, url, favicon, "");
   1.263 +        }
   1.264 +    }
   1.265 +
   1.266 +    private static final class GeckoMediaScannerClient implements MediaScannerConnectionClient {
   1.267 +        private final String mFile;
   1.268 +        private final String mMimeType;
   1.269 +        private MediaScannerConnection mScanner;
   1.270 +
   1.271 +        public static void startScan(Context context, String file, String mimeType) {
   1.272 +            new GeckoMediaScannerClient(context, file, mimeType);
   1.273 +        }
   1.274 +
   1.275 +        private GeckoMediaScannerClient(Context context, String file, String mimeType) {
   1.276 +            mFile = file;
   1.277 +            mMimeType = mimeType;
   1.278 +            mScanner = new MediaScannerConnection(context, this);
   1.279 +            mScanner.connect();
   1.280 +        }
   1.281 +
   1.282 +        @Override
   1.283 +        public void onMediaScannerConnected() {
   1.284 +            mScanner.scanFile(mFile, mMimeType);
   1.285 +        }
   1.286 +
   1.287 +        @Override
   1.288 +        public void onScanCompleted(String path, Uri uri) {
   1.289 +            if(path.equals(mFile)) {
   1.290 +                mScanner.disconnect();
   1.291 +                mScanner = null;
   1.292 +            }
   1.293 +        }
   1.294 +    }
   1.295 +
   1.296 +    private static LayerView sLayerView;
   1.297 +
   1.298 +    public static void setLayerView(LayerView lv) {
   1.299 +        sLayerView = lv;
   1.300 +    }
   1.301 +
   1.302 +    @RobocopTarget
   1.303 +    public static LayerView getLayerView() {
   1.304 +        return sLayerView;
   1.305 +    }
   1.306 +
   1.307 +    public static void runGecko(String apkPath, String args, String url, String type) {
   1.308 +        // Preparation for pumpMessageLoop()
   1.309 +        MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
   1.310 +            @Override public boolean queueIdle() {
   1.311 +                final Handler geckoHandler = ThreadUtils.sGeckoHandler;
   1.312 +                Message idleMsg = Message.obtain(geckoHandler);
   1.313 +                // Use |Message.obj == GeckoHandler| to identify our "queue is empty" message
   1.314 +                idleMsg.obj = geckoHandler;
   1.315 +                geckoHandler.sendMessageAtFrontOfQueue(idleMsg);
   1.316 +                // Keep this IdleHandler
   1.317 +                return true;
   1.318 +            }
   1.319 +        };
   1.320 +        Looper.myQueue().addIdleHandler(idleHandler);
   1.321 +
   1.322 +        // run gecko -- it will spawn its own thread
   1.323 +        GeckoAppShell.nativeInit();
   1.324 +
   1.325 +        if (sLayerView != null)
   1.326 +            GeckoAppShell.setLayerClient(sLayerView.getLayerClientObject());
   1.327 +
   1.328 +        // First argument is the .apk path
   1.329 +        String combinedArgs = apkPath + " -greomni " + apkPath;
   1.330 +        if (args != null)
   1.331 +            combinedArgs += " " + args;
   1.332 +        if (url != null)
   1.333 +            combinedArgs += " -url " + url;
   1.334 +        if (type != null)
   1.335 +            combinedArgs += " " + type;
   1.336 +
   1.337 +        DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
   1.338 +        combinedArgs += " -width " + metrics.widthPixels + " -height " + metrics.heightPixels;
   1.339 +
   1.340 +        ThreadUtils.postToUiThread(new Runnable() {
   1.341 +                @Override
   1.342 +                public void run() {
   1.343 +                    geckoLoaded();
   1.344 +                }
   1.345 +            });
   1.346 +
   1.347 +        // and go
   1.348 +        Log.d(LOGTAG, "GeckoLoader.nativeRun " + combinedArgs);
   1.349 +        GeckoLoader.nativeRun(combinedArgs);
   1.350 +
   1.351 +        // Remove pumpMessageLoop() idle handler
   1.352 +        Looper.myQueue().removeIdleHandler(idleHandler);
   1.353 +    }
   1.354 +
   1.355 +    // Called on the UI thread after Gecko loads.
   1.356 +    private static void geckoLoaded() {
   1.357 +        GeckoEditable editable = new GeckoEditable();
   1.358 +        // install the gecko => editable listener
   1.359 +        editableListener = editable;
   1.360 +    }
   1.361 +
   1.362 +    static void sendPendingEventsToGecko() {
   1.363 +        try {
   1.364 +            while (!PENDING_EVENTS.isEmpty()) {
   1.365 +                final GeckoEvent e = PENDING_EVENTS.poll();
   1.366 +                notifyGeckoOfEvent(e);
   1.367 +            }
   1.368 +        } catch (NoSuchElementException e) {}
   1.369 +    }
   1.370 +
   1.371 +    /**
   1.372 +     * If the Gecko thread is running, immediately dispatches the event to
   1.373 +     * Gecko.
   1.374 +     *
   1.375 +     * If the Gecko thread is not running, queues the event. If the queue is
   1.376 +     * full, throws {@link IllegalStateException}.
   1.377 +     *
   1.378 +     * Queued events will be dispatched in order of arrival when the Gecko
   1.379 +     * thread becomes live.
   1.380 +     *
   1.381 +     * This method can be called from any thread.
   1.382 +     *
   1.383 +     * @param e
   1.384 +     *            the event to dispatch. Cannot be null.
   1.385 +     */
   1.386 +    @RobocopTarget
   1.387 +    public static void sendEventToGecko(GeckoEvent e) {
   1.388 +        if (e == null) {
   1.389 +            throw new IllegalArgumentException("e cannot be null.");
   1.390 +        }
   1.391 +
   1.392 +        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
   1.393 +            notifyGeckoOfEvent(e);
   1.394 +            // Gecko will copy the event data into a normal C++ object. We can recycle the event now.
   1.395 +            e.recycle();
   1.396 +            return;
   1.397 +        }
   1.398 +
   1.399 +        // Throws if unable to add the event due to capacity restrictions.
   1.400 +        PENDING_EVENTS.add(e);
   1.401 +    }
   1.402 +
   1.403 +    // Tell the Gecko event loop that an event is available.
   1.404 +    public static native void notifyGeckoOfEvent(GeckoEvent event);
   1.405 +
   1.406 +    /*
   1.407 +     *  The Gecko-side API: API methods that Gecko calls
   1.408 +     */
   1.409 +
   1.410 +    @WrapElementForJNI(allowMultithread = true, generateStatic = true, noThrow = true)
   1.411 +    public static void handleUncaughtException(Thread thread, Throwable e) {
   1.412 +        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExited)) {
   1.413 +            // We've called System.exit. All exceptions after this point are Android
   1.414 +            // berating us for being nasty to it.
   1.415 +            return;
   1.416 +        }
   1.417 +
   1.418 +        if (thread == null) {
   1.419 +            thread = Thread.currentThread();
   1.420 +        }
   1.421 +        // If the uncaught exception was rethrown, walk the exception `cause` chain to find
   1.422 +        // the original exception so Socorro can correctly collate related crash reports.
   1.423 +        Throwable cause;
   1.424 +        while ((cause = e.getCause()) != null) {
   1.425 +            e = cause;
   1.426 +        }
   1.427 +
   1.428 +        try {
   1.429 +            Log.e(LOGTAG, ">>> REPORTING UNCAUGHT EXCEPTION FROM THREAD "
   1.430 +                          + thread.getId() + " (\"" + thread.getName() + "\")", e);
   1.431 +
   1.432 +            Thread mainThread = ThreadUtils.getUiThread();
   1.433 +            if (mainThread != null && thread != mainThread) {
   1.434 +                Log.e(LOGTAG, "Main thread stack:");
   1.435 +                for (StackTraceElement ste : mainThread.getStackTrace()) {
   1.436 +                    Log.e(LOGTAG, ste.toString());
   1.437 +                }
   1.438 +            }
   1.439 +
   1.440 +            if (e instanceof OutOfMemoryError) {
   1.441 +                SharedPreferences prefs = getSharedPreferences();
   1.442 +                SharedPreferences.Editor editor = prefs.edit();
   1.443 +                editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, true);
   1.444 +                editor.commit();
   1.445 +            }
   1.446 +        } finally {
   1.447 +            reportJavaCrash(getStackTraceString(e));
   1.448 +        }
   1.449 +    }
   1.450 +
   1.451 +    @WrapElementForJNI(generateStatic = true)
   1.452 +    public static void notifyIME(int type) {
   1.453 +        if (editableListener != null) {
   1.454 +            editableListener.notifyIME(type);
   1.455 +        }
   1.456 +    }
   1.457 +
   1.458 +    @WrapElementForJNI(generateStatic = true)
   1.459 +    public static void notifyIMEContext(int state, String typeHint,
   1.460 +                                        String modeHint, String actionHint) {
   1.461 +        if (editableListener != null) {
   1.462 +            editableListener.notifyIMEContext(state, typeHint,
   1.463 +                                               modeHint, actionHint);
   1.464 +        }
   1.465 +    }
   1.466 +
   1.467 +    @WrapElementForJNI(generateStatic = true)
   1.468 +    public static void notifyIMEChange(String text, int start, int end, int newEnd) {
   1.469 +        if (newEnd < 0) { // Selection change
   1.470 +            editableListener.onSelectionChange(start, end);
   1.471 +        } else { // Text change
   1.472 +            editableListener.onTextChange(text, start, end, newEnd);
   1.473 +        }
   1.474 +    }
   1.475 +
   1.476 +    private static final Object sEventAckLock = new Object();
   1.477 +    private static boolean sWaitingForEventAck;
   1.478 +
   1.479 +    // Block the current thread until the Gecko event loop is caught up
   1.480 +    public static void sendEventToGeckoSync(GeckoEvent e) {
   1.481 +        e.setAckNeeded(true);
   1.482 +
   1.483 +        long time = SystemClock.uptimeMillis();
   1.484 +        boolean isUiThread = ThreadUtils.isOnUiThread();
   1.485 +
   1.486 +        synchronized (sEventAckLock) {
   1.487 +            if (sWaitingForEventAck) {
   1.488 +                // should never happen since we always leave it as false when we exit this function.
   1.489 +                Log.e(LOGTAG, "geckoEventSync() may have been called twice concurrently!", new Exception());
   1.490 +                // fall through for graceful handling
   1.491 +            }
   1.492 +
   1.493 +            sendEventToGecko(e);
   1.494 +            sWaitingForEventAck = true;
   1.495 +            while (true) {
   1.496 +                try {
   1.497 +                    sEventAckLock.wait(1000);
   1.498 +                } catch (InterruptedException ie) {
   1.499 +                }
   1.500 +                if (!sWaitingForEventAck) {
   1.501 +                    // response received
   1.502 +                    break;
   1.503 +                }
   1.504 +                long waited = SystemClock.uptimeMillis() - time;
   1.505 +                Log.d(LOGTAG, "Gecko event sync taking too long: " + waited + "ms");
   1.506 +            }
   1.507 +        }
   1.508 +    }
   1.509 +
   1.510 +    // Signal the Java thread that it's time to wake up
   1.511 +    @WrapElementForJNI
   1.512 +    public static void acknowledgeEvent() {
   1.513 +        synchronized (sEventAckLock) {
   1.514 +            sWaitingForEventAck = false;
   1.515 +            sEventAckLock.notifyAll();
   1.516 +        }
   1.517 +    }
   1.518 +
   1.519 +    private static float getLocationAccuracy(Location location) {
   1.520 +        float radius = location.getAccuracy();
   1.521 +        return (location.hasAccuracy() && radius > 0) ? radius : 1001;
   1.522 +    }
   1.523 +
   1.524 +    private static Location getLastKnownLocation(LocationManager lm) {
   1.525 +        Location lastKnownLocation = null;
   1.526 +        List<String> providers = lm.getAllProviders();
   1.527 +
   1.528 +        for (String provider : providers) {
   1.529 +            Location location = lm.getLastKnownLocation(provider);
   1.530 +            if (location == null) {
   1.531 +                continue;
   1.532 +            }
   1.533 +
   1.534 +            if (lastKnownLocation == null) {
   1.535 +                lastKnownLocation = location;
   1.536 +                continue;
   1.537 +            }
   1.538 +
   1.539 +            long timeDiff = location.getTime() - lastKnownLocation.getTime();
   1.540 +            if (timeDiff > 0 ||
   1.541 +                (timeDiff == 0 &&
   1.542 +                 getLocationAccuracy(location) < getLocationAccuracy(lastKnownLocation))) {
   1.543 +                lastKnownLocation = location;
   1.544 +            }
   1.545 +        }
   1.546 +
   1.547 +        return lastKnownLocation;
   1.548 +    }
   1.549 +
   1.550 +    @WrapElementForJNI
   1.551 +    public static void enableLocation(final boolean enable) {
   1.552 +        ThreadUtils.postToUiThread(new Runnable() {
   1.553 +                @Override
   1.554 +                public void run() {
   1.555 +                    LocationManager lm = getLocationManager(getContext());
   1.556 +                    if (lm == null) {
   1.557 +                        return;
   1.558 +                    }
   1.559 +
   1.560 +                    if (enable) {
   1.561 +                        Location lastKnownLocation = getLastKnownLocation(lm);
   1.562 +                        if (lastKnownLocation != null) {
   1.563 +                            getGeckoInterface().getLocationListener().onLocationChanged(lastKnownLocation);
   1.564 +                        }
   1.565 +
   1.566 +                        Criteria criteria = new Criteria();
   1.567 +                        criteria.setSpeedRequired(false);
   1.568 +                        criteria.setBearingRequired(false);
   1.569 +                        criteria.setAltitudeRequired(false);
   1.570 +                        if (locationHighAccuracyEnabled) {
   1.571 +                            criteria.setAccuracy(Criteria.ACCURACY_FINE);
   1.572 +                            criteria.setCostAllowed(true);
   1.573 +                            criteria.setPowerRequirement(Criteria.POWER_HIGH);
   1.574 +                        } else {
   1.575 +                            criteria.setAccuracy(Criteria.ACCURACY_COARSE);
   1.576 +                            criteria.setCostAllowed(false);
   1.577 +                            criteria.setPowerRequirement(Criteria.POWER_LOW);
   1.578 +                        }
   1.579 +
   1.580 +                        String provider = lm.getBestProvider(criteria, true);
   1.581 +                        if (provider == null)
   1.582 +                            return;
   1.583 +
   1.584 +                        Looper l = Looper.getMainLooper();
   1.585 +                        lm.requestLocationUpdates(provider, 100, (float).5, getGeckoInterface().getLocationListener(), l);
   1.586 +                    } else {
   1.587 +                        lm.removeUpdates(getGeckoInterface().getLocationListener());
   1.588 +                    }
   1.589 +                }
   1.590 +            });
   1.591 +    }
   1.592 +
   1.593 +    private static LocationManager getLocationManager(Context context) {
   1.594 +        try {
   1.595 +            return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
   1.596 +        } catch (NoSuchFieldError e) {
   1.597 +            // Some Tegras throw exceptions about missing the CONTROL_LOCATION_UPDATES permission,
   1.598 +            // which allows enabling/disabling location update notifications from the cell radio.
   1.599 +            // CONTROL_LOCATION_UPDATES is not for use by normal applications, but we might be
   1.600 +            // hitting this problem if the Tegras are confused about missing cell radios.
   1.601 +            Log.e(LOGTAG, "LOCATION_SERVICE not found?!", e);
   1.602 +            return null;
   1.603 +        }
   1.604 +    }
   1.605 +
   1.606 +    @WrapElementForJNI
   1.607 +    public static void enableLocationHighAccuracy(final boolean enable) {
   1.608 +        locationHighAccuracyEnabled = enable;
   1.609 +    }
   1.610 +
   1.611 +    @WrapElementForJNI
   1.612 +    public static void enableSensor(int aSensortype) {
   1.613 +        GeckoInterface gi = getGeckoInterface();
   1.614 +        if (gi == null)
   1.615 +            return;
   1.616 +        SensorManager sm = (SensorManager)
   1.617 +            getContext().getSystemService(Context.SENSOR_SERVICE);
   1.618 +
   1.619 +        switch(aSensortype) {
   1.620 +        case GeckoHalDefines.SENSOR_ORIENTATION:
   1.621 +            if(gOrientationSensor == null)
   1.622 +                gOrientationSensor = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);
   1.623 +            if (gOrientationSensor != null) 
   1.624 +                sm.registerListener(gi.getSensorEventListener(), gOrientationSensor, sDefaultSensorHint);
   1.625 +            break;
   1.626 +
   1.627 +        case GeckoHalDefines.SENSOR_ACCELERATION:
   1.628 +            if(gAccelerometerSensor == null)
   1.629 +                gAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
   1.630 +            if (gAccelerometerSensor != null)
   1.631 +                sm.registerListener(gi.getSensorEventListener(), gAccelerometerSensor, sDefaultSensorHint);
   1.632 +            break;
   1.633 +
   1.634 +        case GeckoHalDefines.SENSOR_PROXIMITY:
   1.635 +            if(gProximitySensor == null  )
   1.636 +                gProximitySensor = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
   1.637 +            if (gProximitySensor != null)
   1.638 +                sm.registerListener(gi.getSensorEventListener(), gProximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
   1.639 +            break;
   1.640 +
   1.641 +        case GeckoHalDefines.SENSOR_LIGHT:
   1.642 +            if(gLightSensor == null)
   1.643 +                gLightSensor = sm.getDefaultSensor(Sensor.TYPE_LIGHT);
   1.644 +            if (gLightSensor != null)
   1.645 +                sm.registerListener(gi.getSensorEventListener(), gLightSensor, SensorManager.SENSOR_DELAY_NORMAL);
   1.646 +            break;
   1.647 +
   1.648 +        case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION:
   1.649 +            if(gLinearAccelerometerSensor == null)
   1.650 +                gLinearAccelerometerSensor = sm.getDefaultSensor(10 /* API Level 9 - TYPE_LINEAR_ACCELERATION */);
   1.651 +            if (gLinearAccelerometerSensor != null)
   1.652 +                sm.registerListener(gi.getSensorEventListener(), gLinearAccelerometerSensor, sDefaultSensorHint);
   1.653 +            break;
   1.654 +
   1.655 +        case GeckoHalDefines.SENSOR_GYROSCOPE:
   1.656 +            if(gGyroscopeSensor == null)
   1.657 +                gGyroscopeSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
   1.658 +            if (gGyroscopeSensor != null)
   1.659 +                sm.registerListener(gi.getSensorEventListener(), gGyroscopeSensor, sDefaultSensorHint);
   1.660 +            break;
   1.661 +        default:
   1.662 +            Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " + aSensortype);
   1.663 +        }
   1.664 +    }
   1.665 +
   1.666 +    @WrapElementForJNI
   1.667 +    public static void disableSensor(int aSensortype) {
   1.668 +        GeckoInterface gi = getGeckoInterface();
   1.669 +        if (gi == null)
   1.670 +            return;
   1.671 +
   1.672 +        SensorManager sm = (SensorManager)
   1.673 +            getContext().getSystemService(Context.SENSOR_SERVICE);
   1.674 +
   1.675 +        switch (aSensortype) {
   1.676 +        case GeckoHalDefines.SENSOR_ORIENTATION:
   1.677 +            if (gOrientationSensor != null)
   1.678 +                sm.unregisterListener(gi.getSensorEventListener(), gOrientationSensor);
   1.679 +            break;
   1.680 +
   1.681 +        case GeckoHalDefines.SENSOR_ACCELERATION:
   1.682 +            if (gAccelerometerSensor != null)
   1.683 +                sm.unregisterListener(gi.getSensorEventListener(), gAccelerometerSensor);
   1.684 +            break;
   1.685 +
   1.686 +        case GeckoHalDefines.SENSOR_PROXIMITY:
   1.687 +            if (gProximitySensor != null)
   1.688 +                sm.unregisterListener(gi.getSensorEventListener(), gProximitySensor);
   1.689 +            break;
   1.690 +
   1.691 +        case GeckoHalDefines.SENSOR_LIGHT:
   1.692 +            if (gLightSensor != null)
   1.693 +                sm.unregisterListener(gi.getSensorEventListener(), gLightSensor);
   1.694 +            break;
   1.695 +
   1.696 +        case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION:
   1.697 +            if (gLinearAccelerometerSensor != null)
   1.698 +                sm.unregisterListener(gi.getSensorEventListener(), gLinearAccelerometerSensor);
   1.699 +            break;
   1.700 +
   1.701 +        case GeckoHalDefines.SENSOR_GYROSCOPE:
   1.702 +            if (gGyroscopeSensor != null)
   1.703 +                sm.unregisterListener(gi.getSensorEventListener(), gGyroscopeSensor);
   1.704 +            break;
   1.705 +        default:
   1.706 +            Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype);
   1.707 +        }
   1.708 +    }
   1.709 +
   1.710 +    @WrapElementForJNI
   1.711 +    public static void moveTaskToBack() {
   1.712 +        if (getGeckoInterface() != null)
   1.713 +            getGeckoInterface().getActivity().moveTaskToBack(true);
   1.714 +    }
   1.715 +
   1.716 +    public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
   1.717 +        // This method may be called from JNI to report Gecko's current selection indexes, but
   1.718 +        // Native Fennec doesn't care because the Java code already knows the selection indexes.
   1.719 +    }
   1.720 +
   1.721 +    @WrapElementForJNI(stubName = "NotifyXreExit")
   1.722 +    static void onXreExit() {
   1.723 +        // The launch state can only be Launched or GeckoRunning at this point
   1.724 +        GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoExiting);
   1.725 +        if (getGeckoInterface() != null) {
   1.726 +            if (restartScheduled) {
   1.727 +                getGeckoInterface().doRestart();
   1.728 +            } else {
   1.729 +                getGeckoInterface().getActivity().finish();
   1.730 +            }
   1.731 +        }
   1.732 +
   1.733 +        systemExit();
   1.734 +    }
   1.735 +
   1.736 +    static void systemExit() {
   1.737 +        Log.d(LOGTAG, "Killing via System.exit()");
   1.738 +        GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoExited);
   1.739 +        System.exit(0);
   1.740 +    }
   1.741 +
   1.742 +    @WrapElementForJNI
   1.743 +    static void scheduleRestart() {
   1.744 +        restartScheduled = true;
   1.745 +    }
   1.746 +
   1.747 +    public static Intent getWebappIntent(String aURI, String aOrigin, String aTitle, Bitmap aIcon) {
   1.748 +        Intent intent;
   1.749 +
   1.750 +        if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
   1.751 +            Allocator slots = Allocator.getInstance(getContext());
   1.752 +            int index = slots.getIndexForOrigin(aOrigin);
   1.753 +
   1.754 +            if (index == -1) {
   1.755 +                return null;
   1.756 +            }
   1.757 +            String packageName = slots.getAppForIndex(index);
   1.758 +            intent = getContext().getPackageManager().getLaunchIntentForPackage(packageName);
   1.759 +            if (aURI != null) {
   1.760 +                intent.setData(Uri.parse(aURI));
   1.761 +            }
   1.762 +        } else {
   1.763 +            int index;
   1.764 +            if (aIcon != null && !TextUtils.isEmpty(aTitle))
   1.765 +                index = WebappAllocator.getInstance(getContext()).findAndAllocateIndex(aOrigin, aTitle, aIcon);
   1.766 +            else
   1.767 +                index = WebappAllocator.getInstance(getContext()).getIndexForApp(aOrigin);
   1.768 +
   1.769 +            if (index == -1)
   1.770 +                return null;
   1.771 +
   1.772 +            intent = getWebappIntent(index, aURI);
   1.773 +        }
   1.774 +
   1.775 +        return intent;
   1.776 +    }
   1.777 +
   1.778 +    // The old implementation of getWebappIntent.  Not used by MOZ_ANDROID_SYNTHAPKS.
   1.779 +    public static Intent getWebappIntent(int aIndex, String aURI) {
   1.780 +        Intent intent = new Intent();
   1.781 +        intent.setAction(GeckoApp.ACTION_WEBAPP_PREFIX + aIndex);
   1.782 +        intent.setData(Uri.parse(aURI));
   1.783 +        intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
   1.784 +                            AppConstants.ANDROID_PACKAGE_NAME + ".WebApps$WebApp" + aIndex);
   1.785 +        return intent;
   1.786 +    }
   1.787 +
   1.788 +    // "Installs" an application by creating a shortcut
   1.789 +    // This is the entry point from AndroidBridge.h
   1.790 +    @WrapElementForJNI
   1.791 +    static void createShortcut(String aTitle, String aURI, String aIconData, String aType) {
   1.792 +        if ("webapp".equals(aType)) {
   1.793 +            Log.w(LOGTAG, "createShortcut with no unique URI should not be used for aType = webapp!");
   1.794 +        }
   1.795 +
   1.796 +        createShortcut(aTitle, aURI, aURI, aIconData, aType);
   1.797 +    }
   1.798 +
   1.799 +    // For non-webapps.
   1.800 +    public static void createShortcut(String aTitle, String aURI, Bitmap aBitmap, String aType) {
   1.801 +        createShortcut(aTitle, aURI, aURI, aBitmap, aType);
   1.802 +    }
   1.803 +
   1.804 +    // Internal, for webapps.
   1.805 +    static void createShortcut(final String aTitle, final String aURI, final String aUniqueURI, final String aIconData, final String aType) {
   1.806 +        ThreadUtils.postToBackgroundThread(new Runnable() {
   1.807 +            @Override
   1.808 +            public void run() {
   1.809 +                // TODO: use the cache. Bug 961600.
   1.810 +                Bitmap icon = FaviconDecoder.getMostSuitableBitmapFromDataURI(aIconData, getPreferredIconSize());
   1.811 +                GeckoAppShell.doCreateShortcut(aTitle, aURI, aURI, icon, aType);
   1.812 +            }
   1.813 +        });
   1.814 +    }
   1.815 +
   1.816 +    public static void createShortcut(final String aTitle, final String aURI, final String aUniqueURI,
   1.817 +                                      final Bitmap aIcon, final String aType) {
   1.818 +        ThreadUtils.postToBackgroundThread(new Runnable() {
   1.819 +            @Override
   1.820 +            public void run() {
   1.821 +                GeckoAppShell.doCreateShortcut(aTitle, aURI, aUniqueURI, aIcon, aType);
   1.822 +            }
   1.823 +        });
   1.824 +    }
   1.825 +
   1.826 +    /**
   1.827 +     * Call this method only on the background thread.
   1.828 +     */
   1.829 +    private static void doCreateShortcut(final String aTitle, final String aURI, final String aUniqueURI,
   1.830 +                                         final Bitmap aIcon, final String aType) {
   1.831 +        // The intent to be launched by the shortcut.
   1.832 +        Intent shortcutIntent;
   1.833 +        if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP)) {
   1.834 +            shortcutIntent = getWebappIntent(aURI, aUniqueURI, aTitle, aIcon);
   1.835 +        } else {
   1.836 +            shortcutIntent = new Intent();
   1.837 +            shortcutIntent.setAction(GeckoApp.ACTION_BOOKMARK);
   1.838 +            shortcutIntent.setData(Uri.parse(aURI));
   1.839 +            shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
   1.840 +                                        AppConstants.BROWSER_INTENT_CLASS_NAME);
   1.841 +        }
   1.842 +
   1.843 +        Intent intent = new Intent();
   1.844 +        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
   1.845 +        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon, aType));
   1.846 +
   1.847 +        if (aTitle != null) {
   1.848 +            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
   1.849 +        } else {
   1.850 +            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
   1.851 +        }
   1.852 +
   1.853 +        // Do not allow duplicate items.
   1.854 +        intent.putExtra("duplicate", false);
   1.855 +
   1.856 +        intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
   1.857 +        getContext().sendBroadcast(intent);
   1.858 +    }
   1.859 +
   1.860 +    public static void removeShortcut(final String aTitle, final String aURI, final String aType) {
   1.861 +        removeShortcut(aTitle, aURI, null, aType);
   1.862 +    }
   1.863 +
   1.864 +    public static void removeShortcut(final String aTitle, final String aURI, final String aUniqueURI, final String aType) {
   1.865 +        ThreadUtils.postToBackgroundThread(new Runnable() {
   1.866 +            @Override
   1.867 +            public void run() {
   1.868 +                // the intent to be launched by the shortcut
   1.869 +                Intent shortcutIntent;
   1.870 +                if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP)) {
   1.871 +                    shortcutIntent = getWebappIntent(aURI, aUniqueURI, "", null);
   1.872 +                    if (shortcutIntent == null)
   1.873 +                        return;
   1.874 +                } else {
   1.875 +                    shortcutIntent = new Intent();
   1.876 +                    shortcutIntent.setAction(GeckoApp.ACTION_BOOKMARK);
   1.877 +                    shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
   1.878 +                                                AppConstants.BROWSER_INTENT_CLASS_NAME);
   1.879 +                    shortcutIntent.setData(Uri.parse(aURI));
   1.880 +                }
   1.881 +        
   1.882 +                Intent intent = new Intent();
   1.883 +                intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
   1.884 +                if (aTitle != null)
   1.885 +                    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
   1.886 +                else
   1.887 +                    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
   1.888 +
   1.889 +                intent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
   1.890 +                getContext().sendBroadcast(intent);
   1.891 +            }
   1.892 +        });
   1.893 +    }
   1.894 +
   1.895 +    @JNITarget
   1.896 +    static public int getPreferredIconSize() {
   1.897 +        if (android.os.Build.VERSION.SDK_INT >= 11) {
   1.898 +            ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
   1.899 +            return am.getLauncherLargeIconSize();
   1.900 +        } else {
   1.901 +            switch (getDpi()) {
   1.902 +                case DisplayMetrics.DENSITY_MEDIUM:
   1.903 +                    return 48;
   1.904 +                case DisplayMetrics.DENSITY_XHIGH:
   1.905 +                    return 96;
   1.906 +                case DisplayMetrics.DENSITY_HIGH:
   1.907 +                default:
   1.908 +                    return 72;
   1.909 +            }
   1.910 +        }
   1.911 +    }
   1.912 +
   1.913 +    static private Bitmap getLauncherIcon(Bitmap aSource, String aType) {
   1.914 +        final int kOffset = 6;
   1.915 +        final int kRadius = 5;
   1.916 +        int size = getPreferredIconSize();
   1.917 +        int insetSize = aSource != null ? size * 2 / 3 : size;
   1.918 +
   1.919 +        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
   1.920 +        Canvas canvas = new Canvas(bitmap);
   1.921 +
   1.922 +
   1.923 +        // draw a base color
   1.924 +        Paint paint = new Paint();
   1.925 +        if (aSource == null) {
   1.926 +            // If we aren't drawing a favicon, just use an orange color.
   1.927 +            paint.setColor(Color.HSVToColor(DEFAULT_LAUNCHER_ICON_HSV));
   1.928 +            canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
   1.929 +        } else if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP) || aSource.getWidth() >= insetSize || aSource.getHeight() >= insetSize) {
   1.930 +            // otherwise, if this is a webapp or if the icons is lare enough, just draw it
   1.931 +            Rect iconBounds = new Rect(0, 0, size, size);
   1.932 +            canvas.drawBitmap(aSource, null, iconBounds, null);
   1.933 +            return bitmap;
   1.934 +        } else {
   1.935 +            // otherwise use the dominant color from the icon + a layer of transparent white to lighten it somewhat
   1.936 +            int color = BitmapUtils.getDominantColor(aSource);
   1.937 +            paint.setColor(color);
   1.938 +            canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
   1.939 +            paint.setColor(Color.argb(100, 255, 255, 255));
   1.940 +            canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
   1.941 +        }
   1.942 +
   1.943 +        // draw the overlay
   1.944 +        Bitmap overlay = BitmapUtils.decodeResource(getContext(), R.drawable.home_bg);
   1.945 +        canvas.drawBitmap(overlay, null, new Rect(0, 0, size, size), null);
   1.946 +
   1.947 +        // draw the favicon
   1.948 +        if (aSource == null)
   1.949 +            aSource = BitmapUtils.decodeResource(getContext(), R.drawable.home_star);
   1.950 +
   1.951 +        // by default, we scale the icon to this size
   1.952 +        int sWidth = insetSize / 2;
   1.953 +        int sHeight = sWidth;
   1.954 +
   1.955 +        int halfSize = size / 2;
   1.956 +        canvas.drawBitmap(aSource,
   1.957 +                          null,
   1.958 +                          new Rect(halfSize - sWidth,
   1.959 +                                   halfSize - sHeight,
   1.960 +                                   halfSize + sWidth,
   1.961 +                                   halfSize + sHeight),
   1.962 +                          null);
   1.963 +
   1.964 +        return bitmap;
   1.965 +    }
   1.966 +
   1.967 +    @WrapElementForJNI(stubName = "GetHandlersForMimeTypeWrapper")
   1.968 +    static String[] getHandlersForMimeType(String aMimeType, String aAction) {
   1.969 +        Intent intent = getIntentForActionString(aAction);
   1.970 +        if (aMimeType != null && aMimeType.length() > 0)
   1.971 +            intent.setType(aMimeType);
   1.972 +        return getHandlersForIntent(intent);
   1.973 +    }
   1.974 +
   1.975 +    @WrapElementForJNI(stubName = "GetHandlersForURLWrapper")
   1.976 +    static String[] getHandlersForURL(String aURL, String aAction) {
   1.977 +        // aURL may contain the whole URL or just the protocol
   1.978 +        Uri uri = aURL.indexOf(':') >= 0 ? Uri.parse(aURL) : new Uri.Builder().scheme(aURL).build();
   1.979 +
   1.980 +        Intent intent = getOpenURIIntent(getContext(), uri.toString(), "",
   1.981 +            TextUtils.isEmpty(aAction) ? Intent.ACTION_VIEW : aAction, "");
   1.982 +
   1.983 +        return getHandlersForIntent(intent);
   1.984 +    }
   1.985 +
   1.986 +    static boolean hasHandlersForIntent(Intent intent) {
   1.987 +        PackageManager pm = getContext().getPackageManager();
   1.988 +        List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
   1.989 +        return !list.isEmpty();
   1.990 +    }
   1.991 +
   1.992 +    static String[] getHandlersForIntent(Intent intent) {
   1.993 +        PackageManager pm = getContext().getPackageManager();
   1.994 +        List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
   1.995 +        int numAttr = 4;
   1.996 +        String[] ret = new String[list.size() * numAttr];
   1.997 +        for (int i = 0; i < list.size(); i++) {
   1.998 +            ResolveInfo resolveInfo = list.get(i);
   1.999 +            ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
  1.1000 +            if (resolveInfo.isDefault)
  1.1001 +                ret[i * numAttr + 1] = "default";
  1.1002 +            else
  1.1003 +                ret[i * numAttr + 1] = "";
  1.1004 +            ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
  1.1005 +            ret[i * numAttr + 3] = resolveInfo.activityInfo.name;
  1.1006 +        }
  1.1007 +        return ret;
  1.1008 +    }
  1.1009 +
  1.1010 +    static Intent getIntentForActionString(String aAction) {
  1.1011 +        // Default to the view action if no other action as been specified.
  1.1012 +        if (TextUtils.isEmpty(aAction)) {
  1.1013 +            return new Intent(Intent.ACTION_VIEW);
  1.1014 +        }
  1.1015 +        return new Intent(aAction);
  1.1016 +    }
  1.1017 +
  1.1018 +    @WrapElementForJNI(stubName = "GetExtensionFromMimeTypeWrapper")
  1.1019 +    static String getExtensionFromMimeType(String aMimeType) {
  1.1020 +        return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType);
  1.1021 +    }
  1.1022 +
  1.1023 +    @WrapElementForJNI(stubName = "GetMimeTypeFromExtensionsWrapper")
  1.1024 +    static String getMimeTypeFromExtensions(String aFileExt) {
  1.1025 +        StringTokenizer st = new StringTokenizer(aFileExt, ".,; ");
  1.1026 +        String type = null;
  1.1027 +        String subType = null;
  1.1028 +        while (st.hasMoreElements()) {
  1.1029 +            String ext = st.nextToken();
  1.1030 +            String mt = getMimeTypeFromExtension(ext);
  1.1031 +            if (mt == null)
  1.1032 +                continue;
  1.1033 +            int slash = mt.indexOf('/');
  1.1034 +            String tmpType = mt.substring(0, slash);
  1.1035 +            if (!tmpType.equalsIgnoreCase(type))
  1.1036 +                type = type == null ? tmpType : "*";
  1.1037 +            String tmpSubType = mt.substring(slash + 1);
  1.1038 +            if (!tmpSubType.equalsIgnoreCase(subType))
  1.1039 +                subType = subType == null ? tmpSubType : "*";
  1.1040 +        }
  1.1041 +        if (type == null)
  1.1042 +            type = "*";
  1.1043 +        if (subType == null)
  1.1044 +            subType = "*";
  1.1045 +        return type + "/" + subType;
  1.1046 +    }
  1.1047 +
  1.1048 +    static void safeStreamClose(Closeable stream) {
  1.1049 +        try {
  1.1050 +            if (stream != null)
  1.1051 +                stream.close();
  1.1052 +        } catch (IOException e) {}
  1.1053 +    }
  1.1054 +
  1.1055 +    static boolean isUriSafeForScheme(Uri aUri) {
  1.1056 +        // Bug 794034 - We don't want to pass MWI or USSD codes to the
  1.1057 +        // dialer, and ensure the Uri class doesn't parse a URI
  1.1058 +        // containing a fragment ('#')
  1.1059 +        final String scheme = aUri.getScheme();
  1.1060 +        if ("tel".equals(scheme) || "sms".equals(scheme)) {
  1.1061 +            final String number = aUri.getSchemeSpecificPart();
  1.1062 +            if (number.contains("#") || number.contains("*") || aUri.getFragment() != null) {
  1.1063 +                return false;
  1.1064 +            }
  1.1065 +        }
  1.1066 +        return true;
  1.1067 +    }
  1.1068 +
  1.1069 +    /**
  1.1070 +     * Given the inputs to <code>getOpenURIIntent</code>, plus an optional
  1.1071 +     * package name and class name, create and fire an intent to open the
  1.1072 +     * provided URI. If a class name is specified but a package name is not,
  1.1073 +     * we will default to using the current fennec package.
  1.1074 +     *
  1.1075 +     * @param targetURI the string spec of the URI to open.
  1.1076 +     * @param mimeType an optional MIME type string.
  1.1077 +     * @param packageName an optional app package name.
  1.1078 +     * @param className an optional intent class name.
  1.1079 +     * @param action an Android action specifier, such as
  1.1080 +     *               <code>Intent.ACTION_SEND</code>.
  1.1081 +     * @param title the title to use in <code>ACTION_SEND</code> intents.
  1.1082 +     * @return true if the activity started successfully; false otherwise.
  1.1083 +     */
  1.1084 +    @WrapElementForJNI
  1.1085 +    public static boolean openUriExternal(String targetURI,
  1.1086 +                                          String mimeType,
  1.1087 +              @OptionalGeneratedParameter String packageName,
  1.1088 +              @OptionalGeneratedParameter String className,
  1.1089 +              @OptionalGeneratedParameter String action,
  1.1090 +              @OptionalGeneratedParameter String title) {
  1.1091 +        final Context context = getContext();
  1.1092 +        final Intent intent = getOpenURIIntent(context, targetURI,
  1.1093 +                                               mimeType, action, title);
  1.1094 +
  1.1095 +        if (intent == null) {
  1.1096 +            return false;
  1.1097 +        }
  1.1098 +
  1.1099 +        if (!TextUtils.isEmpty(className)) {
  1.1100 +            if (!TextUtils.isEmpty(packageName)) {
  1.1101 +                intent.setClassName(packageName, className);
  1.1102 +            } else {
  1.1103 +                // Default to using the fennec app context.
  1.1104 +                intent.setClassName(context, className);
  1.1105 +            }
  1.1106 +        }
  1.1107 +
  1.1108 +        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  1.1109 +        try {
  1.1110 +            context.startActivity(intent);
  1.1111 +            return true;
  1.1112 +        } catch (ActivityNotFoundException e) {
  1.1113 +            return false;
  1.1114 +        }
  1.1115 +    }
  1.1116 +
  1.1117 +    /**
  1.1118 +     * Return a <code>Uri</code> instance which is equivalent to <code>u</code>,
  1.1119 +     * but with a guaranteed-lowercase scheme as if the API level 16 method
  1.1120 +     * <code>u.normalizeScheme</code> had been called.
  1.1121 +     *
  1.1122 +     * @param u the <code>Uri</code> to normalize.
  1.1123 +     * @return a <code>Uri</code>, which might be <code>u</code>.
  1.1124 +     */
  1.1125 +    static Uri normalizeUriScheme(final Uri u) {
  1.1126 +        final String scheme = u.getScheme();
  1.1127 +        final String lower  = scheme.toLowerCase(Locale.US);
  1.1128 +        if (lower.equals(scheme)) {
  1.1129 +            return u;
  1.1130 +        }
  1.1131 +
  1.1132 +        // Otherwise, return a new URI with a normalized scheme.
  1.1133 +        return u.buildUpon().scheme(lower).build();
  1.1134 +    }
  1.1135 +
  1.1136 +    /**
  1.1137 +     * Given a URI, a MIME type, and a title,
  1.1138 +     * produce a share intent which can be used to query all activities
  1.1139 +     * than can open the specified URI.
  1.1140 +     *
  1.1141 +     * @param context a <code>Context</code> instance.
  1.1142 +     * @param targetURI the string spec of the URI to open.
  1.1143 +     * @param mimeType an optional MIME type string.
  1.1144 +     * @param title the title to use in <code>ACTION_SEND</code> intents.
  1.1145 +     * @return an <code>Intent</code>, or <code>null</code> if none could be
  1.1146 +     *         produced.
  1.1147 +     */
  1.1148 +    public static Intent getShareIntent(final Context context,
  1.1149 +                                        final String targetURI,
  1.1150 +                                        final String mimeType,
  1.1151 +                                        final String title) {
  1.1152 +        Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND);
  1.1153 +        shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI);
  1.1154 +        shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
  1.1155 +
  1.1156 +        // Note that EXTRA_TITLE is intended to be used for share dialog
  1.1157 +        // titles. Common usage (e.g., Pocket) suggests that it's sometimes
  1.1158 +        // interpreted as an alternate to EXTRA_SUBJECT, so we include it.
  1.1159 +        shareIntent.putExtra(Intent.EXTRA_TITLE, title);
  1.1160 +
  1.1161 +        if (mimeType != null && mimeType.length() > 0) {
  1.1162 +            shareIntent.setType(mimeType);
  1.1163 +        }
  1.1164 +
  1.1165 +        return shareIntent;
  1.1166 +    }
  1.1167 +
  1.1168 +    /**
  1.1169 +     * Given a URI, a MIME type, an Android intent "action", and a title,
  1.1170 +     * produce an intent which can be used to start an activity to open
  1.1171 +     * the specified URI.
  1.1172 +     *
  1.1173 +     * @param context a <code>Context</code> instance.
  1.1174 +     * @param targetURI the string spec of the URI to open.
  1.1175 +     * @param mimeType an optional MIME type string.
  1.1176 +     * @param action an Android action specifier, such as
  1.1177 +     *               <code>Intent.ACTION_SEND</code>.
  1.1178 +     * @param title the title to use in <code>ACTION_SEND</code> intents.
  1.1179 +     * @return an <code>Intent</code>, or <code>null</code> if none could be
  1.1180 +     *         produced.
  1.1181 +     */
  1.1182 +    static Intent getOpenURIIntent(final Context context,
  1.1183 +                                   final String targetURI,
  1.1184 +                                   final String mimeType,
  1.1185 +                                   final String action,
  1.1186 +                                   final String title) {
  1.1187 +
  1.1188 +        if (action.equalsIgnoreCase(Intent.ACTION_SEND)) {
  1.1189 +            Intent shareIntent = getShareIntent(context, targetURI, mimeType, title);
  1.1190 +            return Intent.createChooser(shareIntent,
  1.1191 +                                        context.getResources().getString(R.string.share_title)); 
  1.1192 +        }
  1.1193 +
  1.1194 +        final Uri uri = normalizeUriScheme(targetURI.indexOf(':') >= 0 ? Uri.parse(targetURI) : new Uri.Builder().scheme(targetURI).build());
  1.1195 +        if (mimeType.length() > 0) {
  1.1196 +            Intent intent = getIntentForActionString(action);
  1.1197 +            intent.setDataAndType(uri, mimeType);
  1.1198 +            return intent;
  1.1199 +        }
  1.1200 +
  1.1201 +        if (!isUriSafeForScheme(uri)) {
  1.1202 +            return null;
  1.1203 +        }
  1.1204 +
  1.1205 +        final String scheme = uri.getScheme();
  1.1206 +
  1.1207 +        final Intent intent;
  1.1208 +
  1.1209 +        // Compute our most likely intent, then check to see if there are any
  1.1210 +        // custom handlers that would apply.
  1.1211 +        // Start with the original URI. If we end up modifying it, we'll
  1.1212 +        // overwrite it.
  1.1213 +        final Intent likelyIntent = getIntentForActionString(action);
  1.1214 +        likelyIntent.setData(uri);
  1.1215 +
  1.1216 +        if ("vnd.youtube".equals(scheme) && !hasHandlersForIntent(likelyIntent)) {
  1.1217 +            // Special-case YouTube to use our own player if no system handler
  1.1218 +            // exists.
  1.1219 +            intent = new Intent(VideoPlayer.VIDEO_ACTION);
  1.1220 +            intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
  1.1221 +                                "org.mozilla.gecko.VideoPlayer");
  1.1222 +            intent.setData(uri);
  1.1223 +        } else {
  1.1224 +            intent = likelyIntent;
  1.1225 +        }
  1.1226 +
  1.1227 +        // Have a special handling for SMS, as the message body
  1.1228 +        // is not extracted from the URI automatically.
  1.1229 +        if (!"sms".equals(scheme)) {
  1.1230 +            return intent;
  1.1231 +        }
  1.1232 +
  1.1233 +        final String query = uri.getEncodedQuery();
  1.1234 +        if (TextUtils.isEmpty(query)) {
  1.1235 +            return intent;
  1.1236 +        }
  1.1237 +
  1.1238 +        final String[] fields = query.split("&");
  1.1239 +        boolean foundBody = false;
  1.1240 +        String resultQuery = "";
  1.1241 +        for (String field : fields) {
  1.1242 +            if (foundBody || !field.startsWith("body=")) {
  1.1243 +                resultQuery = resultQuery.concat(resultQuery.length() > 0 ? "&" + field : field);
  1.1244 +                continue;
  1.1245 +            }
  1.1246 +
  1.1247 +            // Found the first body param. Put it into the intent.
  1.1248 +            final String body = Uri.decode(field.substring(5));
  1.1249 +            intent.putExtra("sms_body", body);
  1.1250 +            foundBody = true;
  1.1251 +        }
  1.1252 +
  1.1253 +        if (!foundBody) {
  1.1254 +            // No need to rewrite the URI, then.
  1.1255 +            return intent;
  1.1256 +        }
  1.1257 +
  1.1258 +        // Form a new URI without the body field in the query part, and
  1.1259 +        // push that into the new Intent.
  1.1260 +        final String newQuery = resultQuery.length() > 0 ? "?" + resultQuery : "";
  1.1261 +        final Uri pruned = uri.buildUpon().encodedQuery(newQuery).build();
  1.1262 +        intent.setData(pruned);
  1.1263 +
  1.1264 +        return intent;
  1.1265 +    }
  1.1266 +
  1.1267 +    /**
  1.1268 +     * Only called from GeckoApp.
  1.1269 +     */
  1.1270 +    public static void setNotificationClient(NotificationClient client) {
  1.1271 +        if (notificationClient == null) {
  1.1272 +            notificationClient = client;
  1.1273 +        } else {
  1.1274 +            Log.d(LOGTAG, "Notification client already set");
  1.1275 +        }
  1.1276 +    }
  1.1277 +
  1.1278 +    @WrapElementForJNI(stubName = "ShowAlertNotificationWrapper")
  1.1279 +    public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
  1.1280 +                                             String aAlertCookie, String aAlertName) {
  1.1281 +        // The intent to launch when the user clicks the expanded notification
  1.1282 +        String app = getContext().getClass().getName();
  1.1283 +        Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CALLBACK);
  1.1284 +        notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, app);
  1.1285 +        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  1.1286 +
  1.1287 +        int notificationID = aAlertName.hashCode();
  1.1288 +
  1.1289 +        // Put the strings into the intent as an URI "alert:?name=<alertName>&app=<appName>&cookie=<cookie>"
  1.1290 +        Uri.Builder b = new Uri.Builder();
  1.1291 +        Uri dataUri = b.scheme("alert").path(Integer.toString(notificationID))
  1.1292 +                                       .appendQueryParameter("name", aAlertName)
  1.1293 +                                       .appendQueryParameter("cookie", aAlertCookie)
  1.1294 +                                       .build();
  1.1295 +        notificationIntent.setData(dataUri);
  1.1296 +        PendingIntent contentIntent = PendingIntent.getActivity(
  1.1297 +                getContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  1.1298 +
  1.1299 +        ALERT_COOKIES.put(aAlertName, aAlertCookie);
  1.1300 +        callObserver(aAlertName, "alertshow", aAlertCookie);
  1.1301 +
  1.1302 +        notificationClient.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent);
  1.1303 +    }
  1.1304 +
  1.1305 +    @WrapElementForJNI
  1.1306 +    public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
  1.1307 +        int notificationID = aAlertName.hashCode();
  1.1308 +        notificationClient.update(notificationID, aProgress, aProgressMax, aAlertText);
  1.1309 +    }
  1.1310 +
  1.1311 +    @WrapElementForJNI
  1.1312 +    public static void closeNotification(String aAlertName) {
  1.1313 +        String alertCookie = ALERT_COOKIES.get(aAlertName);
  1.1314 +        if (alertCookie != null) {
  1.1315 +            callObserver(aAlertName, "alertfinished", alertCookie);
  1.1316 +            ALERT_COOKIES.remove(aAlertName);
  1.1317 +        }
  1.1318 +
  1.1319 +        removeObserver(aAlertName);
  1.1320 +
  1.1321 +        int notificationID = aAlertName.hashCode();
  1.1322 +        notificationClient.remove(notificationID);
  1.1323 +    }
  1.1324 +
  1.1325 +    public static void handleNotification(String aAction, String aAlertName, String aAlertCookie) {
  1.1326 +        int notificationID = aAlertName.hashCode();
  1.1327 +
  1.1328 +        if (GeckoApp.ACTION_ALERT_CALLBACK.equals(aAction)) {
  1.1329 +            callObserver(aAlertName, "alertclickcallback", aAlertCookie);
  1.1330 +
  1.1331 +            if (notificationClient.isOngoing(notificationID)) {
  1.1332 +                // When clicked, keep the notification if it displays progress
  1.1333 +                return;
  1.1334 +            }
  1.1335 +        }
  1.1336 +        closeNotification(aAlertName);
  1.1337 +    }
  1.1338 +
  1.1339 +    @WrapElementForJNI(stubName = "GetDpiWrapper")
  1.1340 +    public static int getDpi() {
  1.1341 +        if (sDensityDpi == 0) {
  1.1342 +            sDensityDpi = getContext().getResources().getDisplayMetrics().densityDpi;
  1.1343 +        }
  1.1344 +
  1.1345 +        return sDensityDpi;
  1.1346 +    }
  1.1347 +
  1.1348 +    @WrapElementForJNI
  1.1349 +    public static float getDensity() {
  1.1350 +        return getContext().getResources().getDisplayMetrics().density;
  1.1351 +    }
  1.1352 +
  1.1353 +    private static boolean isHighMemoryDevice() {
  1.1354 +        return HardwareUtils.getMemSize() > HIGH_MEMORY_DEVICE_THRESHOLD_MB;
  1.1355 +    }
  1.1356 +
  1.1357 +    /**
  1.1358 +     * Returns the colour depth of the default screen. This will either be
  1.1359 +     * 24 or 16.
  1.1360 +     */
  1.1361 +    @WrapElementForJNI(stubName = "GetScreenDepthWrapper")
  1.1362 +    public static synchronized int getScreenDepth() {
  1.1363 +        if (sScreenDepth == 0) {
  1.1364 +            sScreenDepth = 16;
  1.1365 +            PixelFormat info = new PixelFormat();
  1.1366 +            PixelFormat.getPixelFormatInfo(getGeckoInterface().getActivity().getWindowManager().getDefaultDisplay().getPixelFormat(), info);
  1.1367 +            if (info.bitsPerPixel >= 24 && isHighMemoryDevice()) {
  1.1368 +                sScreenDepth = 24;
  1.1369 +            }
  1.1370 +        }
  1.1371 +
  1.1372 +        return sScreenDepth;
  1.1373 +    }
  1.1374 +
  1.1375 +    public static synchronized void setScreenDepthOverride(int aScreenDepth) {
  1.1376 +        if (sScreenDepth != 0) {
  1.1377 +            Log.e(LOGTAG, "Tried to override screen depth after it's already been set");
  1.1378 +            return;
  1.1379 +        }
  1.1380 +
  1.1381 +        sScreenDepth = aScreenDepth;
  1.1382 +    }
  1.1383 +
  1.1384 +    @WrapElementForJNI
  1.1385 +    public static void setFullScreen(boolean fullscreen) {
  1.1386 +        if (getGeckoInterface() != null)
  1.1387 +            getGeckoInterface().setFullScreen(fullscreen);
  1.1388 +    }
  1.1389 +
  1.1390 +    @WrapElementForJNI
  1.1391 +    public static void performHapticFeedback(boolean aIsLongPress) {
  1.1392 +        // Don't perform haptic feedback if a vibration is currently playing,
  1.1393 +        // because the haptic feedback will nuke the vibration.
  1.1394 +        if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) {
  1.1395 +            LayerView layerView = getLayerView();
  1.1396 +            layerView.performHapticFeedback(aIsLongPress ?
  1.1397 +                                            HapticFeedbackConstants.LONG_PRESS :
  1.1398 +                                            HapticFeedbackConstants.VIRTUAL_KEY);
  1.1399 +        }
  1.1400 +    }
  1.1401 +
  1.1402 +    private static Vibrator vibrator() {
  1.1403 +        LayerView layerView = getLayerView();
  1.1404 +        return (Vibrator) layerView.getContext().getSystemService(Context.VIBRATOR_SERVICE);
  1.1405 +    }
  1.1406 +
  1.1407 +    @WrapElementForJNI(stubName = "Vibrate1")
  1.1408 +    public static void vibrate(long milliseconds) {
  1.1409 +        sVibrationEndTime = System.nanoTime() + milliseconds * 1000000;
  1.1410 +        sVibrationMaybePlaying = true;
  1.1411 +        vibrator().vibrate(milliseconds);
  1.1412 +    }
  1.1413 +
  1.1414 +    @WrapElementForJNI(stubName = "VibrateA")
  1.1415 +    public static void vibrate(long[] pattern, int repeat) {
  1.1416 +        // If pattern.length is even, the last element in the pattern is a
  1.1417 +        // meaningless delay, so don't include it in vibrationDuration.
  1.1418 +        long vibrationDuration = 0;
  1.1419 +        int iterLen = pattern.length - (pattern.length % 2 == 0 ? 1 : 0);
  1.1420 +        for (int i = 0; i < iterLen; i++) {
  1.1421 +          vibrationDuration += pattern[i];
  1.1422 +        }
  1.1423 +
  1.1424 +        sVibrationEndTime = System.nanoTime() + vibrationDuration * 1000000;
  1.1425 +        sVibrationMaybePlaying = true;
  1.1426 +        vibrator().vibrate(pattern, repeat);
  1.1427 +    }
  1.1428 +
  1.1429 +    @WrapElementForJNI
  1.1430 +    public static void cancelVibrate() {
  1.1431 +        sVibrationMaybePlaying = false;
  1.1432 +        sVibrationEndTime = 0;
  1.1433 +        vibrator().cancel();
  1.1434 +    }
  1.1435 +
  1.1436 +    @WrapElementForJNI
  1.1437 +    public static void showInputMethodPicker() {
  1.1438 +        InputMethodManager imm = (InputMethodManager)
  1.1439 +            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
  1.1440 +        imm.showInputMethodPicker();
  1.1441 +    }
  1.1442 +
  1.1443 +    @WrapElementForJNI
  1.1444 +    public static void setKeepScreenOn(final boolean on) {
  1.1445 +        ThreadUtils.postToUiThread(new Runnable() {
  1.1446 +            @Override
  1.1447 +            public void run() {
  1.1448 +                // TODO
  1.1449 +            }
  1.1450 +        });
  1.1451 +    }
  1.1452 +
  1.1453 +    @WrapElementForJNI
  1.1454 +    public static void notifyDefaultPrevented(final boolean defaultPrevented) {
  1.1455 +        ThreadUtils.postToUiThread(new Runnable() {
  1.1456 +            @Override
  1.1457 +            public void run() {
  1.1458 +                LayerView view = getLayerView();
  1.1459 +                PanZoomController controller = (view == null ? null : view.getPanZoomController());
  1.1460 +                if (controller != null) {
  1.1461 +                    controller.notifyDefaultActionPrevented(defaultPrevented);
  1.1462 +                }
  1.1463 +            }
  1.1464 +        });
  1.1465 +    }
  1.1466 +
  1.1467 +    @WrapElementForJNI
  1.1468 +    public static boolean isNetworkLinkUp() {
  1.1469 +        ConnectivityManager cm = (ConnectivityManager)
  1.1470 +           getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
  1.1471 +        try {
  1.1472 +            NetworkInfo info = cm.getActiveNetworkInfo();
  1.1473 +            if (info == null || !info.isConnected())
  1.1474 +                return false;
  1.1475 +        } catch (SecurityException se) {
  1.1476 +            return false;
  1.1477 +        }
  1.1478 +        return true;
  1.1479 +    }
  1.1480 +
  1.1481 +    @WrapElementForJNI
  1.1482 +    public static boolean isNetworkLinkKnown() {
  1.1483 +        ConnectivityManager cm = (ConnectivityManager)
  1.1484 +            getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
  1.1485 +        try {
  1.1486 +            if (cm.getActiveNetworkInfo() == null)
  1.1487 +                return false;
  1.1488 +        } catch (SecurityException se) {
  1.1489 +            return false;
  1.1490 +        }
  1.1491 +        return true;
  1.1492 +    }
  1.1493 +
  1.1494 +    @WrapElementForJNI
  1.1495 +    public static int networkLinkType() {
  1.1496 +        ConnectivityManager cm = (ConnectivityManager)
  1.1497 +            getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
  1.1498 +        NetworkInfo info = cm.getActiveNetworkInfo();
  1.1499 +        if (info == null) {
  1.1500 +            return LINK_TYPE_UNKNOWN;
  1.1501 +        }
  1.1502 +
  1.1503 +        switch (info.getType()) {
  1.1504 +            case ConnectivityManager.TYPE_ETHERNET:
  1.1505 +                return LINK_TYPE_ETHERNET;
  1.1506 +            case ConnectivityManager.TYPE_WIFI:
  1.1507 +                return LINK_TYPE_WIFI;
  1.1508 +            case ConnectivityManager.TYPE_WIMAX:
  1.1509 +                return LINK_TYPE_WIMAX;
  1.1510 +            case ConnectivityManager.TYPE_MOBILE:
  1.1511 +                break; // We will handle sub-types after the switch.
  1.1512 +            default:
  1.1513 +                Log.w(LOGTAG, "Ignoring the current network type.");
  1.1514 +                return LINK_TYPE_UNKNOWN;
  1.1515 +        }
  1.1516 +
  1.1517 +        TelephonyManager tm = (TelephonyManager)
  1.1518 +            getContext().getSystemService(Context.TELEPHONY_SERVICE);
  1.1519 +        if (tm == null) {
  1.1520 +            Log.e(LOGTAG, "Telephony service does not exist");
  1.1521 +            return LINK_TYPE_UNKNOWN;
  1.1522 +        }
  1.1523 +
  1.1524 +        switch (tm.getNetworkType()) {
  1.1525 +            case TelephonyManager.NETWORK_TYPE_IDEN:
  1.1526 +            case TelephonyManager.NETWORK_TYPE_CDMA:
  1.1527 +            case TelephonyManager.NETWORK_TYPE_GPRS:
  1.1528 +                return LINK_TYPE_2G;
  1.1529 +            case TelephonyManager.NETWORK_TYPE_1xRTT:
  1.1530 +            case TelephonyManager.NETWORK_TYPE_EDGE:
  1.1531 +                return LINK_TYPE_2G; // 2.5G
  1.1532 +            case TelephonyManager.NETWORK_TYPE_UMTS:
  1.1533 +            case TelephonyManager.NETWORK_TYPE_EVDO_0:
  1.1534 +                return LINK_TYPE_3G;
  1.1535 +            case TelephonyManager.NETWORK_TYPE_HSPA:
  1.1536 +            case TelephonyManager.NETWORK_TYPE_HSDPA:
  1.1537 +            case TelephonyManager.NETWORK_TYPE_HSUPA:
  1.1538 +            case TelephonyManager.NETWORK_TYPE_EVDO_A:
  1.1539 +            case TelephonyManager.NETWORK_TYPE_EVDO_B:
  1.1540 +            case TelephonyManager.NETWORK_TYPE_EHRPD:
  1.1541 +                return LINK_TYPE_3G; // 3.5G
  1.1542 +            case TelephonyManager.NETWORK_TYPE_HSPAP:
  1.1543 +                return LINK_TYPE_3G; // 3.75G
  1.1544 +            case TelephonyManager.NETWORK_TYPE_LTE:
  1.1545 +                return LINK_TYPE_4G; // 3.9G
  1.1546 +            case TelephonyManager.NETWORK_TYPE_UNKNOWN:
  1.1547 +            default:
  1.1548 +                Log.w(LOGTAG, "Connected to an unknown mobile network!");
  1.1549 +                return LINK_TYPE_UNKNOWN;
  1.1550 +        }
  1.1551 +    }
  1.1552 +
  1.1553 +    @WrapElementForJNI(stubName = "GetSystemColoursWrapper")
  1.1554 +    public static int[] getSystemColors() {
  1.1555 +        // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
  1.1556 +        final int[] attrsAppearance = {
  1.1557 +            android.R.attr.textColor,
  1.1558 +            android.R.attr.textColorPrimary,
  1.1559 +            android.R.attr.textColorPrimaryInverse,
  1.1560 +            android.R.attr.textColorSecondary,
  1.1561 +            android.R.attr.textColorSecondaryInverse,
  1.1562 +            android.R.attr.textColorTertiary,
  1.1563 +            android.R.attr.textColorTertiaryInverse,
  1.1564 +            android.R.attr.textColorHighlight,
  1.1565 +            android.R.attr.colorForeground,
  1.1566 +            android.R.attr.colorBackground,
  1.1567 +            android.R.attr.panelColorForeground,
  1.1568 +            android.R.attr.panelColorBackground
  1.1569 +        };
  1.1570 +
  1.1571 +        int[] result = new int[attrsAppearance.length];
  1.1572 +
  1.1573 +        final ContextThemeWrapper contextThemeWrapper =
  1.1574 +            new ContextThemeWrapper(getContext(), android.R.style.TextAppearance);
  1.1575 +
  1.1576 +        final TypedArray appearance = contextThemeWrapper.getTheme().obtainStyledAttributes(attrsAppearance);
  1.1577 +
  1.1578 +        if (appearance != null) {
  1.1579 +            for (int i = 0; i < appearance.getIndexCount(); i++) {
  1.1580 +                int idx = appearance.getIndex(i);
  1.1581 +                int color = appearance.getColor(idx, 0);
  1.1582 +                result[idx] = color;
  1.1583 +            }
  1.1584 +            appearance.recycle();
  1.1585 +        }
  1.1586 +
  1.1587 +        return result;
  1.1588 +    }
  1.1589 +
  1.1590 +    @WrapElementForJNI
  1.1591 +    public static void killAnyZombies() {
  1.1592 +        GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() {
  1.1593 +            @Override
  1.1594 +            public boolean callback(int pid) {
  1.1595 +                if (pid != android.os.Process.myPid())
  1.1596 +                    android.os.Process.killProcess(pid);
  1.1597 +                return true;
  1.1598 +            }
  1.1599 +        };
  1.1600 +            
  1.1601 +        EnumerateGeckoProcesses(visitor);
  1.1602 +    }
  1.1603 +
  1.1604 +    public static boolean checkForGeckoProcs() {
  1.1605 +
  1.1606 +        class GeckoPidCallback implements GeckoProcessesVisitor {
  1.1607 +            public boolean otherPidExist = false;
  1.1608 +            @Override
  1.1609 +            public boolean callback(int pid) {
  1.1610 +                if (pid != android.os.Process.myPid()) {
  1.1611 +                    otherPidExist = true;
  1.1612 +                    return false;
  1.1613 +                }
  1.1614 +                return true;
  1.1615 +            }            
  1.1616 +        }
  1.1617 +        GeckoPidCallback visitor = new GeckoPidCallback();            
  1.1618 +        EnumerateGeckoProcesses(visitor);
  1.1619 +        return visitor.otherPidExist;
  1.1620 +    }
  1.1621 +
  1.1622 +    interface GeckoProcessesVisitor{
  1.1623 +        boolean callback(int pid);
  1.1624 +    }
  1.1625 +
  1.1626 +    private static void EnumerateGeckoProcesses(GeckoProcessesVisitor visiter) {
  1.1627 +        int pidColumn = -1;
  1.1628 +        int userColumn = -1;
  1.1629 +
  1.1630 +        try {
  1.1631 +            // run ps and parse its output
  1.1632 +            java.lang.Process ps = Runtime.getRuntime().exec("ps");
  1.1633 +            BufferedReader in = new BufferedReader(new InputStreamReader(ps.getInputStream()),
  1.1634 +                                                   2048);
  1.1635 +
  1.1636 +            String headerOutput = in.readLine();
  1.1637 +
  1.1638 +            // figure out the column offsets.  We only care about the pid and user fields
  1.1639 +            StringTokenizer st = new StringTokenizer(headerOutput);
  1.1640 +            
  1.1641 +            int tokenSoFar = 0;
  1.1642 +            while (st.hasMoreTokens()) {
  1.1643 +                String next = st.nextToken();
  1.1644 +                if (next.equalsIgnoreCase("PID"))
  1.1645 +                    pidColumn = tokenSoFar;
  1.1646 +                else if (next.equalsIgnoreCase("USER"))
  1.1647 +                    userColumn = tokenSoFar;
  1.1648 +                tokenSoFar++;
  1.1649 +            }
  1.1650 +
  1.1651 +            // alright, the rest are process entries.
  1.1652 +            String psOutput = null;
  1.1653 +            while ((psOutput = in.readLine()) != null) {
  1.1654 +                String[] split = psOutput.split("\\s+");
  1.1655 +                if (split.length <= pidColumn || split.length <= userColumn)
  1.1656 +                    continue;
  1.1657 +                int uid = android.os.Process.getUidForName(split[userColumn]);
  1.1658 +                if (uid == android.os.Process.myUid() &&
  1.1659 +                    !split[split.length - 1].equalsIgnoreCase("ps")) {
  1.1660 +                    int pid = Integer.parseInt(split[pidColumn]);
  1.1661 +                    boolean keepGoing = visiter.callback(pid);
  1.1662 +                    if (keepGoing == false)
  1.1663 +                        break;
  1.1664 +                }
  1.1665 +            }
  1.1666 +            in.close();
  1.1667 +        }
  1.1668 +        catch (Exception e) {
  1.1669 +            Log.w(LOGTAG, "Failed to enumerate Gecko processes.",  e);
  1.1670 +        }
  1.1671 +    }
  1.1672 +
  1.1673 +    public static void waitForAnotherGeckoProc(){
  1.1674 +        int countdown = 40;
  1.1675 +        while (!checkForGeckoProcs() &&  --countdown > 0) {
  1.1676 +            try {
  1.1677 +                Thread.sleep(100);
  1.1678 +            } catch (InterruptedException ie) {}
  1.1679 +        }
  1.1680 +    }
  1.1681 +    public static String getAppNameByPID(int pid) {
  1.1682 +        BufferedReader cmdlineReader = null;
  1.1683 +        String path = "/proc/" + pid + "/cmdline";
  1.1684 +        try {
  1.1685 +            File cmdlineFile = new File(path);
  1.1686 +            if (!cmdlineFile.exists())
  1.1687 +                return "";
  1.1688 +            cmdlineReader = new BufferedReader(new FileReader(cmdlineFile));
  1.1689 +            return cmdlineReader.readLine().trim();
  1.1690 +        } catch (Exception ex) {
  1.1691 +            return "";
  1.1692 +        } finally {
  1.1693 +            if (null != cmdlineReader) {
  1.1694 +                try {
  1.1695 +                    cmdlineReader.close();
  1.1696 +                } catch (Exception e) {}
  1.1697 +            }
  1.1698 +        }
  1.1699 +    }
  1.1700 +
  1.1701 +    public static void listOfOpenFiles() {
  1.1702 +        int pidColumn = -1;
  1.1703 +        int nameColumn = -1;
  1.1704 +
  1.1705 +        try {
  1.1706 +            String filter = GeckoProfile.get(getContext()).getDir().toString();
  1.1707 +            Log.i(LOGTAG, "[OPENFILE] Filter: " + filter);
  1.1708 +
  1.1709 +            // run lsof and parse its output
  1.1710 +            java.lang.Process lsof = Runtime.getRuntime().exec("lsof");
  1.1711 +            BufferedReader in = new BufferedReader(new InputStreamReader(lsof.getInputStream()), 2048);
  1.1712 +
  1.1713 +            String headerOutput = in.readLine();
  1.1714 +            StringTokenizer st = new StringTokenizer(headerOutput);
  1.1715 +            int token = 0;
  1.1716 +            while (st.hasMoreTokens()) {
  1.1717 +                String next = st.nextToken();
  1.1718 +                if (next.equalsIgnoreCase("PID"))
  1.1719 +                    pidColumn = token;
  1.1720 +                else if (next.equalsIgnoreCase("NAME"))
  1.1721 +                    nameColumn = token;
  1.1722 +                token++;
  1.1723 +            }
  1.1724 +
  1.1725 +            // alright, the rest are open file entries.
  1.1726 +            Map<Integer, String> pidNameMap = new TreeMap<Integer, String>();
  1.1727 +            String output = null;
  1.1728 +            while ((output = in.readLine()) != null) {
  1.1729 +                String[] split = output.split("\\s+");
  1.1730 +                if (split.length <= pidColumn || split.length <= nameColumn)
  1.1731 +                    continue;
  1.1732 +                Integer pid = new Integer(split[pidColumn]);
  1.1733 +                String name = pidNameMap.get(pid);
  1.1734 +                if (name == null) {
  1.1735 +                    name = getAppNameByPID(pid.intValue());
  1.1736 +                    pidNameMap.put(pid, name);
  1.1737 +                }
  1.1738 +                String file = split[nameColumn];
  1.1739 +                if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter))
  1.1740 +                    Log.i(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file);
  1.1741 +            }
  1.1742 +            in.close();
  1.1743 +        } catch (Exception e) { }
  1.1744 +    }
  1.1745 +
  1.1746 +    @WrapElementForJNI
  1.1747 +    public static void scanMedia(String aFile, String aMimeType) {
  1.1748 +        // If the platform didn't give us a mimetype, try to guess one from the filename
  1.1749 +        if (TextUtils.isEmpty(aMimeType)) {
  1.1750 +            int extPosition = aFile.lastIndexOf(".");
  1.1751 +            if (extPosition > 0 && extPosition < aFile.length() - 1) {
  1.1752 +                aMimeType = getMimeTypeFromExtension(aFile.substring(extPosition+1));
  1.1753 +            }
  1.1754 +        }
  1.1755 +
  1.1756 +        Context context = getContext();
  1.1757 +        GeckoMediaScannerClient.startScan(context, aFile, aMimeType);
  1.1758 +    }
  1.1759 +
  1.1760 +    @WrapElementForJNI(stubName = "GetIconForExtensionWrapper")
  1.1761 +    public static byte[] getIconForExtension(String aExt, int iconSize) {
  1.1762 +        try {
  1.1763 +            if (iconSize <= 0)
  1.1764 +                iconSize = 16;
  1.1765 +
  1.1766 +            if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.')
  1.1767 +                aExt = aExt.substring(1);
  1.1768 +
  1.1769 +            PackageManager pm = getContext().getPackageManager();
  1.1770 +            Drawable icon = getDrawableForExtension(pm, aExt);
  1.1771 +            if (icon == null) {
  1.1772 +                // Use a generic icon
  1.1773 +                icon = pm.getDefaultActivityIcon();
  1.1774 +            }
  1.1775 +
  1.1776 +            Bitmap bitmap = ((BitmapDrawable)icon).getBitmap();
  1.1777 +            if (bitmap.getWidth() != iconSize || bitmap.getHeight() != iconSize)
  1.1778 +                bitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
  1.1779 +
  1.1780 +            ByteBuffer buf = ByteBuffer.allocate(iconSize * iconSize * 4);
  1.1781 +            bitmap.copyPixelsToBuffer(buf);
  1.1782 +
  1.1783 +            return buf.array();
  1.1784 +        }
  1.1785 +        catch (Exception e) {
  1.1786 +            Log.w(LOGTAG, "getIconForExtension failed.",  e);
  1.1787 +            return null;
  1.1788 +        }
  1.1789 +    }
  1.1790 +
  1.1791 +    private static String getMimeTypeFromExtension(String ext) {
  1.1792 +        final MimeTypeMap mtm = MimeTypeMap.getSingleton();
  1.1793 +        return mtm.getMimeTypeFromExtension(ext);
  1.1794 +    }
  1.1795 +    
  1.1796 +    private static Drawable getDrawableForExtension(PackageManager pm, String aExt) {
  1.1797 +        Intent intent = new Intent(Intent.ACTION_VIEW);
  1.1798 +        final String mimeType = getMimeTypeFromExtension(aExt);
  1.1799 +        if (mimeType != null && mimeType.length() > 0)
  1.1800 +            intent.setType(mimeType);
  1.1801 +        else
  1.1802 +            return null;
  1.1803 +
  1.1804 +        List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
  1.1805 +        if (list.size() == 0)
  1.1806 +            return null;
  1.1807 +
  1.1808 +        ResolveInfo resolveInfo = list.get(0);
  1.1809 +
  1.1810 +        if (resolveInfo == null)
  1.1811 +            return null;
  1.1812 +
  1.1813 +        ActivityInfo activityInfo = resolveInfo.activityInfo;
  1.1814 +
  1.1815 +        return activityInfo.loadIcon(pm);
  1.1816 +    }
  1.1817 +
  1.1818 +    @WrapElementForJNI
  1.1819 +    public static boolean getShowPasswordSetting() {
  1.1820 +        try {
  1.1821 +            int showPassword =
  1.1822 +                Settings.System.getInt(getContext().getContentResolver(),
  1.1823 +                                       Settings.System.TEXT_SHOW_PASSWORD, 1);
  1.1824 +            return (showPassword > 0);
  1.1825 +        }
  1.1826 +        catch (Exception e) {
  1.1827 +            return true;
  1.1828 +        }
  1.1829 +    }
  1.1830 +
  1.1831 +    @WrapElementForJNI(stubName = "AddPluginViewWrapper")
  1.1832 +    public static void addPluginView(View view,
  1.1833 +                                     float x, float y,
  1.1834 +                                     float w, float h,
  1.1835 +                                     boolean isFullScreen) {
  1.1836 +        if (getGeckoInterface() != null)
  1.1837 +             getGeckoInterface().addPluginView(view, new RectF(x, y, x + w, y + h), isFullScreen);
  1.1838 +    }
  1.1839 +
  1.1840 +    @WrapElementForJNI
  1.1841 +    public static void removePluginView(View view, boolean isFullScreen) {
  1.1842 +        if (getGeckoInterface() != null)
  1.1843 +            getGeckoInterface().removePluginView(view, isFullScreen);
  1.1844 +    }
  1.1845 +
  1.1846 +    /**
  1.1847 +     * A plugin that wish to be loaded in the WebView must provide this permission
  1.1848 +     * in their AndroidManifest.xml.
  1.1849 +     */
  1.1850 +    public static final String PLUGIN_ACTION = "android.webkit.PLUGIN";
  1.1851 +    public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN";
  1.1852 +
  1.1853 +    private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/";
  1.1854 +
  1.1855 +    private static final String PLUGIN_TYPE = "type";
  1.1856 +    private static final String TYPE_NATIVE = "native";
  1.1857 +    static public ArrayList<PackageInfo> mPackageInfoCache = new ArrayList<PackageInfo>();
  1.1858 +
  1.1859 +    // Returns null if plugins are blocked on the device.
  1.1860 +    static String[] getPluginDirectories() {
  1.1861 +
  1.1862 +        // An awful hack to detect Tegra devices. Easiest way to do it without spinning up a EGL context.
  1.1863 +        boolean isTegra = (new File("/system/lib/hw/gralloc.tegra.so")).exists() ||
  1.1864 +                          (new File("/system/lib/hw/gralloc.tegra3.so")).exists();
  1.1865 +        if (isTegra) {
  1.1866 +            // disable Flash on Tegra ICS with CM9 and other custom firmware (bug 736421)
  1.1867 +            File vfile = new File("/proc/version");
  1.1868 +            FileReader vreader = null;
  1.1869 +            try {
  1.1870 +                if (vfile.canRead()) {
  1.1871 +                    vreader = new FileReader(vfile);
  1.1872 +                    String version = new BufferedReader(vreader).readLine();
  1.1873 +                    if (version.indexOf("CM9") != -1 ||
  1.1874 +                        version.indexOf("cyanogen") != -1 ||
  1.1875 +                        version.indexOf("Nova") != -1)
  1.1876 +                    {
  1.1877 +                        Log.w(LOGTAG, "Blocking plugins because of Tegra 2 + unofficial ICS bug (bug 736421)");
  1.1878 +                        return null;
  1.1879 +                    }
  1.1880 +                }
  1.1881 +            } catch (IOException ex) {
  1.1882 +                // nothing
  1.1883 +            } finally {
  1.1884 +                try {
  1.1885 +                    if (vreader != null) {
  1.1886 +                        vreader.close();
  1.1887 +                    }
  1.1888 +                } catch (IOException ex) {
  1.1889 +                    // nothing
  1.1890 +                }
  1.1891 +            }
  1.1892 +
  1.1893 +            // disable on KitKat (bug 957694)
  1.1894 +            if (Build.VERSION.SDK_INT >= 19) {
  1.1895 +                Log.w(LOGTAG, "Blocking plugins because of Tegra (bug 957694)");
  1.1896 +                return null;
  1.1897 +            }
  1.1898 +        }
  1.1899 +
  1.1900 +        ArrayList<String> directories = new ArrayList<String>();
  1.1901 +        PackageManager pm = getContext().getPackageManager();
  1.1902 +        List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
  1.1903 +                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
  1.1904 +
  1.1905 +        synchronized(mPackageInfoCache) {
  1.1906 +
  1.1907 +            // clear the list of existing packageInfo objects
  1.1908 +            mPackageInfoCache.clear();
  1.1909 +
  1.1910 +
  1.1911 +            for (ResolveInfo info : plugins) {
  1.1912 +
  1.1913 +                // retrieve the plugin's service information
  1.1914 +                ServiceInfo serviceInfo = info.serviceInfo;
  1.1915 +                if (serviceInfo == null) {
  1.1916 +                    Log.w(LOGTAG, "Ignoring bad plugin.");
  1.1917 +                    continue;
  1.1918 +                }
  1.1919 +
  1.1920 +                // Blacklist HTC's flash lite.
  1.1921 +                // See bug #704516 - We're not quite sure what Flash Lite does,
  1.1922 +                // but loading it causes Flash to give errors and fail to draw.
  1.1923 +                if (serviceInfo.packageName.equals("com.htc.flashliteplugin")) {
  1.1924 +                    Log.w(LOGTAG, "Skipping HTC's flash lite plugin");
  1.1925 +                    continue;
  1.1926 +                }
  1.1927 +
  1.1928 +
  1.1929 +                // Retrieve information from the plugin's manifest.
  1.1930 +                PackageInfo pkgInfo;
  1.1931 +                try {
  1.1932 +                    pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
  1.1933 +                                    PackageManager.GET_PERMISSIONS
  1.1934 +                                    | PackageManager.GET_SIGNATURES);
  1.1935 +                } catch (Exception e) {
  1.1936 +                    Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
  1.1937 +                    continue;
  1.1938 +                }
  1.1939 +
  1.1940 +                if (pkgInfo == null) {
  1.1941 +                    Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Could not load package information.");
  1.1942 +                    continue;
  1.1943 +                }
  1.1944 +
  1.1945 +                /*
  1.1946 +                 * find the location of the plugin's shared library. The default
  1.1947 +                 * is to assume the app is either a user installed app or an
  1.1948 +                 * updated system app. In both of these cases the library is
  1.1949 +                 * stored in the app's data directory.
  1.1950 +                 */
  1.1951 +                String directory = pkgInfo.applicationInfo.dataDir + "/lib";
  1.1952 +                final int appFlags = pkgInfo.applicationInfo.flags;
  1.1953 +                final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM |
  1.1954 +                                               ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
  1.1955 +
  1.1956 +                // preloaded system app with no user updates
  1.1957 +                if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) {
  1.1958 +                    directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
  1.1959 +                }
  1.1960 +
  1.1961 +                // check if the plugin has the required permissions
  1.1962 +                String permissions[] = pkgInfo.requestedPermissions;
  1.1963 +                if (permissions == null) {
  1.1964 +                    Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission.");
  1.1965 +                    continue;
  1.1966 +                }
  1.1967 +                boolean permissionOk = false;
  1.1968 +                for (String permit : permissions) {
  1.1969 +                    if (PLUGIN_PERMISSION.equals(permit)) {
  1.1970 +                        permissionOk = true;
  1.1971 +                        break;
  1.1972 +                    }
  1.1973 +                }
  1.1974 +                if (!permissionOk) {
  1.1975 +                    Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission (2).");
  1.1976 +                    continue;
  1.1977 +                }
  1.1978 +
  1.1979 +                // check to ensure the plugin is properly signed
  1.1980 +                Signature signatures[] = pkgInfo.signatures;
  1.1981 +                if (signatures == null) {
  1.1982 +                    Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Not signed.");
  1.1983 +                    continue;
  1.1984 +                }
  1.1985 +
  1.1986 +                // determine the type of plugin from the manifest
  1.1987 +                if (serviceInfo.metaData == null) {
  1.1988 +                    Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no defined type.");
  1.1989 +                    continue;
  1.1990 +                }
  1.1991 +
  1.1992 +                String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
  1.1993 +                if (!TYPE_NATIVE.equals(pluginType)) {
  1.1994 +                    Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
  1.1995 +                    continue;
  1.1996 +                }
  1.1997 +
  1.1998 +                try {
  1.1999 +                    Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
  1.2000 +
  1.2001 +                    //TODO implement any requirements of the plugin class here!
  1.2002 +                    boolean classFound = true;
  1.2003 +
  1.2004 +                    if (!classFound) {
  1.2005 +                        Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
  1.2006 +                        continue;
  1.2007 +                    }
  1.2008 +
  1.2009 +                } catch (NameNotFoundException e) {
  1.2010 +                    Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
  1.2011 +                    continue;
  1.2012 +                } catch (ClassNotFoundException e) {
  1.2013 +                    Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
  1.2014 +                    continue;
  1.2015 +                }
  1.2016 +
  1.2017 +                // if all checks have passed then make the plugin available
  1.2018 +                mPackageInfoCache.add(pkgInfo);
  1.2019 +                directories.add(directory);
  1.2020 +            }
  1.2021 +        }
  1.2022 +
  1.2023 +        return directories.toArray(new String[directories.size()]);
  1.2024 +    }
  1.2025 +
  1.2026 +    static String getPluginPackage(String pluginLib) {
  1.2027 +
  1.2028 +        if (pluginLib == null || pluginLib.length() == 0) {
  1.2029 +            return null;
  1.2030 +        }
  1.2031 +
  1.2032 +        synchronized(mPackageInfoCache) {
  1.2033 +            for (PackageInfo pkgInfo : mPackageInfoCache) {
  1.2034 +                if (pluginLib.contains(pkgInfo.packageName)) {
  1.2035 +                    return pkgInfo.packageName;
  1.2036 +                }
  1.2037 +            }
  1.2038 +        }
  1.2039 +
  1.2040 +        return null;
  1.2041 +    }
  1.2042 +
  1.2043 +    static Class<?> getPluginClass(String packageName, String className)
  1.2044 +            throws NameNotFoundException, ClassNotFoundException {
  1.2045 +        Context pluginContext = getContext().createPackageContext(packageName,
  1.2046 +                Context.CONTEXT_INCLUDE_CODE |
  1.2047 +                Context.CONTEXT_IGNORE_SECURITY);
  1.2048 +        ClassLoader pluginCL = pluginContext.getClassLoader();
  1.2049 +        return pluginCL.loadClass(className);
  1.2050 +    }
  1.2051 +
  1.2052 +    @WrapElementForJNI(allowMultithread = true)
  1.2053 +    public static Class<?> loadPluginClass(String className, String libName) {
  1.2054 +        if (getGeckoInterface() == null)
  1.2055 +            return null;
  1.2056 +        try {
  1.2057 +            final String packageName = getPluginPackage(libName);
  1.2058 +            final int contextFlags = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY;
  1.2059 +            final Context pluginContext = getContext().createPackageContext(packageName, contextFlags);
  1.2060 +            return pluginContext.getClassLoader().loadClass(className);
  1.2061 +        } catch (java.lang.ClassNotFoundException cnfe) {
  1.2062 +            Log.w(LOGTAG, "Couldn't find plugin class " + className, cnfe);
  1.2063 +            return null;
  1.2064 +        } catch (android.content.pm.PackageManager.NameNotFoundException nnfe) {
  1.2065 +            Log.w(LOGTAG, "Couldn't find package.", nnfe);
  1.2066 +            return null;
  1.2067 +        }
  1.2068 +    }
  1.2069 +
  1.2070 +    private static ContextGetter sContextGetter;
  1.2071 +
  1.2072 +    @WrapElementForJNI(allowMultithread = true)
  1.2073 +    public static Context getContext() {
  1.2074 +        return sContextGetter.getContext();
  1.2075 +    }
  1.2076 +
  1.2077 +    public static void setContextGetter(ContextGetter cg) {
  1.2078 +        sContextGetter = cg;
  1.2079 +    }
  1.2080 +
  1.2081 +    public static SharedPreferences getSharedPreferences() {
  1.2082 +        if (sContextGetter == null) {
  1.2083 +            throw new IllegalStateException("No ContextGetter; cannot fetch prefs.");
  1.2084 +        }
  1.2085 +        return sContextGetter.getSharedPreferences();
  1.2086 +    }
  1.2087 +
  1.2088 +    public interface AppStateListener {
  1.2089 +        public void onPause();
  1.2090 +        public void onResume();
  1.2091 +        public void onOrientationChanged();
  1.2092 +    }
  1.2093 +
  1.2094 +    public interface GeckoInterface {
  1.2095 +        public GeckoProfile getProfile();
  1.2096 +        public PromptService getPromptService();
  1.2097 +        public Activity getActivity();
  1.2098 +        public String getDefaultUAString();
  1.2099 +        public LocationListener getLocationListener();
  1.2100 +        public SensorEventListener getSensorEventListener();
  1.2101 +        public void doRestart();
  1.2102 +        public void setFullScreen(boolean fullscreen);
  1.2103 +        public void addPluginView(View view, final RectF rect, final boolean isFullScreen);
  1.2104 +        public void removePluginView(final View view, final boolean isFullScreen);
  1.2105 +        public void enableCameraView();
  1.2106 +        public void disableCameraView();
  1.2107 +        public void addAppStateListener(AppStateListener listener);
  1.2108 +        public void removeAppStateListener(AppStateListener listener);
  1.2109 +        public View getCameraView();
  1.2110 +        public void notifyWakeLockChanged(String topic, String state);
  1.2111 +        public FormAssistPopup getFormAssistPopup();
  1.2112 +        public boolean areTabsShown();
  1.2113 +        public AbsoluteLayout getPluginContainer();
  1.2114 +        public void notifyCheckUpdateResult(String result);
  1.2115 +        public boolean hasTabsSideBar();
  1.2116 +        public void invalidateOptionsMenu();
  1.2117 +    };
  1.2118 +
  1.2119 +    private static GeckoInterface sGeckoInterface;
  1.2120 +
  1.2121 +    public static GeckoInterface getGeckoInterface() {
  1.2122 +        return sGeckoInterface;
  1.2123 +    }
  1.2124 +
  1.2125 +    public static void setGeckoInterface(GeckoInterface aGeckoInterface) {
  1.2126 +        sGeckoInterface = aGeckoInterface;
  1.2127 +    }
  1.2128 +
  1.2129 +    public static android.hardware.Camera sCamera = null;
  1.2130 +
  1.2131 +    static native void cameraCallbackBridge(byte[] data);
  1.2132 +
  1.2133 +    static int kPreferedFps = 25;
  1.2134 +    static byte[] sCameraBuffer = null;
  1.2135 +
  1.2136 +
  1.2137 +    @WrapElementForJNI(stubName = "InitCameraWrapper")
  1.2138 +    static int[] initCamera(String aContentType, int aCamera, int aWidth, int aHeight) {
  1.2139 +        ThreadUtils.postToUiThread(new Runnable() {
  1.2140 +                @Override
  1.2141 +                public void run() {
  1.2142 +                    try {
  1.2143 +                        if (getGeckoInterface() != null)
  1.2144 +                            getGeckoInterface().enableCameraView();
  1.2145 +                    } catch (Exception e) {}
  1.2146 +                }
  1.2147 +            });
  1.2148 +
  1.2149 +        // [0] = 0|1 (failure/success)
  1.2150 +        // [1] = width
  1.2151 +        // [2] = height
  1.2152 +        // [3] = fps
  1.2153 +        int[] result = new int[4];
  1.2154 +        result[0] = 0;
  1.2155 +
  1.2156 +        if (Build.VERSION.SDK_INT >= 9) {
  1.2157 +            if (android.hardware.Camera.getNumberOfCameras() == 0)
  1.2158 +                return result;
  1.2159 +        }
  1.2160 +
  1.2161 +        try {
  1.2162 +            // no front/back camera before API level 9
  1.2163 +            if (Build.VERSION.SDK_INT >= 9)
  1.2164 +                sCamera = android.hardware.Camera.open(aCamera);
  1.2165 +            else
  1.2166 +                sCamera = android.hardware.Camera.open();
  1.2167 +
  1.2168 +            android.hardware.Camera.Parameters params = sCamera.getParameters();
  1.2169 +            params.setPreviewFormat(ImageFormat.NV21);
  1.2170 +
  1.2171 +            // use the preview fps closest to 25 fps.
  1.2172 +            int fpsDelta = 1000;
  1.2173 +            try {
  1.2174 +                Iterator<Integer> it = params.getSupportedPreviewFrameRates().iterator();
  1.2175 +                while (it.hasNext()) {
  1.2176 +                    int nFps = it.next();
  1.2177 +                    if (Math.abs(nFps - kPreferedFps) < fpsDelta) {
  1.2178 +                        fpsDelta = Math.abs(nFps - kPreferedFps);
  1.2179 +                        params.setPreviewFrameRate(nFps);
  1.2180 +                    }
  1.2181 +                }
  1.2182 +            } catch(Exception e) {
  1.2183 +                params.setPreviewFrameRate(kPreferedFps);
  1.2184 +            }
  1.2185 +
  1.2186 +            // set up the closest preview size available
  1.2187 +            Iterator<android.hardware.Camera.Size> sit = params.getSupportedPreviewSizes().iterator();
  1.2188 +            int sizeDelta = 10000000;
  1.2189 +            int bufferSize = 0;
  1.2190 +            while (sit.hasNext()) {
  1.2191 +                android.hardware.Camera.Size size = sit.next();
  1.2192 +                if (Math.abs(size.width * size.height - aWidth * aHeight) < sizeDelta) {
  1.2193 +                    sizeDelta = Math.abs(size.width * size.height - aWidth * aHeight);
  1.2194 +                    params.setPreviewSize(size.width, size.height);
  1.2195 +                    bufferSize = size.width * size.height;
  1.2196 +                }
  1.2197 +            }
  1.2198 +
  1.2199 +            try {
  1.2200 +                if (getGeckoInterface() != null) {
  1.2201 +                    View cameraView = getGeckoInterface().getCameraView();
  1.2202 +                    if (cameraView instanceof SurfaceView) {
  1.2203 +                        sCamera.setPreviewDisplay(((SurfaceView)cameraView).getHolder());
  1.2204 +                    } else if (cameraView instanceof TextureView) {
  1.2205 +                        sCamera.setPreviewTexture(((TextureView)cameraView).getSurfaceTexture());
  1.2206 +                    }
  1.2207 +                }
  1.2208 +            } catch(IOException e) {
  1.2209 +                Log.w(LOGTAG, "Error setPreviewXXX:", e);
  1.2210 +            } catch(RuntimeException e) {
  1.2211 +                Log.w(LOGTAG, "Error setPreviewXXX:", e);
  1.2212 +            }
  1.2213 +
  1.2214 +            sCamera.setParameters(params);
  1.2215 +            sCameraBuffer = new byte[(bufferSize * 12) / 8];
  1.2216 +            sCamera.addCallbackBuffer(sCameraBuffer);
  1.2217 +            sCamera.setPreviewCallbackWithBuffer(new android.hardware.Camera.PreviewCallback() {
  1.2218 +                @Override
  1.2219 +                public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
  1.2220 +                    cameraCallbackBridge(data);
  1.2221 +                    if (sCamera != null)
  1.2222 +                        sCamera.addCallbackBuffer(sCameraBuffer);
  1.2223 +                }
  1.2224 +            });
  1.2225 +            sCamera.startPreview();
  1.2226 +            params = sCamera.getParameters();
  1.2227 +            result[0] = 1;
  1.2228 +            result[1] = params.getPreviewSize().width;
  1.2229 +            result[2] = params.getPreviewSize().height;
  1.2230 +            result[3] = params.getPreviewFrameRate();
  1.2231 +        } catch(RuntimeException e) {
  1.2232 +            Log.w(LOGTAG, "initCamera RuntimeException.", e);
  1.2233 +            result[0] = result[1] = result[2] = result[3] = 0;
  1.2234 +        }
  1.2235 +        return result;
  1.2236 +    }
  1.2237 +
  1.2238 +    @WrapElementForJNI
  1.2239 +    static synchronized void closeCamera() {
  1.2240 +        ThreadUtils.postToUiThread(new Runnable() {
  1.2241 +                @Override
  1.2242 +                public void run() {
  1.2243 +                    try {
  1.2244 +                        if (getGeckoInterface() != null)
  1.2245 +                            getGeckoInterface().disableCameraView();
  1.2246 +                    } catch (Exception e) {}
  1.2247 +                }
  1.2248 +            });
  1.2249 +        if (sCamera != null) {
  1.2250 +            sCamera.stopPreview();
  1.2251 +            sCamera.release();
  1.2252 +            sCamera = null;
  1.2253 +            sCameraBuffer = null;
  1.2254 +        }
  1.2255 +    }
  1.2256 +
  1.2257 +    /**
  1.2258 +     * Adds a listener for a gecko event.
  1.2259 +     * This method is thread-safe and may be called at any time. In particular, calling it
  1.2260 +     * with an event that is currently being processed has the properly-defined behaviour that
  1.2261 +     * any added listeners will not be invoked on the event currently being processed, but
  1.2262 +     * will be invoked on future events of that type.
  1.2263 +     */
  1.2264 +    @RobocopTarget
  1.2265 +    public static void registerEventListener(String event, GeckoEventListener listener) {
  1.2266 +        sEventDispatcher.registerEventListener(event, listener);
  1.2267 +    }
  1.2268 +
  1.2269 +    public static EventDispatcher getEventDispatcher() {
  1.2270 +        return sEventDispatcher;
  1.2271 +    }
  1.2272 +
  1.2273 +    /**
  1.2274 +     * Remove a previously-registered listener for a gecko event.
  1.2275 +     * This method is thread-safe and may be called at any time. In particular, calling it
  1.2276 +     * with an event that is currently being processed has the properly-defined behaviour that
  1.2277 +     * any removed listeners will still be invoked on the event currently being processed, but
  1.2278 +     * will not be invoked on future events of that type.
  1.2279 +     */
  1.2280 +    @RobocopTarget
  1.2281 +    public static void unregisterEventListener(String event, GeckoEventListener listener) {
  1.2282 +        sEventDispatcher.unregisterEventListener(event, listener);
  1.2283 +    }
  1.2284 +
  1.2285 +    /*
  1.2286 +     * Battery API related methods.
  1.2287 +     */
  1.2288 +    @WrapElementForJNI
  1.2289 +    public static void enableBatteryNotifications() {
  1.2290 +        GeckoBatteryManager.enableNotifications();
  1.2291 +    }
  1.2292 +
  1.2293 +    @WrapElementForJNI(stubName = "HandleGeckoMessageWrapper")
  1.2294 +    public static void handleGeckoMessage(final NativeJSContainer message) {
  1.2295 +        sEventDispatcher.dispatchEvent(message);
  1.2296 +        message.dispose();
  1.2297 +    }
  1.2298 +
  1.2299 +    @WrapElementForJNI
  1.2300 +    public static void disableBatteryNotifications() {
  1.2301 +        GeckoBatteryManager.disableNotifications();
  1.2302 +    }
  1.2303 +
  1.2304 +    @WrapElementForJNI(stubName = "GetCurrentBatteryInformationWrapper")
  1.2305 +    public static double[] getCurrentBatteryInformation() {
  1.2306 +        return GeckoBatteryManager.getCurrentInformation();
  1.2307 +    }
  1.2308 +
  1.2309 +    @WrapElementForJNI(stubName = "CheckURIVisited")
  1.2310 +    static void checkUriVisited(String uri) {
  1.2311 +        GlobalHistory.getInstance().checkUriVisited(uri);
  1.2312 +    }
  1.2313 +
  1.2314 +    @WrapElementForJNI(stubName = "MarkURIVisited")
  1.2315 +    static void markUriVisited(final String uri) {
  1.2316 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2317 +            @Override
  1.2318 +            public void run() {
  1.2319 +                GlobalHistory.getInstance().add(uri);
  1.2320 +            }
  1.2321 +        });
  1.2322 +    }
  1.2323 +
  1.2324 +    @WrapElementForJNI(stubName = "SetURITitle")
  1.2325 +    static void setUriTitle(final String uri, final String title) {
  1.2326 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2327 +            @Override
  1.2328 +            public void run() {
  1.2329 +                GlobalHistory.getInstance().update(uri, title);
  1.2330 +            }
  1.2331 +        });
  1.2332 +    }
  1.2333 +
  1.2334 +    @WrapElementForJNI
  1.2335 +    static void hideProgressDialog() {
  1.2336 +        // unused stub
  1.2337 +    }
  1.2338 +
  1.2339 +    /*
  1.2340 +     * WebSMS related methods.
  1.2341 +     */
  1.2342 +    @WrapElementForJNI(stubName = "SendMessageWrapper")
  1.2343 +    public static void sendMessage(String aNumber, String aMessage, int aRequestId) {
  1.2344 +        if (SmsManager.getInstance() == null) {
  1.2345 +            return;
  1.2346 +        }
  1.2347 +
  1.2348 +        SmsManager.getInstance().send(aNumber, aMessage, aRequestId);
  1.2349 +    }
  1.2350 +
  1.2351 +    @WrapElementForJNI(stubName = "GetMessageWrapper")
  1.2352 +    public static void getMessage(int aMessageId, int aRequestId) {
  1.2353 +        if (SmsManager.getInstance() == null) {
  1.2354 +            return;
  1.2355 +        }
  1.2356 +
  1.2357 +        SmsManager.getInstance().getMessage(aMessageId, aRequestId);
  1.2358 +    }
  1.2359 +
  1.2360 +    @WrapElementForJNI(stubName = "DeleteMessageWrapper")
  1.2361 +    public static void deleteMessage(int aMessageId, int aRequestId) {
  1.2362 +        if (SmsManager.getInstance() == null) {
  1.2363 +            return;
  1.2364 +        }
  1.2365 +
  1.2366 +        SmsManager.getInstance().deleteMessage(aMessageId, aRequestId);
  1.2367 +    }
  1.2368 +
  1.2369 +    @WrapElementForJNI(stubName = "CreateMessageListWrapper")
  1.2370 +    public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
  1.2371 +        if (SmsManager.getInstance() == null) {
  1.2372 +            return;
  1.2373 +        }
  1.2374 +
  1.2375 +        SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId);
  1.2376 +    }
  1.2377 +
  1.2378 +    @WrapElementForJNI(stubName = "GetNextMessageInListWrapper")
  1.2379 +    public static void getNextMessageInList(int aListId, int aRequestId) {
  1.2380 +        if (SmsManager.getInstance() == null) {
  1.2381 +            return;
  1.2382 +        }
  1.2383 +
  1.2384 +        SmsManager.getInstance().getNextMessageInList(aListId, aRequestId);
  1.2385 +    }
  1.2386 +
  1.2387 +    @WrapElementForJNI
  1.2388 +    public static void clearMessageList(int aListId) {
  1.2389 +        if (SmsManager.getInstance() == null) {
  1.2390 +            return;
  1.2391 +        }
  1.2392 +
  1.2393 +        SmsManager.getInstance().clearMessageList(aListId);
  1.2394 +    }
  1.2395 +
  1.2396 +    /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */
  1.2397 +    @WrapElementForJNI
  1.2398 +    @RobocopTarget
  1.2399 +    public static boolean isTablet() {
  1.2400 +        return HardwareUtils.isTablet();
  1.2401 +    }
  1.2402 +
  1.2403 +    public static void viewSizeChanged() {
  1.2404 +        LayerView v = getLayerView();
  1.2405 +        if (v != null && v.isIMEEnabled()) {
  1.2406 +            sendEventToGecko(GeckoEvent.createBroadcastEvent(
  1.2407 +                    "ScrollTo:FocusedInput", ""));
  1.2408 +        }
  1.2409 +    }
  1.2410 +
  1.2411 +    @WrapElementForJNI(stubName = "GetCurrentNetworkInformationWrapper")
  1.2412 +    public static double[] getCurrentNetworkInformation() {
  1.2413 +        return GeckoNetworkManager.getInstance().getCurrentInformation();
  1.2414 +    }
  1.2415 +
  1.2416 +    @WrapElementForJNI
  1.2417 +    public static void enableNetworkNotifications() {
  1.2418 +        GeckoNetworkManager.getInstance().enableNotifications();
  1.2419 +    }
  1.2420 +
  1.2421 +    @WrapElementForJNI
  1.2422 +    public static void disableNetworkNotifications() {
  1.2423 +        GeckoNetworkManager.getInstance().disableNotifications();
  1.2424 +    }
  1.2425 +
  1.2426 +    // values taken from android's Base64
  1.2427 +    public static final int BASE64_DEFAULT = 0;
  1.2428 +    public static final int BASE64_URL_SAFE = 8;
  1.2429 +
  1.2430 +    /**
  1.2431 +     * taken from http://www.source-code.biz/base64coder/java/Base64Coder.java.txt and modified (MIT License)
  1.2432 +     */
  1.2433 +    // Mapping table from 6-bit nibbles to Base64 characters.
  1.2434 +    private static final byte[] map1 = new byte[64];
  1.2435 +    private static final byte[] map1_urlsafe;
  1.2436 +    static {
  1.2437 +      int i=0;
  1.2438 +      for (byte c='A'; c<='Z'; c++) map1[i++] = c;
  1.2439 +      for (byte c='a'; c<='z'; c++) map1[i++] = c;
  1.2440 +      for (byte c='0'; c<='9'; c++) map1[i++] = c;
  1.2441 +      map1[i++] = '+'; map1[i++] = '/';
  1.2442 +      map1_urlsafe = map1.clone();
  1.2443 +      map1_urlsafe[62] = '-'; map1_urlsafe[63] = '_'; 
  1.2444 +    }
  1.2445 +
  1.2446 +    // Mapping table from Base64 characters to 6-bit nibbles.
  1.2447 +    private static final byte[] map2 = new byte[128];
  1.2448 +    static {
  1.2449 +        for (int i=0; i<map2.length; i++) map2[i] = -1;
  1.2450 +        for (int i=0; i<64; i++) map2[map1[i]] = (byte)i;
  1.2451 +        map2['-'] = (byte)62; map2['_'] = (byte)63;
  1.2452 +    }
  1.2453 +
  1.2454 +    final static byte EQUALS_ASCII = (byte) '=';
  1.2455 +
  1.2456 +    /**
  1.2457 +     * Encodes a byte array into Base64 format.
  1.2458 +     * No blanks or line breaks are inserted in the output.
  1.2459 +     * @param in    An array containing the data bytes to be encoded.
  1.2460 +     * @return      A character array containing the Base64 encoded data.
  1.2461 +     */
  1.2462 +    public static byte[] encodeBase64(byte[] in, int flags) {
  1.2463 +        if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.FROYO)
  1.2464 +            return Base64.encode(in, flags | Base64.NO_WRAP);
  1.2465 +        int oDataLen = (in.length*4+2)/3;       // output length without padding
  1.2466 +        int oLen = ((in.length+2)/3)*4;         // output length including padding
  1.2467 +        byte[] out = new byte[oLen];
  1.2468 +        int ip = 0;
  1.2469 +        int iEnd = in.length;
  1.2470 +        int op = 0;
  1.2471 +        byte[] toMap = ((flags & BASE64_URL_SAFE) == 0 ? map1 : map1_urlsafe);
  1.2472 +        while (ip < iEnd) {
  1.2473 +            int i0 = in[ip++] & 0xff;
  1.2474 +            int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
  1.2475 +            int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
  1.2476 +            int o0 = i0 >>> 2;
  1.2477 +            int o1 = ((i0 &   3) << 4) | (i1 >>> 4);
  1.2478 +            int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
  1.2479 +            int o3 = i2 & 0x3F;
  1.2480 +            out[op++] = toMap[o0];
  1.2481 +            out[op++] = toMap[o1];
  1.2482 +            out[op] = op < oDataLen ? toMap[o2] : EQUALS_ASCII; op++;
  1.2483 +            out[op] = op < oDataLen ? toMap[o3] : EQUALS_ASCII; op++;
  1.2484 +        }
  1.2485 +        return out; 
  1.2486 +    }
  1.2487 +
  1.2488 +    /**
  1.2489 +     * Decodes a byte array from Base64 format.
  1.2490 +     * No blanks or line breaks are allowed within the Base64 encoded input data.
  1.2491 +     * @param in    A character array containing the Base64 encoded data.
  1.2492 +     * @param iOff  Offset of the first character in <code>in</code> to be processed.
  1.2493 +     * @param iLen  Number of characters to process in <code>in</code>, starting at <code>iOff</code>.
  1.2494 +     * @return      An array containing the decoded data bytes.
  1.2495 +     * @throws      IllegalArgumentException If the input is not valid Base64 encoded data.
  1.2496 +     */
  1.2497 +    public static byte[] decodeBase64(byte[] in, int flags) {
  1.2498 +        if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.FROYO)
  1.2499 +            return Base64.decode(in, flags);
  1.2500 +        int iOff = 0;
  1.2501 +        int iLen = in.length;
  1.2502 +        if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4.");
  1.2503 +        while (iLen > 0 && in[iOff+iLen-1] == '=') iLen--;
  1.2504 +        int oLen = (iLen*3) / 4;
  1.2505 +        byte[] out = new byte[oLen];
  1.2506 +        int ip = iOff;
  1.2507 +        int iEnd = iOff + iLen;
  1.2508 +        int op = 0;
  1.2509 +        while (ip < iEnd) {
  1.2510 +            int i0 = in[ip++];
  1.2511 +            int i1 = in[ip++];
  1.2512 +            int i2 = ip < iEnd ? in[ip++] : 'A';
  1.2513 +            int i3 = ip < iEnd ? in[ip++] : 'A';
  1.2514 +            if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
  1.2515 +                throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
  1.2516 +            int b0 = map2[i0];
  1.2517 +            int b1 = map2[i1];
  1.2518 +            int b2 = map2[i2];
  1.2519 +            int b3 = map2[i3];
  1.2520 +            if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
  1.2521 +                throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
  1.2522 +            int o0 = ( b0       <<2) | (b1>>>4);
  1.2523 +            int o1 = ((b1 & 0xf)<<4) | (b2>>>2);
  1.2524 +            int o2 = ((b2 &   3)<<6) |  b3;
  1.2525 +            out[op++] = (byte)o0;
  1.2526 +            if (op<oLen) out[op++] = (byte)o1;
  1.2527 +            if (op<oLen) out[op++] = (byte)o2; }
  1.2528 +        return out; 
  1.2529 +    }
  1.2530 +
  1.2531 +    public static byte[] decodeBase64(String s, int flags) {
  1.2532 +        return decodeBase64(s.getBytes(), flags);
  1.2533 +    }
  1.2534 +
  1.2535 +    @WrapElementForJNI(stubName = "GetScreenOrientationWrapper")
  1.2536 +    public static short getScreenOrientation() {
  1.2537 +        return GeckoScreenOrientation.getInstance().getScreenOrientation().value;
  1.2538 +    }
  1.2539 +
  1.2540 +    @WrapElementForJNI
  1.2541 +    public static void enableScreenOrientationNotifications() {
  1.2542 +        GeckoScreenOrientation.getInstance().enableNotifications();
  1.2543 +    }
  1.2544 +
  1.2545 +    @WrapElementForJNI
  1.2546 +    public static void disableScreenOrientationNotifications() {
  1.2547 +        GeckoScreenOrientation.getInstance().disableNotifications();
  1.2548 +    }
  1.2549 +
  1.2550 +    @WrapElementForJNI
  1.2551 +    public static void lockScreenOrientation(int aOrientation) {
  1.2552 +        GeckoScreenOrientation.getInstance().lock(aOrientation);
  1.2553 +    }
  1.2554 +
  1.2555 +    @WrapElementForJNI
  1.2556 +    public static void unlockScreenOrientation() {
  1.2557 +        GeckoScreenOrientation.getInstance().unlock();
  1.2558 +    }
  1.2559 +
  1.2560 +    @WrapElementForJNI
  1.2561 +    public static boolean pumpMessageLoop() {
  1.2562 +        Handler geckoHandler = ThreadUtils.sGeckoHandler;
  1.2563 +        Message msg = getNextMessageFromQueue(ThreadUtils.sGeckoQueue);
  1.2564 +
  1.2565 +        if (msg == null)
  1.2566 +            return false;
  1.2567 +        if (msg.obj == geckoHandler && msg.getTarget() == geckoHandler) {
  1.2568 +            // Our "queue is empty" message; see runGecko()
  1.2569 +            msg.recycle();
  1.2570 +            return false;
  1.2571 +        }
  1.2572 +        if (msg.getTarget() == null) 
  1.2573 +            Looper.myLooper().quit();
  1.2574 +        else
  1.2575 +            msg.getTarget().dispatchMessage(msg);
  1.2576 +        msg.recycle();
  1.2577 +        return true;
  1.2578 +    }
  1.2579 +
  1.2580 +    @WrapElementForJNI
  1.2581 +    public static void notifyWakeLockChanged(String topic, String state) {
  1.2582 +        if (getGeckoInterface() != null)
  1.2583 +            getGeckoInterface().notifyWakeLockChanged(topic, state);
  1.2584 +    }
  1.2585 +
  1.2586 +    @WrapElementForJNI
  1.2587 +    public static void registerSurfaceTextureFrameListener(Object surfaceTexture, final int id) {
  1.2588 +        ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
  1.2589 +            @Override
  1.2590 +            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
  1.2591 +                GeckoAppShell.onSurfaceTextureFrameAvailable(surfaceTexture, id);
  1.2592 +            }
  1.2593 +        });
  1.2594 +    }
  1.2595 +
  1.2596 +    @WrapElementForJNI(allowMultithread = true)
  1.2597 +    public static void unregisterSurfaceTextureFrameListener(Object surfaceTexture) {
  1.2598 +        ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(null);
  1.2599 +    }
  1.2600 +
  1.2601 +    @WrapElementForJNI
  1.2602 +    public static boolean unlockProfile() {
  1.2603 +        // Try to kill any zombie Fennec's that might be running
  1.2604 +        GeckoAppShell.killAnyZombies();
  1.2605 +
  1.2606 +        // Then force unlock this profile
  1.2607 +        if (getGeckoInterface() != null) {
  1.2608 +            GeckoProfile profile = getGeckoInterface().getProfile();
  1.2609 +            File lock = profile.getFile(".parentlock");
  1.2610 +            return lock.exists() && lock.delete();
  1.2611 +        }
  1.2612 +        return false;
  1.2613 +    }
  1.2614 +
  1.2615 +    @WrapElementForJNI(stubName = "GetProxyForURIWrapper")
  1.2616 +    public static String getProxyForURI(String spec, String scheme, String host, int port) {
  1.2617 +        final ProxySelector ps = new ProxySelector();
  1.2618 +
  1.2619 +        Proxy proxy = ps.select(scheme, host);
  1.2620 +        if (Proxy.NO_PROXY.equals(proxy)) {
  1.2621 +            return "DIRECT";
  1.2622 +        }
  1.2623 +        
  1.2624 +        switch (proxy.type()) {
  1.2625 +            case HTTP:
  1.2626 +                return "PROXY " + proxy.address().toString();
  1.2627 +            case SOCKS:
  1.2628 +                return "SOCKS " + proxy.address().toString();
  1.2629 +        }
  1.2630 +
  1.2631 +        return "DIRECT";
  1.2632 +    }
  1.2633 +
  1.2634 +    /* Downloads the uri pointed to by a share intent, and alters the intent to point to the locally stored file.
  1.2635 +     */
  1.2636 +    public static void downloadImageForIntent(final Intent intent) {
  1.2637 +        final String src = intent.getStringExtra(Intent.EXTRA_TEXT);
  1.2638 +        final File dir = GeckoApp.getTempDirectory();
  1.2639 +
  1.2640 +        if (dir == null) {
  1.2641 +            showImageShareFailureToast();
  1.2642 +            return;
  1.2643 +        }
  1.2644 +
  1.2645 +        GeckoApp.deleteTempFiles();
  1.2646 +
  1.2647 +        String type = intent.getType();
  1.2648 +        OutputStream os = null;
  1.2649 +        try {
  1.2650 +            // Create a temporary file for the image
  1.2651 +            if (src.startsWith("data:")) {
  1.2652 +                final int dataStart = src.indexOf(",");
  1.2653 +
  1.2654 +                String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type);
  1.2655 +
  1.2656 +                // If we weren't given an explicit mimetype, try to dig one out of the data uri.
  1.2657 +                if (TextUtils.isEmpty(extension) && dataStart > 5) {
  1.2658 +                    type = src.substring(5, dataStart).replace(";base64", "");
  1.2659 +                    extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type);
  1.2660 +                }
  1.2661 +
  1.2662 +                final File imageFile = File.createTempFile("image", "." + extension, dir);
  1.2663 +                os = new FileOutputStream(imageFile);
  1.2664 +
  1.2665 +                byte[] buf = Base64.decode(src.substring(dataStart + 1), Base64.DEFAULT);
  1.2666 +                os.write(buf);
  1.2667 +
  1.2668 +                // Only alter the intent when we're sure everything has worked
  1.2669 +                intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
  1.2670 +            } else {
  1.2671 +                InputStream is = null;
  1.2672 +                try {
  1.2673 +                    final byte[] buf = new byte[2048];
  1.2674 +                    final URL url = new URL(src);
  1.2675 +                    final String filename = URLUtil.guessFileName(src, null, type);
  1.2676 +                    is = url.openStream();
  1.2677 +
  1.2678 +                    final File imageFile = new File(dir, filename);
  1.2679 +                    os = new FileOutputStream(imageFile);
  1.2680 +
  1.2681 +                    int length;
  1.2682 +                    while ((length = is.read(buf)) != -1) {
  1.2683 +                        os.write(buf, 0, length);
  1.2684 +                    }
  1.2685 +
  1.2686 +                    // Only alter the intent when we're sure everything has worked
  1.2687 +                    intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
  1.2688 +                } finally {
  1.2689 +                    safeStreamClose(is);
  1.2690 +                }
  1.2691 +            }
  1.2692 +        } catch(IOException ex) {
  1.2693 +            // If something went wrong, we'll just leave the intent un-changed
  1.2694 +        } finally {
  1.2695 +            safeStreamClose(os);
  1.2696 +        }
  1.2697 +    }
  1.2698 +
  1.2699 +    // Don't fail silently, tell the user that we weren't able to share the image
  1.2700 +    private static final void showImageShareFailureToast() {
  1.2701 +        Toast toast = Toast.makeText(getContext(),
  1.2702 +                                     getContext().getResources().getString(R.string.share_image_failed),
  1.2703 +                                     Toast.LENGTH_SHORT);
  1.2704 +        toast.show();
  1.2705 +    }
  1.2706 +
  1.2707 +}

mercurial