mobile/android/base/GeckoAppShell.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko;
     8 import java.io.BufferedReader;
     9 import java.io.Closeable;
    10 import java.io.File;
    11 import java.io.FileReader;
    12 import java.io.FileOutputStream;
    13 import java.io.IOException;
    14 import java.io.InputStream;
    15 import java.io.InputStreamReader;
    16 import java.io.OutputStream;
    17 import java.io.PrintWriter;
    18 import java.io.StringWriter;
    19 import java.net.Proxy;
    20 import java.net.URL;
    21 import java.nio.ByteBuffer;
    22 import java.util.ArrayList;
    23 import java.util.Iterator;
    24 import java.util.List;
    25 import java.util.Locale;
    26 import java.util.Map;
    27 import java.util.NoSuchElementException;
    28 import java.util.Queue;
    29 import java.util.StringTokenizer;
    30 import java.util.TreeMap;
    31 import java.util.concurrent.ConcurrentHashMap;
    32 import java.util.concurrent.ConcurrentLinkedQueue;
    34 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
    35 import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
    36 import org.mozilla.gecko.gfx.BitmapUtils;
    37 import org.mozilla.gecko.gfx.LayerView;
    38 import org.mozilla.gecko.gfx.PanZoomController;
    39 import org.mozilla.gecko.mozglue.GeckoLoader;
    40 import org.mozilla.gecko.mozglue.JNITarget;
    41 import org.mozilla.gecko.mozglue.RobocopTarget;
    42 import org.mozilla.gecko.mozglue.generatorannotations.OptionalGeneratedParameter;
    43 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
    44 import org.mozilla.gecko.prompts.PromptService;
    45 import org.mozilla.gecko.util.GeckoEventListener;
    46 import org.mozilla.gecko.util.HardwareUtils;
    47 import org.mozilla.gecko.util.NativeJSContainer;
    48 import org.mozilla.gecko.util.ProxySelector;
    49 import org.mozilla.gecko.util.ThreadUtils;
    50 import org.mozilla.gecko.webapp.Allocator;
    52 import android.app.Activity;
    53 import android.app.ActivityManager;
    54 import android.app.PendingIntent;
    55 import android.content.ActivityNotFoundException;
    56 import android.content.Context;
    57 import android.content.Intent;
    58 import android.content.SharedPreferences;
    59 import android.content.pm.ActivityInfo;
    60 import android.content.pm.ApplicationInfo;
    61 import android.content.pm.PackageInfo;
    62 import android.content.pm.PackageManager;
    63 import android.content.pm.PackageManager.NameNotFoundException;
    64 import android.content.pm.ResolveInfo;
    65 import android.content.pm.ServiceInfo;
    66 import android.content.pm.Signature;
    67 import android.content.res.TypedArray;
    68 import android.graphics.Bitmap;
    69 import android.graphics.Canvas;
    70 import android.graphics.Color;
    71 import android.graphics.ImageFormat;
    72 import android.graphics.Paint;
    73 import android.graphics.PixelFormat;
    74 import android.graphics.Rect;
    75 import android.graphics.RectF;
    76 import android.graphics.SurfaceTexture;
    77 import android.graphics.drawable.BitmapDrawable;
    78 import android.graphics.drawable.Drawable;
    79 import android.hardware.Sensor;
    80 import android.hardware.SensorEventListener;
    81 import android.hardware.SensorManager;
    82 import android.location.Criteria;
    83 import android.location.Location;
    84 import android.location.LocationListener;
    85 import android.location.LocationManager;
    86 import android.media.MediaScannerConnection;
    87 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
    88 import android.net.ConnectivityManager;
    89 import android.net.NetworkInfo;
    90 import android.net.Uri;
    91 import android.os.Build;
    92 import android.os.Handler;
    93 import android.os.Looper;
    94 import android.os.Message;
    95 import android.os.MessageQueue;
    96 import android.os.SystemClock;
    97 import android.os.Vibrator;
    98 import android.provider.Settings;
    99 import android.telephony.TelephonyManager;
   100 import android.text.TextUtils;
   101 import android.util.Base64;
   102 import android.util.DisplayMetrics;
   103 import android.util.Log;
   104 import android.view.ContextThemeWrapper;
   105 import android.view.HapticFeedbackConstants;
   106 import android.view.Surface;
   107 import android.view.SurfaceView;
   108 import android.view.TextureView;
   109 import android.view.View;
   110 import android.view.inputmethod.InputMethodManager;
   111 import android.webkit.MimeTypeMap;
   112 import android.webkit.URLUtil;
   113 import android.widget.AbsoluteLayout;
   114 import android.widget.Toast;
   116 public class GeckoAppShell
   117 {
   118     private static final String LOGTAG = "GeckoAppShell";
   119     private static final boolean LOGGING = false;
   121     // We have static members only.
   122     private GeckoAppShell() { }
   124     private static boolean restartScheduled = false;
   125     private static GeckoEditableListener editableListener = null;
   127     private static final Queue<GeckoEvent> PENDING_EVENTS = new ConcurrentLinkedQueue<GeckoEvent>();
   128     private static final Map<String, String> ALERT_COOKIES = new ConcurrentHashMap<String, String>();
   130     private static volatile boolean locationHighAccuracyEnabled;
   132     // Accessed by NotificationHelper. This should be encapsulated.
   133     /* package */ static NotificationClient notificationClient;
   135     // See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB.
   136     private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
   138     public static final String SHORTCUT_TYPE_WEBAPP = "webapp";
   139     public static final String SHORTCUT_TYPE_BOOKMARK = "bookmark";
   141     static private int sDensityDpi = 0;
   142     static private int sScreenDepth = 0;
   144     private static final EventDispatcher sEventDispatcher = new EventDispatcher();
   146     /* Default colors. */
   147     private static final float[] DEFAULT_LAUNCHER_ICON_HSV = { 32.0f, 1.0f, 1.0f };
   149     /* Is the value in sVibrationEndTime valid? */
   150     private static boolean sVibrationMaybePlaying = false;
   152     /* Time (in System.nanoTime() units) when the currently-playing vibration
   153      * is scheduled to end.  This value is valid only when
   154      * sVibrationMaybePlaying is true. */
   155     private static long sVibrationEndTime = 0;
   157     /* Default value of how fast we should hint the Android sensors. */
   158     private static int sDefaultSensorHint = 100;
   160     private static Sensor gAccelerometerSensor = null;
   161     private static Sensor gLinearAccelerometerSensor = null;
   162     private static Sensor gGyroscopeSensor = null;
   163     private static Sensor gOrientationSensor = null;
   164     private static Sensor gProximitySensor = null;
   165     private static Sensor gLightSensor = null;
   167     /*
   168      * Keep in sync with constants found here:
   169      * http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
   170     */
   171     static public final int WPL_STATE_START = 0x00000001;
   172     static public final int WPL_STATE_STOP = 0x00000010;
   173     static public final int WPL_STATE_IS_DOCUMENT = 0x00020000;
   174     static public final int WPL_STATE_IS_NETWORK = 0x00040000;
   176     /* Keep in sync with constants found here:
   177       http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsINetworkLinkService.idl
   178     */
   179     static public final int LINK_TYPE_UNKNOWN = 0;
   180     static public final int LINK_TYPE_ETHERNET = 1;
   181     static public final int LINK_TYPE_USB = 2;
   182     static public final int LINK_TYPE_WIFI = 3;
   183     static public final int LINK_TYPE_WIMAX = 4;
   184     static public final int LINK_TYPE_2G = 5;
   185     static public final int LINK_TYPE_3G = 6;
   186     static public final int LINK_TYPE_4G = 7;
   188     /* The Android-side API: API methods that Android calls */
   190     // Initialization methods
   191     public static native void nativeInit();
   193     // helper methods
   194     //    public static native void setSurfaceView(GeckoSurfaceView sv);
   195     public static native void setLayerClient(Object client);
   196     public static native void onResume();
   197     public static void callObserver(String observerKey, String topic, String data) {
   198         sendEventToGecko(GeckoEvent.createCallObserverEvent(observerKey, topic, data));
   199     }
   200     public static void removeObserver(String observerKey) {
   201         sendEventToGecko(GeckoEvent.createRemoveObserverEvent(observerKey));
   202     }
   203     public static native Message getNextMessageFromQueue(MessageQueue queue);
   204     public static native void onSurfaceTextureFrameAvailable(Object surfaceTexture, int id);
   205     public static native void dispatchMemoryPressure();
   207     public static void registerGlobalExceptionHandler() {
   208         Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
   209             @Override
   210             public void uncaughtException(Thread thread, Throwable e) {
   211                 handleUncaughtException(thread, e);
   212             }
   213         });
   214     }
   216     private static String getStackTraceString(Throwable e) {
   217         StringWriter sw = new StringWriter();
   218         PrintWriter pw = new PrintWriter(sw);
   219         e.printStackTrace(pw);
   220         pw.flush();
   221         return sw.toString();
   222     }
   224     private static native void reportJavaCrash(String stackTrace);
   226     public static void notifyUriVisited(String uri) {
   227         sendEventToGecko(GeckoEvent.createVisitedEvent(uri));
   228     }
   230     public static native void processNextNativeEvent(boolean mayWait);
   232     public static native void notifyBatteryChange(double aLevel, boolean aCharging, double aRemainingTime);
   234     public static native void scheduleComposite();
   236     // Resuming the compositor is a synchronous request, so be
   237     // careful of possible deadlock. Resuming the compositor will also cause
   238     // a composition, so there is no need to schedule a composition after
   239     // resuming.
   240     public static native void scheduleResumeComposition(int width, int height);
   242     public static native float computeRenderIntegrity();
   244     public static native SurfaceBits getSurfaceBits(Surface surface);
   246     public static native void onFullScreenPluginHidden(View view);
   248     public static class CreateShortcutFaviconLoadedListener implements OnFaviconLoadedListener {
   249         private final String title;
   250         private final String url;
   252         public CreateShortcutFaviconLoadedListener(final String url, final String title) {
   253             this.url = url;
   254             this.title = title;
   255         }
   257         @Override
   258         public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) {
   259             GeckoAppShell.createShortcut(title, url, url, favicon, "");
   260         }
   261     }
   263     private static final class GeckoMediaScannerClient implements MediaScannerConnectionClient {
   264         private final String mFile;
   265         private final String mMimeType;
   266         private MediaScannerConnection mScanner;
   268         public static void startScan(Context context, String file, String mimeType) {
   269             new GeckoMediaScannerClient(context, file, mimeType);
   270         }
   272         private GeckoMediaScannerClient(Context context, String file, String mimeType) {
   273             mFile = file;
   274             mMimeType = mimeType;
   275             mScanner = new MediaScannerConnection(context, this);
   276             mScanner.connect();
   277         }
   279         @Override
   280         public void onMediaScannerConnected() {
   281             mScanner.scanFile(mFile, mMimeType);
   282         }
   284         @Override
   285         public void onScanCompleted(String path, Uri uri) {
   286             if(path.equals(mFile)) {
   287                 mScanner.disconnect();
   288                 mScanner = null;
   289             }
   290         }
   291     }
   293     private static LayerView sLayerView;
   295     public static void setLayerView(LayerView lv) {
   296         sLayerView = lv;
   297     }
   299     @RobocopTarget
   300     public static LayerView getLayerView() {
   301         return sLayerView;
   302     }
   304     public static void runGecko(String apkPath, String args, String url, String type) {
   305         // Preparation for pumpMessageLoop()
   306         MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
   307             @Override public boolean queueIdle() {
   308                 final Handler geckoHandler = ThreadUtils.sGeckoHandler;
   309                 Message idleMsg = Message.obtain(geckoHandler);
   310                 // Use |Message.obj == GeckoHandler| to identify our "queue is empty" message
   311                 idleMsg.obj = geckoHandler;
   312                 geckoHandler.sendMessageAtFrontOfQueue(idleMsg);
   313                 // Keep this IdleHandler
   314                 return true;
   315             }
   316         };
   317         Looper.myQueue().addIdleHandler(idleHandler);
   319         // run gecko -- it will spawn its own thread
   320         GeckoAppShell.nativeInit();
   322         if (sLayerView != null)
   323             GeckoAppShell.setLayerClient(sLayerView.getLayerClientObject());
   325         // First argument is the .apk path
   326         String combinedArgs = apkPath + " -greomni " + apkPath;
   327         if (args != null)
   328             combinedArgs += " " + args;
   329         if (url != null)
   330             combinedArgs += " -url " + url;
   331         if (type != null)
   332             combinedArgs += " " + type;
   334         DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
   335         combinedArgs += " -width " + metrics.widthPixels + " -height " + metrics.heightPixels;
   337         ThreadUtils.postToUiThread(new Runnable() {
   338                 @Override
   339                 public void run() {
   340                     geckoLoaded();
   341                 }
   342             });
   344         // and go
   345         Log.d(LOGTAG, "GeckoLoader.nativeRun " + combinedArgs);
   346         GeckoLoader.nativeRun(combinedArgs);
   348         // Remove pumpMessageLoop() idle handler
   349         Looper.myQueue().removeIdleHandler(idleHandler);
   350     }
   352     // Called on the UI thread after Gecko loads.
   353     private static void geckoLoaded() {
   354         GeckoEditable editable = new GeckoEditable();
   355         // install the gecko => editable listener
   356         editableListener = editable;
   357     }
   359     static void sendPendingEventsToGecko() {
   360         try {
   361             while (!PENDING_EVENTS.isEmpty()) {
   362                 final GeckoEvent e = PENDING_EVENTS.poll();
   363                 notifyGeckoOfEvent(e);
   364             }
   365         } catch (NoSuchElementException e) {}
   366     }
   368     /**
   369      * If the Gecko thread is running, immediately dispatches the event to
   370      * Gecko.
   371      *
   372      * If the Gecko thread is not running, queues the event. If the queue is
   373      * full, throws {@link IllegalStateException}.
   374      *
   375      * Queued events will be dispatched in order of arrival when the Gecko
   376      * thread becomes live.
   377      *
   378      * This method can be called from any thread.
   379      *
   380      * @param e
   381      *            the event to dispatch. Cannot be null.
   382      */
   383     @RobocopTarget
   384     public static void sendEventToGecko(GeckoEvent e) {
   385         if (e == null) {
   386             throw new IllegalArgumentException("e cannot be null.");
   387         }
   389         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
   390             notifyGeckoOfEvent(e);
   391             // Gecko will copy the event data into a normal C++ object. We can recycle the event now.
   392             e.recycle();
   393             return;
   394         }
   396         // Throws if unable to add the event due to capacity restrictions.
   397         PENDING_EVENTS.add(e);
   398     }
   400     // Tell the Gecko event loop that an event is available.
   401     public static native void notifyGeckoOfEvent(GeckoEvent event);
   403     /*
   404      *  The Gecko-side API: API methods that Gecko calls
   405      */
   407     @WrapElementForJNI(allowMultithread = true, generateStatic = true, noThrow = true)
   408     public static void handleUncaughtException(Thread thread, Throwable e) {
   409         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExited)) {
   410             // We've called System.exit. All exceptions after this point are Android
   411             // berating us for being nasty to it.
   412             return;
   413         }
   415         if (thread == null) {
   416             thread = Thread.currentThread();
   417         }
   418         // If the uncaught exception was rethrown, walk the exception `cause` chain to find
   419         // the original exception so Socorro can correctly collate related crash reports.
   420         Throwable cause;
   421         while ((cause = e.getCause()) != null) {
   422             e = cause;
   423         }
   425         try {
   426             Log.e(LOGTAG, ">>> REPORTING UNCAUGHT EXCEPTION FROM THREAD "
   427                           + thread.getId() + " (\"" + thread.getName() + "\")", e);
   429             Thread mainThread = ThreadUtils.getUiThread();
   430             if (mainThread != null && thread != mainThread) {
   431                 Log.e(LOGTAG, "Main thread stack:");
   432                 for (StackTraceElement ste : mainThread.getStackTrace()) {
   433                     Log.e(LOGTAG, ste.toString());
   434                 }
   435             }
   437             if (e instanceof OutOfMemoryError) {
   438                 SharedPreferences prefs = getSharedPreferences();
   439                 SharedPreferences.Editor editor = prefs.edit();
   440                 editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, true);
   441                 editor.commit();
   442             }
   443         } finally {
   444             reportJavaCrash(getStackTraceString(e));
   445         }
   446     }
   448     @WrapElementForJNI(generateStatic = true)
   449     public static void notifyIME(int type) {
   450         if (editableListener != null) {
   451             editableListener.notifyIME(type);
   452         }
   453     }
   455     @WrapElementForJNI(generateStatic = true)
   456     public static void notifyIMEContext(int state, String typeHint,
   457                                         String modeHint, String actionHint) {
   458         if (editableListener != null) {
   459             editableListener.notifyIMEContext(state, typeHint,
   460                                                modeHint, actionHint);
   461         }
   462     }
   464     @WrapElementForJNI(generateStatic = true)
   465     public static void notifyIMEChange(String text, int start, int end, int newEnd) {
   466         if (newEnd < 0) { // Selection change
   467             editableListener.onSelectionChange(start, end);
   468         } else { // Text change
   469             editableListener.onTextChange(text, start, end, newEnd);
   470         }
   471     }
   473     private static final Object sEventAckLock = new Object();
   474     private static boolean sWaitingForEventAck;
   476     // Block the current thread until the Gecko event loop is caught up
   477     public static void sendEventToGeckoSync(GeckoEvent e) {
   478         e.setAckNeeded(true);
   480         long time = SystemClock.uptimeMillis();
   481         boolean isUiThread = ThreadUtils.isOnUiThread();
   483         synchronized (sEventAckLock) {
   484             if (sWaitingForEventAck) {
   485                 // should never happen since we always leave it as false when we exit this function.
   486                 Log.e(LOGTAG, "geckoEventSync() may have been called twice concurrently!", new Exception());
   487                 // fall through for graceful handling
   488             }
   490             sendEventToGecko(e);
   491             sWaitingForEventAck = true;
   492             while (true) {
   493                 try {
   494                     sEventAckLock.wait(1000);
   495                 } catch (InterruptedException ie) {
   496                 }
   497                 if (!sWaitingForEventAck) {
   498                     // response received
   499                     break;
   500                 }
   501                 long waited = SystemClock.uptimeMillis() - time;
   502                 Log.d(LOGTAG, "Gecko event sync taking too long: " + waited + "ms");
   503             }
   504         }
   505     }
   507     // Signal the Java thread that it's time to wake up
   508     @WrapElementForJNI
   509     public static void acknowledgeEvent() {
   510         synchronized (sEventAckLock) {
   511             sWaitingForEventAck = false;
   512             sEventAckLock.notifyAll();
   513         }
   514     }
   516     private static float getLocationAccuracy(Location location) {
   517         float radius = location.getAccuracy();
   518         return (location.hasAccuracy() && radius > 0) ? radius : 1001;
   519     }
   521     private static Location getLastKnownLocation(LocationManager lm) {
   522         Location lastKnownLocation = null;
   523         List<String> providers = lm.getAllProviders();
   525         for (String provider : providers) {
   526             Location location = lm.getLastKnownLocation(provider);
   527             if (location == null) {
   528                 continue;
   529             }
   531             if (lastKnownLocation == null) {
   532                 lastKnownLocation = location;
   533                 continue;
   534             }
   536             long timeDiff = location.getTime() - lastKnownLocation.getTime();
   537             if (timeDiff > 0 ||
   538                 (timeDiff == 0 &&
   539                  getLocationAccuracy(location) < getLocationAccuracy(lastKnownLocation))) {
   540                 lastKnownLocation = location;
   541             }
   542         }
   544         return lastKnownLocation;
   545     }
   547     @WrapElementForJNI
   548     public static void enableLocation(final boolean enable) {
   549         ThreadUtils.postToUiThread(new Runnable() {
   550                 @Override
   551                 public void run() {
   552                     LocationManager lm = getLocationManager(getContext());
   553                     if (lm == null) {
   554                         return;
   555                     }
   557                     if (enable) {
   558                         Location lastKnownLocation = getLastKnownLocation(lm);
   559                         if (lastKnownLocation != null) {
   560                             getGeckoInterface().getLocationListener().onLocationChanged(lastKnownLocation);
   561                         }
   563                         Criteria criteria = new Criteria();
   564                         criteria.setSpeedRequired(false);
   565                         criteria.setBearingRequired(false);
   566                         criteria.setAltitudeRequired(false);
   567                         if (locationHighAccuracyEnabled) {
   568                             criteria.setAccuracy(Criteria.ACCURACY_FINE);
   569                             criteria.setCostAllowed(true);
   570                             criteria.setPowerRequirement(Criteria.POWER_HIGH);
   571                         } else {
   572                             criteria.setAccuracy(Criteria.ACCURACY_COARSE);
   573                             criteria.setCostAllowed(false);
   574                             criteria.setPowerRequirement(Criteria.POWER_LOW);
   575                         }
   577                         String provider = lm.getBestProvider(criteria, true);
   578                         if (provider == null)
   579                             return;
   581                         Looper l = Looper.getMainLooper();
   582                         lm.requestLocationUpdates(provider, 100, (float).5, getGeckoInterface().getLocationListener(), l);
   583                     } else {
   584                         lm.removeUpdates(getGeckoInterface().getLocationListener());
   585                     }
   586                 }
   587             });
   588     }
   590     private static LocationManager getLocationManager(Context context) {
   591         try {
   592             return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
   593         } catch (NoSuchFieldError e) {
   594             // Some Tegras throw exceptions about missing the CONTROL_LOCATION_UPDATES permission,
   595             // which allows enabling/disabling location update notifications from the cell radio.
   596             // CONTROL_LOCATION_UPDATES is not for use by normal applications, but we might be
   597             // hitting this problem if the Tegras are confused about missing cell radios.
   598             Log.e(LOGTAG, "LOCATION_SERVICE not found?!", e);
   599             return null;
   600         }
   601     }
   603     @WrapElementForJNI
   604     public static void enableLocationHighAccuracy(final boolean enable) {
   605         locationHighAccuracyEnabled = enable;
   606     }
   608     @WrapElementForJNI
   609     public static void enableSensor(int aSensortype) {
   610         GeckoInterface gi = getGeckoInterface();
   611         if (gi == null)
   612             return;
   613         SensorManager sm = (SensorManager)
   614             getContext().getSystemService(Context.SENSOR_SERVICE);
   616         switch(aSensortype) {
   617         case GeckoHalDefines.SENSOR_ORIENTATION:
   618             if(gOrientationSensor == null)
   619                 gOrientationSensor = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);
   620             if (gOrientationSensor != null) 
   621                 sm.registerListener(gi.getSensorEventListener(), gOrientationSensor, sDefaultSensorHint);
   622             break;
   624         case GeckoHalDefines.SENSOR_ACCELERATION:
   625             if(gAccelerometerSensor == null)
   626                 gAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
   627             if (gAccelerometerSensor != null)
   628                 sm.registerListener(gi.getSensorEventListener(), gAccelerometerSensor, sDefaultSensorHint);
   629             break;
   631         case GeckoHalDefines.SENSOR_PROXIMITY:
   632             if(gProximitySensor == null  )
   633                 gProximitySensor = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
   634             if (gProximitySensor != null)
   635                 sm.registerListener(gi.getSensorEventListener(), gProximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
   636             break;
   638         case GeckoHalDefines.SENSOR_LIGHT:
   639             if(gLightSensor == null)
   640                 gLightSensor = sm.getDefaultSensor(Sensor.TYPE_LIGHT);
   641             if (gLightSensor != null)
   642                 sm.registerListener(gi.getSensorEventListener(), gLightSensor, SensorManager.SENSOR_DELAY_NORMAL);
   643             break;
   645         case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION:
   646             if(gLinearAccelerometerSensor == null)
   647                 gLinearAccelerometerSensor = sm.getDefaultSensor(10 /* API Level 9 - TYPE_LINEAR_ACCELERATION */);
   648             if (gLinearAccelerometerSensor != null)
   649                 sm.registerListener(gi.getSensorEventListener(), gLinearAccelerometerSensor, sDefaultSensorHint);
   650             break;
   652         case GeckoHalDefines.SENSOR_GYROSCOPE:
   653             if(gGyroscopeSensor == null)
   654                 gGyroscopeSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
   655             if (gGyroscopeSensor != null)
   656                 sm.registerListener(gi.getSensorEventListener(), gGyroscopeSensor, sDefaultSensorHint);
   657             break;
   658         default:
   659             Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " + aSensortype);
   660         }
   661     }
   663     @WrapElementForJNI
   664     public static void disableSensor(int aSensortype) {
   665         GeckoInterface gi = getGeckoInterface();
   666         if (gi == null)
   667             return;
   669         SensorManager sm = (SensorManager)
   670             getContext().getSystemService(Context.SENSOR_SERVICE);
   672         switch (aSensortype) {
   673         case GeckoHalDefines.SENSOR_ORIENTATION:
   674             if (gOrientationSensor != null)
   675                 sm.unregisterListener(gi.getSensorEventListener(), gOrientationSensor);
   676             break;
   678         case GeckoHalDefines.SENSOR_ACCELERATION:
   679             if (gAccelerometerSensor != null)
   680                 sm.unregisterListener(gi.getSensorEventListener(), gAccelerometerSensor);
   681             break;
   683         case GeckoHalDefines.SENSOR_PROXIMITY:
   684             if (gProximitySensor != null)
   685                 sm.unregisterListener(gi.getSensorEventListener(), gProximitySensor);
   686             break;
   688         case GeckoHalDefines.SENSOR_LIGHT:
   689             if (gLightSensor != null)
   690                 sm.unregisterListener(gi.getSensorEventListener(), gLightSensor);
   691             break;
   693         case GeckoHalDefines.SENSOR_LINEAR_ACCELERATION:
   694             if (gLinearAccelerometerSensor != null)
   695                 sm.unregisterListener(gi.getSensorEventListener(), gLinearAccelerometerSensor);
   696             break;
   698         case GeckoHalDefines.SENSOR_GYROSCOPE:
   699             if (gGyroscopeSensor != null)
   700                 sm.unregisterListener(gi.getSensorEventListener(), gGyroscopeSensor);
   701             break;
   702         default:
   703             Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype);
   704         }
   705     }
   707     @WrapElementForJNI
   708     public static void moveTaskToBack() {
   709         if (getGeckoInterface() != null)
   710             getGeckoInterface().getActivity().moveTaskToBack(true);
   711     }
   713     public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
   714         // This method may be called from JNI to report Gecko's current selection indexes, but
   715         // Native Fennec doesn't care because the Java code already knows the selection indexes.
   716     }
   718     @WrapElementForJNI(stubName = "NotifyXreExit")
   719     static void onXreExit() {
   720         // The launch state can only be Launched or GeckoRunning at this point
   721         GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoExiting);
   722         if (getGeckoInterface() != null) {
   723             if (restartScheduled) {
   724                 getGeckoInterface().doRestart();
   725             } else {
   726                 getGeckoInterface().getActivity().finish();
   727             }
   728         }
   730         systemExit();
   731     }
   733     static void systemExit() {
   734         Log.d(LOGTAG, "Killing via System.exit()");
   735         GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoExited);
   736         System.exit(0);
   737     }
   739     @WrapElementForJNI
   740     static void scheduleRestart() {
   741         restartScheduled = true;
   742     }
   744     public static Intent getWebappIntent(String aURI, String aOrigin, String aTitle, Bitmap aIcon) {
   745         Intent intent;
   747         if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
   748             Allocator slots = Allocator.getInstance(getContext());
   749             int index = slots.getIndexForOrigin(aOrigin);
   751             if (index == -1) {
   752                 return null;
   753             }
   754             String packageName = slots.getAppForIndex(index);
   755             intent = getContext().getPackageManager().getLaunchIntentForPackage(packageName);
   756             if (aURI != null) {
   757                 intent.setData(Uri.parse(aURI));
   758             }
   759         } else {
   760             int index;
   761             if (aIcon != null && !TextUtils.isEmpty(aTitle))
   762                 index = WebappAllocator.getInstance(getContext()).findAndAllocateIndex(aOrigin, aTitle, aIcon);
   763             else
   764                 index = WebappAllocator.getInstance(getContext()).getIndexForApp(aOrigin);
   766             if (index == -1)
   767                 return null;
   769             intent = getWebappIntent(index, aURI);
   770         }
   772         return intent;
   773     }
   775     // The old implementation of getWebappIntent.  Not used by MOZ_ANDROID_SYNTHAPKS.
   776     public static Intent getWebappIntent(int aIndex, String aURI) {
   777         Intent intent = new Intent();
   778         intent.setAction(GeckoApp.ACTION_WEBAPP_PREFIX + aIndex);
   779         intent.setData(Uri.parse(aURI));
   780         intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
   781                             AppConstants.ANDROID_PACKAGE_NAME + ".WebApps$WebApp" + aIndex);
   782         return intent;
   783     }
   785     // "Installs" an application by creating a shortcut
   786     // This is the entry point from AndroidBridge.h
   787     @WrapElementForJNI
   788     static void createShortcut(String aTitle, String aURI, String aIconData, String aType) {
   789         if ("webapp".equals(aType)) {
   790             Log.w(LOGTAG, "createShortcut with no unique URI should not be used for aType = webapp!");
   791         }
   793         createShortcut(aTitle, aURI, aURI, aIconData, aType);
   794     }
   796     // For non-webapps.
   797     public static void createShortcut(String aTitle, String aURI, Bitmap aBitmap, String aType) {
   798         createShortcut(aTitle, aURI, aURI, aBitmap, aType);
   799     }
   801     // Internal, for webapps.
   802     static void createShortcut(final String aTitle, final String aURI, final String aUniqueURI, final String aIconData, final String aType) {
   803         ThreadUtils.postToBackgroundThread(new Runnable() {
   804             @Override
   805             public void run() {
   806                 // TODO: use the cache. Bug 961600.
   807                 Bitmap icon = FaviconDecoder.getMostSuitableBitmapFromDataURI(aIconData, getPreferredIconSize());
   808                 GeckoAppShell.doCreateShortcut(aTitle, aURI, aURI, icon, aType);
   809             }
   810         });
   811     }
   813     public static void createShortcut(final String aTitle, final String aURI, final String aUniqueURI,
   814                                       final Bitmap aIcon, final String aType) {
   815         ThreadUtils.postToBackgroundThread(new Runnable() {
   816             @Override
   817             public void run() {
   818                 GeckoAppShell.doCreateShortcut(aTitle, aURI, aUniqueURI, aIcon, aType);
   819             }
   820         });
   821     }
   823     /**
   824      * Call this method only on the background thread.
   825      */
   826     private static void doCreateShortcut(final String aTitle, final String aURI, final String aUniqueURI,
   827                                          final Bitmap aIcon, final String aType) {
   828         // The intent to be launched by the shortcut.
   829         Intent shortcutIntent;
   830         if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP)) {
   831             shortcutIntent = getWebappIntent(aURI, aUniqueURI, aTitle, aIcon);
   832         } else {
   833             shortcutIntent = new Intent();
   834             shortcutIntent.setAction(GeckoApp.ACTION_BOOKMARK);
   835             shortcutIntent.setData(Uri.parse(aURI));
   836             shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
   837                                         AppConstants.BROWSER_INTENT_CLASS_NAME);
   838         }
   840         Intent intent = new Intent();
   841         intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
   842         intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon, aType));
   844         if (aTitle != null) {
   845             intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
   846         } else {
   847             intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
   848         }
   850         // Do not allow duplicate items.
   851         intent.putExtra("duplicate", false);
   853         intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
   854         getContext().sendBroadcast(intent);
   855     }
   857     public static void removeShortcut(final String aTitle, final String aURI, final String aType) {
   858         removeShortcut(aTitle, aURI, null, aType);
   859     }
   861     public static void removeShortcut(final String aTitle, final String aURI, final String aUniqueURI, final String aType) {
   862         ThreadUtils.postToBackgroundThread(new Runnable() {
   863             @Override
   864             public void run() {
   865                 // the intent to be launched by the shortcut
   866                 Intent shortcutIntent;
   867                 if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP)) {
   868                     shortcutIntent = getWebappIntent(aURI, aUniqueURI, "", null);
   869                     if (shortcutIntent == null)
   870                         return;
   871                 } else {
   872                     shortcutIntent = new Intent();
   873                     shortcutIntent.setAction(GeckoApp.ACTION_BOOKMARK);
   874                     shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
   875                                                 AppConstants.BROWSER_INTENT_CLASS_NAME);
   876                     shortcutIntent.setData(Uri.parse(aURI));
   877                 }
   879                 Intent intent = new Intent();
   880                 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
   881                 if (aTitle != null)
   882                     intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
   883                 else
   884                     intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
   886                 intent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
   887                 getContext().sendBroadcast(intent);
   888             }
   889         });
   890     }
   892     @JNITarget
   893     static public int getPreferredIconSize() {
   894         if (android.os.Build.VERSION.SDK_INT >= 11) {
   895             ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
   896             return am.getLauncherLargeIconSize();
   897         } else {
   898             switch (getDpi()) {
   899                 case DisplayMetrics.DENSITY_MEDIUM:
   900                     return 48;
   901                 case DisplayMetrics.DENSITY_XHIGH:
   902                     return 96;
   903                 case DisplayMetrics.DENSITY_HIGH:
   904                 default:
   905                     return 72;
   906             }
   907         }
   908     }
   910     static private Bitmap getLauncherIcon(Bitmap aSource, String aType) {
   911         final int kOffset = 6;
   912         final int kRadius = 5;
   913         int size = getPreferredIconSize();
   914         int insetSize = aSource != null ? size * 2 / 3 : size;
   916         Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
   917         Canvas canvas = new Canvas(bitmap);
   920         // draw a base color
   921         Paint paint = new Paint();
   922         if (aSource == null) {
   923             // If we aren't drawing a favicon, just use an orange color.
   924             paint.setColor(Color.HSVToColor(DEFAULT_LAUNCHER_ICON_HSV));
   925             canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
   926         } else if (aType.equalsIgnoreCase(SHORTCUT_TYPE_WEBAPP) || aSource.getWidth() >= insetSize || aSource.getHeight() >= insetSize) {
   927             // otherwise, if this is a webapp or if the icons is lare enough, just draw it
   928             Rect iconBounds = new Rect(0, 0, size, size);
   929             canvas.drawBitmap(aSource, null, iconBounds, null);
   930             return bitmap;
   931         } else {
   932             // otherwise use the dominant color from the icon + a layer of transparent white to lighten it somewhat
   933             int color = BitmapUtils.getDominantColor(aSource);
   934             paint.setColor(color);
   935             canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
   936             paint.setColor(Color.argb(100, 255, 255, 255));
   937             canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
   938         }
   940         // draw the overlay
   941         Bitmap overlay = BitmapUtils.decodeResource(getContext(), R.drawable.home_bg);
   942         canvas.drawBitmap(overlay, null, new Rect(0, 0, size, size), null);
   944         // draw the favicon
   945         if (aSource == null)
   946             aSource = BitmapUtils.decodeResource(getContext(), R.drawable.home_star);
   948         // by default, we scale the icon to this size
   949         int sWidth = insetSize / 2;
   950         int sHeight = sWidth;
   952         int halfSize = size / 2;
   953         canvas.drawBitmap(aSource,
   954                           null,
   955                           new Rect(halfSize - sWidth,
   956                                    halfSize - sHeight,
   957                                    halfSize + sWidth,
   958                                    halfSize + sHeight),
   959                           null);
   961         return bitmap;
   962     }
   964     @WrapElementForJNI(stubName = "GetHandlersForMimeTypeWrapper")
   965     static String[] getHandlersForMimeType(String aMimeType, String aAction) {
   966         Intent intent = getIntentForActionString(aAction);
   967         if (aMimeType != null && aMimeType.length() > 0)
   968             intent.setType(aMimeType);
   969         return getHandlersForIntent(intent);
   970     }
   972     @WrapElementForJNI(stubName = "GetHandlersForURLWrapper")
   973     static String[] getHandlersForURL(String aURL, String aAction) {
   974         // aURL may contain the whole URL or just the protocol
   975         Uri uri = aURL.indexOf(':') >= 0 ? Uri.parse(aURL) : new Uri.Builder().scheme(aURL).build();
   977         Intent intent = getOpenURIIntent(getContext(), uri.toString(), "",
   978             TextUtils.isEmpty(aAction) ? Intent.ACTION_VIEW : aAction, "");
   980         return getHandlersForIntent(intent);
   981     }
   983     static boolean hasHandlersForIntent(Intent intent) {
   984         PackageManager pm = getContext().getPackageManager();
   985         List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
   986         return !list.isEmpty();
   987     }
   989     static String[] getHandlersForIntent(Intent intent) {
   990         PackageManager pm = getContext().getPackageManager();
   991         List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
   992         int numAttr = 4;
   993         String[] ret = new String[list.size() * numAttr];
   994         for (int i = 0; i < list.size(); i++) {
   995             ResolveInfo resolveInfo = list.get(i);
   996             ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
   997             if (resolveInfo.isDefault)
   998                 ret[i * numAttr + 1] = "default";
   999             else
  1000                 ret[i * numAttr + 1] = "";
  1001             ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
  1002             ret[i * numAttr + 3] = resolveInfo.activityInfo.name;
  1004         return ret;
  1007     static Intent getIntentForActionString(String aAction) {
  1008         // Default to the view action if no other action as been specified.
  1009         if (TextUtils.isEmpty(aAction)) {
  1010             return new Intent(Intent.ACTION_VIEW);
  1012         return new Intent(aAction);
  1015     @WrapElementForJNI(stubName = "GetExtensionFromMimeTypeWrapper")
  1016     static String getExtensionFromMimeType(String aMimeType) {
  1017         return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType);
  1020     @WrapElementForJNI(stubName = "GetMimeTypeFromExtensionsWrapper")
  1021     static String getMimeTypeFromExtensions(String aFileExt) {
  1022         StringTokenizer st = new StringTokenizer(aFileExt, ".,; ");
  1023         String type = null;
  1024         String subType = null;
  1025         while (st.hasMoreElements()) {
  1026             String ext = st.nextToken();
  1027             String mt = getMimeTypeFromExtension(ext);
  1028             if (mt == null)
  1029                 continue;
  1030             int slash = mt.indexOf('/');
  1031             String tmpType = mt.substring(0, slash);
  1032             if (!tmpType.equalsIgnoreCase(type))
  1033                 type = type == null ? tmpType : "*";
  1034             String tmpSubType = mt.substring(slash + 1);
  1035             if (!tmpSubType.equalsIgnoreCase(subType))
  1036                 subType = subType == null ? tmpSubType : "*";
  1038         if (type == null)
  1039             type = "*";
  1040         if (subType == null)
  1041             subType = "*";
  1042         return type + "/" + subType;
  1045     static void safeStreamClose(Closeable stream) {
  1046         try {
  1047             if (stream != null)
  1048                 stream.close();
  1049         } catch (IOException e) {}
  1052     static boolean isUriSafeForScheme(Uri aUri) {
  1053         // Bug 794034 - We don't want to pass MWI or USSD codes to the
  1054         // dialer, and ensure the Uri class doesn't parse a URI
  1055         // containing a fragment ('#')
  1056         final String scheme = aUri.getScheme();
  1057         if ("tel".equals(scheme) || "sms".equals(scheme)) {
  1058             final String number = aUri.getSchemeSpecificPart();
  1059             if (number.contains("#") || number.contains("*") || aUri.getFragment() != null) {
  1060                 return false;
  1063         return true;
  1066     /**
  1067      * Given the inputs to <code>getOpenURIIntent</code>, plus an optional
  1068      * package name and class name, create and fire an intent to open the
  1069      * provided URI. If a class name is specified but a package name is not,
  1070      * we will default to using the current fennec package.
  1072      * @param targetURI the string spec of the URI to open.
  1073      * @param mimeType an optional MIME type string.
  1074      * @param packageName an optional app package name.
  1075      * @param className an optional intent class name.
  1076      * @param action an Android action specifier, such as
  1077      *               <code>Intent.ACTION_SEND</code>.
  1078      * @param title the title to use in <code>ACTION_SEND</code> intents.
  1079      * @return true if the activity started successfully; false otherwise.
  1080      */
  1081     @WrapElementForJNI
  1082     public static boolean openUriExternal(String targetURI,
  1083                                           String mimeType,
  1084               @OptionalGeneratedParameter String packageName,
  1085               @OptionalGeneratedParameter String className,
  1086               @OptionalGeneratedParameter String action,
  1087               @OptionalGeneratedParameter String title) {
  1088         final Context context = getContext();
  1089         final Intent intent = getOpenURIIntent(context, targetURI,
  1090                                                mimeType, action, title);
  1092         if (intent == null) {
  1093             return false;
  1096         if (!TextUtils.isEmpty(className)) {
  1097             if (!TextUtils.isEmpty(packageName)) {
  1098                 intent.setClassName(packageName, className);
  1099             } else {
  1100                 // Default to using the fennec app context.
  1101                 intent.setClassName(context, className);
  1105         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  1106         try {
  1107             context.startActivity(intent);
  1108             return true;
  1109         } catch (ActivityNotFoundException e) {
  1110             return false;
  1114     /**
  1115      * Return a <code>Uri</code> instance which is equivalent to <code>u</code>,
  1116      * but with a guaranteed-lowercase scheme as if the API level 16 method
  1117      * <code>u.normalizeScheme</code> had been called.
  1119      * @param u the <code>Uri</code> to normalize.
  1120      * @return a <code>Uri</code>, which might be <code>u</code>.
  1121      */
  1122     static Uri normalizeUriScheme(final Uri u) {
  1123         final String scheme = u.getScheme();
  1124         final String lower  = scheme.toLowerCase(Locale.US);
  1125         if (lower.equals(scheme)) {
  1126             return u;
  1129         // Otherwise, return a new URI with a normalized scheme.
  1130         return u.buildUpon().scheme(lower).build();
  1133     /**
  1134      * Given a URI, a MIME type, and a title,
  1135      * produce a share intent which can be used to query all activities
  1136      * than can open the specified URI.
  1138      * @param context a <code>Context</code> instance.
  1139      * @param targetURI the string spec of the URI to open.
  1140      * @param mimeType an optional MIME type string.
  1141      * @param title the title to use in <code>ACTION_SEND</code> intents.
  1142      * @return an <code>Intent</code>, or <code>null</code> if none could be
  1143      *         produced.
  1144      */
  1145     public static Intent getShareIntent(final Context context,
  1146                                         final String targetURI,
  1147                                         final String mimeType,
  1148                                         final String title) {
  1149         Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND);
  1150         shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI);
  1151         shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
  1153         // Note that EXTRA_TITLE is intended to be used for share dialog
  1154         // titles. Common usage (e.g., Pocket) suggests that it's sometimes
  1155         // interpreted as an alternate to EXTRA_SUBJECT, so we include it.
  1156         shareIntent.putExtra(Intent.EXTRA_TITLE, title);
  1158         if (mimeType != null && mimeType.length() > 0) {
  1159             shareIntent.setType(mimeType);
  1162         return shareIntent;
  1165     /**
  1166      * Given a URI, a MIME type, an Android intent "action", and a title,
  1167      * produce an intent which can be used to start an activity to open
  1168      * the specified URI.
  1170      * @param context a <code>Context</code> instance.
  1171      * @param targetURI the string spec of the URI to open.
  1172      * @param mimeType an optional MIME type string.
  1173      * @param action an Android action specifier, such as
  1174      *               <code>Intent.ACTION_SEND</code>.
  1175      * @param title the title to use in <code>ACTION_SEND</code> intents.
  1176      * @return an <code>Intent</code>, or <code>null</code> if none could be
  1177      *         produced.
  1178      */
  1179     static Intent getOpenURIIntent(final Context context,
  1180                                    final String targetURI,
  1181                                    final String mimeType,
  1182                                    final String action,
  1183                                    final String title) {
  1185         if (action.equalsIgnoreCase(Intent.ACTION_SEND)) {
  1186             Intent shareIntent = getShareIntent(context, targetURI, mimeType, title);
  1187             return Intent.createChooser(shareIntent,
  1188                                         context.getResources().getString(R.string.share_title)); 
  1191         final Uri uri = normalizeUriScheme(targetURI.indexOf(':') >= 0 ? Uri.parse(targetURI) : new Uri.Builder().scheme(targetURI).build());
  1192         if (mimeType.length() > 0) {
  1193             Intent intent = getIntentForActionString(action);
  1194             intent.setDataAndType(uri, mimeType);
  1195             return intent;
  1198         if (!isUriSafeForScheme(uri)) {
  1199             return null;
  1202         final String scheme = uri.getScheme();
  1204         final Intent intent;
  1206         // Compute our most likely intent, then check to see if there are any
  1207         // custom handlers that would apply.
  1208         // Start with the original URI. If we end up modifying it, we'll
  1209         // overwrite it.
  1210         final Intent likelyIntent = getIntentForActionString(action);
  1211         likelyIntent.setData(uri);
  1213         if ("vnd.youtube".equals(scheme) && !hasHandlersForIntent(likelyIntent)) {
  1214             // Special-case YouTube to use our own player if no system handler
  1215             // exists.
  1216             intent = new Intent(VideoPlayer.VIDEO_ACTION);
  1217             intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
  1218                                 "org.mozilla.gecko.VideoPlayer");
  1219             intent.setData(uri);
  1220         } else {
  1221             intent = likelyIntent;
  1224         // Have a special handling for SMS, as the message body
  1225         // is not extracted from the URI automatically.
  1226         if (!"sms".equals(scheme)) {
  1227             return intent;
  1230         final String query = uri.getEncodedQuery();
  1231         if (TextUtils.isEmpty(query)) {
  1232             return intent;
  1235         final String[] fields = query.split("&");
  1236         boolean foundBody = false;
  1237         String resultQuery = "";
  1238         for (String field : fields) {
  1239             if (foundBody || !field.startsWith("body=")) {
  1240                 resultQuery = resultQuery.concat(resultQuery.length() > 0 ? "&" + field : field);
  1241                 continue;
  1244             // Found the first body param. Put it into the intent.
  1245             final String body = Uri.decode(field.substring(5));
  1246             intent.putExtra("sms_body", body);
  1247             foundBody = true;
  1250         if (!foundBody) {
  1251             // No need to rewrite the URI, then.
  1252             return intent;
  1255         // Form a new URI without the body field in the query part, and
  1256         // push that into the new Intent.
  1257         final String newQuery = resultQuery.length() > 0 ? "?" + resultQuery : "";
  1258         final Uri pruned = uri.buildUpon().encodedQuery(newQuery).build();
  1259         intent.setData(pruned);
  1261         return intent;
  1264     /**
  1265      * Only called from GeckoApp.
  1266      */
  1267     public static void setNotificationClient(NotificationClient client) {
  1268         if (notificationClient == null) {
  1269             notificationClient = client;
  1270         } else {
  1271             Log.d(LOGTAG, "Notification client already set");
  1275     @WrapElementForJNI(stubName = "ShowAlertNotificationWrapper")
  1276     public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
  1277                                              String aAlertCookie, String aAlertName) {
  1278         // The intent to launch when the user clicks the expanded notification
  1279         String app = getContext().getClass().getName();
  1280         Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CALLBACK);
  1281         notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, app);
  1282         notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  1284         int notificationID = aAlertName.hashCode();
  1286         // Put the strings into the intent as an URI "alert:?name=<alertName>&app=<appName>&cookie=<cookie>"
  1287         Uri.Builder b = new Uri.Builder();
  1288         Uri dataUri = b.scheme("alert").path(Integer.toString(notificationID))
  1289                                        .appendQueryParameter("name", aAlertName)
  1290                                        .appendQueryParameter("cookie", aAlertCookie)
  1291                                        .build();
  1292         notificationIntent.setData(dataUri);
  1293         PendingIntent contentIntent = PendingIntent.getActivity(
  1294                 getContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  1296         ALERT_COOKIES.put(aAlertName, aAlertCookie);
  1297         callObserver(aAlertName, "alertshow", aAlertCookie);
  1299         notificationClient.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent);
  1302     @WrapElementForJNI
  1303     public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
  1304         int notificationID = aAlertName.hashCode();
  1305         notificationClient.update(notificationID, aProgress, aProgressMax, aAlertText);
  1308     @WrapElementForJNI
  1309     public static void closeNotification(String aAlertName) {
  1310         String alertCookie = ALERT_COOKIES.get(aAlertName);
  1311         if (alertCookie != null) {
  1312             callObserver(aAlertName, "alertfinished", alertCookie);
  1313             ALERT_COOKIES.remove(aAlertName);
  1316         removeObserver(aAlertName);
  1318         int notificationID = aAlertName.hashCode();
  1319         notificationClient.remove(notificationID);
  1322     public static void handleNotification(String aAction, String aAlertName, String aAlertCookie) {
  1323         int notificationID = aAlertName.hashCode();
  1325         if (GeckoApp.ACTION_ALERT_CALLBACK.equals(aAction)) {
  1326             callObserver(aAlertName, "alertclickcallback", aAlertCookie);
  1328             if (notificationClient.isOngoing(notificationID)) {
  1329                 // When clicked, keep the notification if it displays progress
  1330                 return;
  1333         closeNotification(aAlertName);
  1336     @WrapElementForJNI(stubName = "GetDpiWrapper")
  1337     public static int getDpi() {
  1338         if (sDensityDpi == 0) {
  1339             sDensityDpi = getContext().getResources().getDisplayMetrics().densityDpi;
  1342         return sDensityDpi;
  1345     @WrapElementForJNI
  1346     public static float getDensity() {
  1347         return getContext().getResources().getDisplayMetrics().density;
  1350     private static boolean isHighMemoryDevice() {
  1351         return HardwareUtils.getMemSize() > HIGH_MEMORY_DEVICE_THRESHOLD_MB;
  1354     /**
  1355      * Returns the colour depth of the default screen. This will either be
  1356      * 24 or 16.
  1357      */
  1358     @WrapElementForJNI(stubName = "GetScreenDepthWrapper")
  1359     public static synchronized int getScreenDepth() {
  1360         if (sScreenDepth == 0) {
  1361             sScreenDepth = 16;
  1362             PixelFormat info = new PixelFormat();
  1363             PixelFormat.getPixelFormatInfo(getGeckoInterface().getActivity().getWindowManager().getDefaultDisplay().getPixelFormat(), info);
  1364             if (info.bitsPerPixel >= 24 && isHighMemoryDevice()) {
  1365                 sScreenDepth = 24;
  1369         return sScreenDepth;
  1372     public static synchronized void setScreenDepthOverride(int aScreenDepth) {
  1373         if (sScreenDepth != 0) {
  1374             Log.e(LOGTAG, "Tried to override screen depth after it's already been set");
  1375             return;
  1378         sScreenDepth = aScreenDepth;
  1381     @WrapElementForJNI
  1382     public static void setFullScreen(boolean fullscreen) {
  1383         if (getGeckoInterface() != null)
  1384             getGeckoInterface().setFullScreen(fullscreen);
  1387     @WrapElementForJNI
  1388     public static void performHapticFeedback(boolean aIsLongPress) {
  1389         // Don't perform haptic feedback if a vibration is currently playing,
  1390         // because the haptic feedback will nuke the vibration.
  1391         if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) {
  1392             LayerView layerView = getLayerView();
  1393             layerView.performHapticFeedback(aIsLongPress ?
  1394                                             HapticFeedbackConstants.LONG_PRESS :
  1395                                             HapticFeedbackConstants.VIRTUAL_KEY);
  1399     private static Vibrator vibrator() {
  1400         LayerView layerView = getLayerView();
  1401         return (Vibrator) layerView.getContext().getSystemService(Context.VIBRATOR_SERVICE);
  1404     @WrapElementForJNI(stubName = "Vibrate1")
  1405     public static void vibrate(long milliseconds) {
  1406         sVibrationEndTime = System.nanoTime() + milliseconds * 1000000;
  1407         sVibrationMaybePlaying = true;
  1408         vibrator().vibrate(milliseconds);
  1411     @WrapElementForJNI(stubName = "VibrateA")
  1412     public static void vibrate(long[] pattern, int repeat) {
  1413         // If pattern.length is even, the last element in the pattern is a
  1414         // meaningless delay, so don't include it in vibrationDuration.
  1415         long vibrationDuration = 0;
  1416         int iterLen = pattern.length - (pattern.length % 2 == 0 ? 1 : 0);
  1417         for (int i = 0; i < iterLen; i++) {
  1418           vibrationDuration += pattern[i];
  1421         sVibrationEndTime = System.nanoTime() + vibrationDuration * 1000000;
  1422         sVibrationMaybePlaying = true;
  1423         vibrator().vibrate(pattern, repeat);
  1426     @WrapElementForJNI
  1427     public static void cancelVibrate() {
  1428         sVibrationMaybePlaying = false;
  1429         sVibrationEndTime = 0;
  1430         vibrator().cancel();
  1433     @WrapElementForJNI
  1434     public static void showInputMethodPicker() {
  1435         InputMethodManager imm = (InputMethodManager)
  1436             getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
  1437         imm.showInputMethodPicker();
  1440     @WrapElementForJNI
  1441     public static void setKeepScreenOn(final boolean on) {
  1442         ThreadUtils.postToUiThread(new Runnable() {
  1443             @Override
  1444             public void run() {
  1445                 // TODO
  1447         });
  1450     @WrapElementForJNI
  1451     public static void notifyDefaultPrevented(final boolean defaultPrevented) {
  1452         ThreadUtils.postToUiThread(new Runnable() {
  1453             @Override
  1454             public void run() {
  1455                 LayerView view = getLayerView();
  1456                 PanZoomController controller = (view == null ? null : view.getPanZoomController());
  1457                 if (controller != null) {
  1458                     controller.notifyDefaultActionPrevented(defaultPrevented);
  1461         });
  1464     @WrapElementForJNI
  1465     public static boolean isNetworkLinkUp() {
  1466         ConnectivityManager cm = (ConnectivityManager)
  1467            getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
  1468         try {
  1469             NetworkInfo info = cm.getActiveNetworkInfo();
  1470             if (info == null || !info.isConnected())
  1471                 return false;
  1472         } catch (SecurityException se) {
  1473             return false;
  1475         return true;
  1478     @WrapElementForJNI
  1479     public static boolean isNetworkLinkKnown() {
  1480         ConnectivityManager cm = (ConnectivityManager)
  1481             getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
  1482         try {
  1483             if (cm.getActiveNetworkInfo() == null)
  1484                 return false;
  1485         } catch (SecurityException se) {
  1486             return false;
  1488         return true;
  1491     @WrapElementForJNI
  1492     public static int networkLinkType() {
  1493         ConnectivityManager cm = (ConnectivityManager)
  1494             getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
  1495         NetworkInfo info = cm.getActiveNetworkInfo();
  1496         if (info == null) {
  1497             return LINK_TYPE_UNKNOWN;
  1500         switch (info.getType()) {
  1501             case ConnectivityManager.TYPE_ETHERNET:
  1502                 return LINK_TYPE_ETHERNET;
  1503             case ConnectivityManager.TYPE_WIFI:
  1504                 return LINK_TYPE_WIFI;
  1505             case ConnectivityManager.TYPE_WIMAX:
  1506                 return LINK_TYPE_WIMAX;
  1507             case ConnectivityManager.TYPE_MOBILE:
  1508                 break; // We will handle sub-types after the switch.
  1509             default:
  1510                 Log.w(LOGTAG, "Ignoring the current network type.");
  1511                 return LINK_TYPE_UNKNOWN;
  1514         TelephonyManager tm = (TelephonyManager)
  1515             getContext().getSystemService(Context.TELEPHONY_SERVICE);
  1516         if (tm == null) {
  1517             Log.e(LOGTAG, "Telephony service does not exist");
  1518             return LINK_TYPE_UNKNOWN;
  1521         switch (tm.getNetworkType()) {
  1522             case TelephonyManager.NETWORK_TYPE_IDEN:
  1523             case TelephonyManager.NETWORK_TYPE_CDMA:
  1524             case TelephonyManager.NETWORK_TYPE_GPRS:
  1525                 return LINK_TYPE_2G;
  1526             case TelephonyManager.NETWORK_TYPE_1xRTT:
  1527             case TelephonyManager.NETWORK_TYPE_EDGE:
  1528                 return LINK_TYPE_2G; // 2.5G
  1529             case TelephonyManager.NETWORK_TYPE_UMTS:
  1530             case TelephonyManager.NETWORK_TYPE_EVDO_0:
  1531                 return LINK_TYPE_3G;
  1532             case TelephonyManager.NETWORK_TYPE_HSPA:
  1533             case TelephonyManager.NETWORK_TYPE_HSDPA:
  1534             case TelephonyManager.NETWORK_TYPE_HSUPA:
  1535             case TelephonyManager.NETWORK_TYPE_EVDO_A:
  1536             case TelephonyManager.NETWORK_TYPE_EVDO_B:
  1537             case TelephonyManager.NETWORK_TYPE_EHRPD:
  1538                 return LINK_TYPE_3G; // 3.5G
  1539             case TelephonyManager.NETWORK_TYPE_HSPAP:
  1540                 return LINK_TYPE_3G; // 3.75G
  1541             case TelephonyManager.NETWORK_TYPE_LTE:
  1542                 return LINK_TYPE_4G; // 3.9G
  1543             case TelephonyManager.NETWORK_TYPE_UNKNOWN:
  1544             default:
  1545                 Log.w(LOGTAG, "Connected to an unknown mobile network!");
  1546                 return LINK_TYPE_UNKNOWN;
  1550     @WrapElementForJNI(stubName = "GetSystemColoursWrapper")
  1551     public static int[] getSystemColors() {
  1552         // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
  1553         final int[] attrsAppearance = {
  1554             android.R.attr.textColor,
  1555             android.R.attr.textColorPrimary,
  1556             android.R.attr.textColorPrimaryInverse,
  1557             android.R.attr.textColorSecondary,
  1558             android.R.attr.textColorSecondaryInverse,
  1559             android.R.attr.textColorTertiary,
  1560             android.R.attr.textColorTertiaryInverse,
  1561             android.R.attr.textColorHighlight,
  1562             android.R.attr.colorForeground,
  1563             android.R.attr.colorBackground,
  1564             android.R.attr.panelColorForeground,
  1565             android.R.attr.panelColorBackground
  1566         };
  1568         int[] result = new int[attrsAppearance.length];
  1570         final ContextThemeWrapper contextThemeWrapper =
  1571             new ContextThemeWrapper(getContext(), android.R.style.TextAppearance);
  1573         final TypedArray appearance = contextThemeWrapper.getTheme().obtainStyledAttributes(attrsAppearance);
  1575         if (appearance != null) {
  1576             for (int i = 0; i < appearance.getIndexCount(); i++) {
  1577                 int idx = appearance.getIndex(i);
  1578                 int color = appearance.getColor(idx, 0);
  1579                 result[idx] = color;
  1581             appearance.recycle();
  1584         return result;
  1587     @WrapElementForJNI
  1588     public static void killAnyZombies() {
  1589         GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() {
  1590             @Override
  1591             public boolean callback(int pid) {
  1592                 if (pid != android.os.Process.myPid())
  1593                     android.os.Process.killProcess(pid);
  1594                 return true;
  1596         };
  1598         EnumerateGeckoProcesses(visitor);
  1601     public static boolean checkForGeckoProcs() {
  1603         class GeckoPidCallback implements GeckoProcessesVisitor {
  1604             public boolean otherPidExist = false;
  1605             @Override
  1606             public boolean callback(int pid) {
  1607                 if (pid != android.os.Process.myPid()) {
  1608                     otherPidExist = true;
  1609                     return false;
  1611                 return true;
  1614         GeckoPidCallback visitor = new GeckoPidCallback();            
  1615         EnumerateGeckoProcesses(visitor);
  1616         return visitor.otherPidExist;
  1619     interface GeckoProcessesVisitor{
  1620         boolean callback(int pid);
  1623     private static void EnumerateGeckoProcesses(GeckoProcessesVisitor visiter) {
  1624         int pidColumn = -1;
  1625         int userColumn = -1;
  1627         try {
  1628             // run ps and parse its output
  1629             java.lang.Process ps = Runtime.getRuntime().exec("ps");
  1630             BufferedReader in = new BufferedReader(new InputStreamReader(ps.getInputStream()),
  1631                                                    2048);
  1633             String headerOutput = in.readLine();
  1635             // figure out the column offsets.  We only care about the pid and user fields
  1636             StringTokenizer st = new StringTokenizer(headerOutput);
  1638             int tokenSoFar = 0;
  1639             while (st.hasMoreTokens()) {
  1640                 String next = st.nextToken();
  1641                 if (next.equalsIgnoreCase("PID"))
  1642                     pidColumn = tokenSoFar;
  1643                 else if (next.equalsIgnoreCase("USER"))
  1644                     userColumn = tokenSoFar;
  1645                 tokenSoFar++;
  1648             // alright, the rest are process entries.
  1649             String psOutput = null;
  1650             while ((psOutput = in.readLine()) != null) {
  1651                 String[] split = psOutput.split("\\s+");
  1652                 if (split.length <= pidColumn || split.length <= userColumn)
  1653                     continue;
  1654                 int uid = android.os.Process.getUidForName(split[userColumn]);
  1655                 if (uid == android.os.Process.myUid() &&
  1656                     !split[split.length - 1].equalsIgnoreCase("ps")) {
  1657                     int pid = Integer.parseInt(split[pidColumn]);
  1658                     boolean keepGoing = visiter.callback(pid);
  1659                     if (keepGoing == false)
  1660                         break;
  1663             in.close();
  1665         catch (Exception e) {
  1666             Log.w(LOGTAG, "Failed to enumerate Gecko processes.",  e);
  1670     public static void waitForAnotherGeckoProc(){
  1671         int countdown = 40;
  1672         while (!checkForGeckoProcs() &&  --countdown > 0) {
  1673             try {
  1674                 Thread.sleep(100);
  1675             } catch (InterruptedException ie) {}
  1678     public static String getAppNameByPID(int pid) {
  1679         BufferedReader cmdlineReader = null;
  1680         String path = "/proc/" + pid + "/cmdline";
  1681         try {
  1682             File cmdlineFile = new File(path);
  1683             if (!cmdlineFile.exists())
  1684                 return "";
  1685             cmdlineReader = new BufferedReader(new FileReader(cmdlineFile));
  1686             return cmdlineReader.readLine().trim();
  1687         } catch (Exception ex) {
  1688             return "";
  1689         } finally {
  1690             if (null != cmdlineReader) {
  1691                 try {
  1692                     cmdlineReader.close();
  1693                 } catch (Exception e) {}
  1698     public static void listOfOpenFiles() {
  1699         int pidColumn = -1;
  1700         int nameColumn = -1;
  1702         try {
  1703             String filter = GeckoProfile.get(getContext()).getDir().toString();
  1704             Log.i(LOGTAG, "[OPENFILE] Filter: " + filter);
  1706             // run lsof and parse its output
  1707             java.lang.Process lsof = Runtime.getRuntime().exec("lsof");
  1708             BufferedReader in = new BufferedReader(new InputStreamReader(lsof.getInputStream()), 2048);
  1710             String headerOutput = in.readLine();
  1711             StringTokenizer st = new StringTokenizer(headerOutput);
  1712             int token = 0;
  1713             while (st.hasMoreTokens()) {
  1714                 String next = st.nextToken();
  1715                 if (next.equalsIgnoreCase("PID"))
  1716                     pidColumn = token;
  1717                 else if (next.equalsIgnoreCase("NAME"))
  1718                     nameColumn = token;
  1719                 token++;
  1722             // alright, the rest are open file entries.
  1723             Map<Integer, String> pidNameMap = new TreeMap<Integer, String>();
  1724             String output = null;
  1725             while ((output = in.readLine()) != null) {
  1726                 String[] split = output.split("\\s+");
  1727                 if (split.length <= pidColumn || split.length <= nameColumn)
  1728                     continue;
  1729                 Integer pid = new Integer(split[pidColumn]);
  1730                 String name = pidNameMap.get(pid);
  1731                 if (name == null) {
  1732                     name = getAppNameByPID(pid.intValue());
  1733                     pidNameMap.put(pid, name);
  1735                 String file = split[nameColumn];
  1736                 if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter))
  1737                     Log.i(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file);
  1739             in.close();
  1740         } catch (Exception e) { }
  1743     @WrapElementForJNI
  1744     public static void scanMedia(String aFile, String aMimeType) {
  1745         // If the platform didn't give us a mimetype, try to guess one from the filename
  1746         if (TextUtils.isEmpty(aMimeType)) {
  1747             int extPosition = aFile.lastIndexOf(".");
  1748             if (extPosition > 0 && extPosition < aFile.length() - 1) {
  1749                 aMimeType = getMimeTypeFromExtension(aFile.substring(extPosition+1));
  1753         Context context = getContext();
  1754         GeckoMediaScannerClient.startScan(context, aFile, aMimeType);
  1757     @WrapElementForJNI(stubName = "GetIconForExtensionWrapper")
  1758     public static byte[] getIconForExtension(String aExt, int iconSize) {
  1759         try {
  1760             if (iconSize <= 0)
  1761                 iconSize = 16;
  1763             if (aExt != null && aExt.length() > 1 && aExt.charAt(0) == '.')
  1764                 aExt = aExt.substring(1);
  1766             PackageManager pm = getContext().getPackageManager();
  1767             Drawable icon = getDrawableForExtension(pm, aExt);
  1768             if (icon == null) {
  1769                 // Use a generic icon
  1770                 icon = pm.getDefaultActivityIcon();
  1773             Bitmap bitmap = ((BitmapDrawable)icon).getBitmap();
  1774             if (bitmap.getWidth() != iconSize || bitmap.getHeight() != iconSize)
  1775                 bitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
  1777             ByteBuffer buf = ByteBuffer.allocate(iconSize * iconSize * 4);
  1778             bitmap.copyPixelsToBuffer(buf);
  1780             return buf.array();
  1782         catch (Exception e) {
  1783             Log.w(LOGTAG, "getIconForExtension failed.",  e);
  1784             return null;
  1788     private static String getMimeTypeFromExtension(String ext) {
  1789         final MimeTypeMap mtm = MimeTypeMap.getSingleton();
  1790         return mtm.getMimeTypeFromExtension(ext);
  1793     private static Drawable getDrawableForExtension(PackageManager pm, String aExt) {
  1794         Intent intent = new Intent(Intent.ACTION_VIEW);
  1795         final String mimeType = getMimeTypeFromExtension(aExt);
  1796         if (mimeType != null && mimeType.length() > 0)
  1797             intent.setType(mimeType);
  1798         else
  1799             return null;
  1801         List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
  1802         if (list.size() == 0)
  1803             return null;
  1805         ResolveInfo resolveInfo = list.get(0);
  1807         if (resolveInfo == null)
  1808             return null;
  1810         ActivityInfo activityInfo = resolveInfo.activityInfo;
  1812         return activityInfo.loadIcon(pm);
  1815     @WrapElementForJNI
  1816     public static boolean getShowPasswordSetting() {
  1817         try {
  1818             int showPassword =
  1819                 Settings.System.getInt(getContext().getContentResolver(),
  1820                                        Settings.System.TEXT_SHOW_PASSWORD, 1);
  1821             return (showPassword > 0);
  1823         catch (Exception e) {
  1824             return true;
  1828     @WrapElementForJNI(stubName = "AddPluginViewWrapper")
  1829     public static void addPluginView(View view,
  1830                                      float x, float y,
  1831                                      float w, float h,
  1832                                      boolean isFullScreen) {
  1833         if (getGeckoInterface() != null)
  1834              getGeckoInterface().addPluginView(view, new RectF(x, y, x + w, y + h), isFullScreen);
  1837     @WrapElementForJNI
  1838     public static void removePluginView(View view, boolean isFullScreen) {
  1839         if (getGeckoInterface() != null)
  1840             getGeckoInterface().removePluginView(view, isFullScreen);
  1843     /**
  1844      * A plugin that wish to be loaded in the WebView must provide this permission
  1845      * in their AndroidManifest.xml.
  1846      */
  1847     public static final String PLUGIN_ACTION = "android.webkit.PLUGIN";
  1848     public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN";
  1850     private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/";
  1852     private static final String PLUGIN_TYPE = "type";
  1853     private static final String TYPE_NATIVE = "native";
  1854     static public ArrayList<PackageInfo> mPackageInfoCache = new ArrayList<PackageInfo>();
  1856     // Returns null if plugins are blocked on the device.
  1857     static String[] getPluginDirectories() {
  1859         // An awful hack to detect Tegra devices. Easiest way to do it without spinning up a EGL context.
  1860         boolean isTegra = (new File("/system/lib/hw/gralloc.tegra.so")).exists() ||
  1861                           (new File("/system/lib/hw/gralloc.tegra3.so")).exists();
  1862         if (isTegra) {
  1863             // disable Flash on Tegra ICS with CM9 and other custom firmware (bug 736421)
  1864             File vfile = new File("/proc/version");
  1865             FileReader vreader = null;
  1866             try {
  1867                 if (vfile.canRead()) {
  1868                     vreader = new FileReader(vfile);
  1869                     String version = new BufferedReader(vreader).readLine();
  1870                     if (version.indexOf("CM9") != -1 ||
  1871                         version.indexOf("cyanogen") != -1 ||
  1872                         version.indexOf("Nova") != -1)
  1874                         Log.w(LOGTAG, "Blocking plugins because of Tegra 2 + unofficial ICS bug (bug 736421)");
  1875                         return null;
  1878             } catch (IOException ex) {
  1879                 // nothing
  1880             } finally {
  1881                 try {
  1882                     if (vreader != null) {
  1883                         vreader.close();
  1885                 } catch (IOException ex) {
  1886                     // nothing
  1890             // disable on KitKat (bug 957694)
  1891             if (Build.VERSION.SDK_INT >= 19) {
  1892                 Log.w(LOGTAG, "Blocking plugins because of Tegra (bug 957694)");
  1893                 return null;
  1897         ArrayList<String> directories = new ArrayList<String>();
  1898         PackageManager pm = getContext().getPackageManager();
  1899         List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
  1900                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
  1902         synchronized(mPackageInfoCache) {
  1904             // clear the list of existing packageInfo objects
  1905             mPackageInfoCache.clear();
  1908             for (ResolveInfo info : plugins) {
  1910                 // retrieve the plugin's service information
  1911                 ServiceInfo serviceInfo = info.serviceInfo;
  1912                 if (serviceInfo == null) {
  1913                     Log.w(LOGTAG, "Ignoring bad plugin.");
  1914                     continue;
  1917                 // Blacklist HTC's flash lite.
  1918                 // See bug #704516 - We're not quite sure what Flash Lite does,
  1919                 // but loading it causes Flash to give errors and fail to draw.
  1920                 if (serviceInfo.packageName.equals("com.htc.flashliteplugin")) {
  1921                     Log.w(LOGTAG, "Skipping HTC's flash lite plugin");
  1922                     continue;
  1926                 // Retrieve information from the plugin's manifest.
  1927                 PackageInfo pkgInfo;
  1928                 try {
  1929                     pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
  1930                                     PackageManager.GET_PERMISSIONS
  1931                                     | PackageManager.GET_SIGNATURES);
  1932                 } catch (Exception e) {
  1933                     Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
  1934                     continue;
  1937                 if (pkgInfo == null) {
  1938                     Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Could not load package information.");
  1939                     continue;
  1942                 /*
  1943                  * find the location of the plugin's shared library. The default
  1944                  * is to assume the app is either a user installed app or an
  1945                  * updated system app. In both of these cases the library is
  1946                  * stored in the app's data directory.
  1947                  */
  1948                 String directory = pkgInfo.applicationInfo.dataDir + "/lib";
  1949                 final int appFlags = pkgInfo.applicationInfo.flags;
  1950                 final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM |
  1951                                                ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
  1953                 // preloaded system app with no user updates
  1954                 if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) {
  1955                     directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
  1958                 // check if the plugin has the required permissions
  1959                 String permissions[] = pkgInfo.requestedPermissions;
  1960                 if (permissions == null) {
  1961                     Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission.");
  1962                     continue;
  1964                 boolean permissionOk = false;
  1965                 for (String permit : permissions) {
  1966                     if (PLUGIN_PERMISSION.equals(permit)) {
  1967                         permissionOk = true;
  1968                         break;
  1971                 if (!permissionOk) {
  1972                     Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission (2).");
  1973                     continue;
  1976                 // check to ensure the plugin is properly signed
  1977                 Signature signatures[] = pkgInfo.signatures;
  1978                 if (signatures == null) {
  1979                     Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Not signed.");
  1980                     continue;
  1983                 // determine the type of plugin from the manifest
  1984                 if (serviceInfo.metaData == null) {
  1985                     Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no defined type.");
  1986                     continue;
  1989                 String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
  1990                 if (!TYPE_NATIVE.equals(pluginType)) {
  1991                     Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
  1992                     continue;
  1995                 try {
  1996                     Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
  1998                     //TODO implement any requirements of the plugin class here!
  1999                     boolean classFound = true;
  2001                     if (!classFound) {
  2002                         Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
  2003                         continue;
  2006                 } catch (NameNotFoundException e) {
  2007                     Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
  2008                     continue;
  2009                 } catch (ClassNotFoundException e) {
  2010                     Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
  2011                     continue;
  2014                 // if all checks have passed then make the plugin available
  2015                 mPackageInfoCache.add(pkgInfo);
  2016                 directories.add(directory);
  2020         return directories.toArray(new String[directories.size()]);
  2023     static String getPluginPackage(String pluginLib) {
  2025         if (pluginLib == null || pluginLib.length() == 0) {
  2026             return null;
  2029         synchronized(mPackageInfoCache) {
  2030             for (PackageInfo pkgInfo : mPackageInfoCache) {
  2031                 if (pluginLib.contains(pkgInfo.packageName)) {
  2032                     return pkgInfo.packageName;
  2037         return null;
  2040     static Class<?> getPluginClass(String packageName, String className)
  2041             throws NameNotFoundException, ClassNotFoundException {
  2042         Context pluginContext = getContext().createPackageContext(packageName,
  2043                 Context.CONTEXT_INCLUDE_CODE |
  2044                 Context.CONTEXT_IGNORE_SECURITY);
  2045         ClassLoader pluginCL = pluginContext.getClassLoader();
  2046         return pluginCL.loadClass(className);
  2049     @WrapElementForJNI(allowMultithread = true)
  2050     public static Class<?> loadPluginClass(String className, String libName) {
  2051         if (getGeckoInterface() == null)
  2052             return null;
  2053         try {
  2054             final String packageName = getPluginPackage(libName);
  2055             final int contextFlags = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY;
  2056             final Context pluginContext = getContext().createPackageContext(packageName, contextFlags);
  2057             return pluginContext.getClassLoader().loadClass(className);
  2058         } catch (java.lang.ClassNotFoundException cnfe) {
  2059             Log.w(LOGTAG, "Couldn't find plugin class " + className, cnfe);
  2060             return null;
  2061         } catch (android.content.pm.PackageManager.NameNotFoundException nnfe) {
  2062             Log.w(LOGTAG, "Couldn't find package.", nnfe);
  2063             return null;
  2067     private static ContextGetter sContextGetter;
  2069     @WrapElementForJNI(allowMultithread = true)
  2070     public static Context getContext() {
  2071         return sContextGetter.getContext();
  2074     public static void setContextGetter(ContextGetter cg) {
  2075         sContextGetter = cg;
  2078     public static SharedPreferences getSharedPreferences() {
  2079         if (sContextGetter == null) {
  2080             throw new IllegalStateException("No ContextGetter; cannot fetch prefs.");
  2082         return sContextGetter.getSharedPreferences();
  2085     public interface AppStateListener {
  2086         public void onPause();
  2087         public void onResume();
  2088         public void onOrientationChanged();
  2091     public interface GeckoInterface {
  2092         public GeckoProfile getProfile();
  2093         public PromptService getPromptService();
  2094         public Activity getActivity();
  2095         public String getDefaultUAString();
  2096         public LocationListener getLocationListener();
  2097         public SensorEventListener getSensorEventListener();
  2098         public void doRestart();
  2099         public void setFullScreen(boolean fullscreen);
  2100         public void addPluginView(View view, final RectF rect, final boolean isFullScreen);
  2101         public void removePluginView(final View view, final boolean isFullScreen);
  2102         public void enableCameraView();
  2103         public void disableCameraView();
  2104         public void addAppStateListener(AppStateListener listener);
  2105         public void removeAppStateListener(AppStateListener listener);
  2106         public View getCameraView();
  2107         public void notifyWakeLockChanged(String topic, String state);
  2108         public FormAssistPopup getFormAssistPopup();
  2109         public boolean areTabsShown();
  2110         public AbsoluteLayout getPluginContainer();
  2111         public void notifyCheckUpdateResult(String result);
  2112         public boolean hasTabsSideBar();
  2113         public void invalidateOptionsMenu();
  2114     };
  2116     private static GeckoInterface sGeckoInterface;
  2118     public static GeckoInterface getGeckoInterface() {
  2119         return sGeckoInterface;
  2122     public static void setGeckoInterface(GeckoInterface aGeckoInterface) {
  2123         sGeckoInterface = aGeckoInterface;
  2126     public static android.hardware.Camera sCamera = null;
  2128     static native void cameraCallbackBridge(byte[] data);
  2130     static int kPreferedFps = 25;
  2131     static byte[] sCameraBuffer = null;
  2134     @WrapElementForJNI(stubName = "InitCameraWrapper")
  2135     static int[] initCamera(String aContentType, int aCamera, int aWidth, int aHeight) {
  2136         ThreadUtils.postToUiThread(new Runnable() {
  2137                 @Override
  2138                 public void run() {
  2139                     try {
  2140                         if (getGeckoInterface() != null)
  2141                             getGeckoInterface().enableCameraView();
  2142                     } catch (Exception e) {}
  2144             });
  2146         // [0] = 0|1 (failure/success)
  2147         // [1] = width
  2148         // [2] = height
  2149         // [3] = fps
  2150         int[] result = new int[4];
  2151         result[0] = 0;
  2153         if (Build.VERSION.SDK_INT >= 9) {
  2154             if (android.hardware.Camera.getNumberOfCameras() == 0)
  2155                 return result;
  2158         try {
  2159             // no front/back camera before API level 9
  2160             if (Build.VERSION.SDK_INT >= 9)
  2161                 sCamera = android.hardware.Camera.open(aCamera);
  2162             else
  2163                 sCamera = android.hardware.Camera.open();
  2165             android.hardware.Camera.Parameters params = sCamera.getParameters();
  2166             params.setPreviewFormat(ImageFormat.NV21);
  2168             // use the preview fps closest to 25 fps.
  2169             int fpsDelta = 1000;
  2170             try {
  2171                 Iterator<Integer> it = params.getSupportedPreviewFrameRates().iterator();
  2172                 while (it.hasNext()) {
  2173                     int nFps = it.next();
  2174                     if (Math.abs(nFps - kPreferedFps) < fpsDelta) {
  2175                         fpsDelta = Math.abs(nFps - kPreferedFps);
  2176                         params.setPreviewFrameRate(nFps);
  2179             } catch(Exception e) {
  2180                 params.setPreviewFrameRate(kPreferedFps);
  2183             // set up the closest preview size available
  2184             Iterator<android.hardware.Camera.Size> sit = params.getSupportedPreviewSizes().iterator();
  2185             int sizeDelta = 10000000;
  2186             int bufferSize = 0;
  2187             while (sit.hasNext()) {
  2188                 android.hardware.Camera.Size size = sit.next();
  2189                 if (Math.abs(size.width * size.height - aWidth * aHeight) < sizeDelta) {
  2190                     sizeDelta = Math.abs(size.width * size.height - aWidth * aHeight);
  2191                     params.setPreviewSize(size.width, size.height);
  2192                     bufferSize = size.width * size.height;
  2196             try {
  2197                 if (getGeckoInterface() != null) {
  2198                     View cameraView = getGeckoInterface().getCameraView();
  2199                     if (cameraView instanceof SurfaceView) {
  2200                         sCamera.setPreviewDisplay(((SurfaceView)cameraView).getHolder());
  2201                     } else if (cameraView instanceof TextureView) {
  2202                         sCamera.setPreviewTexture(((TextureView)cameraView).getSurfaceTexture());
  2205             } catch(IOException e) {
  2206                 Log.w(LOGTAG, "Error setPreviewXXX:", e);
  2207             } catch(RuntimeException e) {
  2208                 Log.w(LOGTAG, "Error setPreviewXXX:", e);
  2211             sCamera.setParameters(params);
  2212             sCameraBuffer = new byte[(bufferSize * 12) / 8];
  2213             sCamera.addCallbackBuffer(sCameraBuffer);
  2214             sCamera.setPreviewCallbackWithBuffer(new android.hardware.Camera.PreviewCallback() {
  2215                 @Override
  2216                 public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
  2217                     cameraCallbackBridge(data);
  2218                     if (sCamera != null)
  2219                         sCamera.addCallbackBuffer(sCameraBuffer);
  2221             });
  2222             sCamera.startPreview();
  2223             params = sCamera.getParameters();
  2224             result[0] = 1;
  2225             result[1] = params.getPreviewSize().width;
  2226             result[2] = params.getPreviewSize().height;
  2227             result[3] = params.getPreviewFrameRate();
  2228         } catch(RuntimeException e) {
  2229             Log.w(LOGTAG, "initCamera RuntimeException.", e);
  2230             result[0] = result[1] = result[2] = result[3] = 0;
  2232         return result;
  2235     @WrapElementForJNI
  2236     static synchronized void closeCamera() {
  2237         ThreadUtils.postToUiThread(new Runnable() {
  2238                 @Override
  2239                 public void run() {
  2240                     try {
  2241                         if (getGeckoInterface() != null)
  2242                             getGeckoInterface().disableCameraView();
  2243                     } catch (Exception e) {}
  2245             });
  2246         if (sCamera != null) {
  2247             sCamera.stopPreview();
  2248             sCamera.release();
  2249             sCamera = null;
  2250             sCameraBuffer = null;
  2254     /**
  2255      * Adds a listener for a gecko event.
  2256      * This method is thread-safe and may be called at any time. In particular, calling it
  2257      * with an event that is currently being processed has the properly-defined behaviour that
  2258      * any added listeners will not be invoked on the event currently being processed, but
  2259      * will be invoked on future events of that type.
  2260      */
  2261     @RobocopTarget
  2262     public static void registerEventListener(String event, GeckoEventListener listener) {
  2263         sEventDispatcher.registerEventListener(event, listener);
  2266     public static EventDispatcher getEventDispatcher() {
  2267         return sEventDispatcher;
  2270     /**
  2271      * Remove a previously-registered listener for a gecko event.
  2272      * This method is thread-safe and may be called at any time. In particular, calling it
  2273      * with an event that is currently being processed has the properly-defined behaviour that
  2274      * any removed listeners will still be invoked on the event currently being processed, but
  2275      * will not be invoked on future events of that type.
  2276      */
  2277     @RobocopTarget
  2278     public static void unregisterEventListener(String event, GeckoEventListener listener) {
  2279         sEventDispatcher.unregisterEventListener(event, listener);
  2282     /*
  2283      * Battery API related methods.
  2284      */
  2285     @WrapElementForJNI
  2286     public static void enableBatteryNotifications() {
  2287         GeckoBatteryManager.enableNotifications();
  2290     @WrapElementForJNI(stubName = "HandleGeckoMessageWrapper")
  2291     public static void handleGeckoMessage(final NativeJSContainer message) {
  2292         sEventDispatcher.dispatchEvent(message);
  2293         message.dispose();
  2296     @WrapElementForJNI
  2297     public static void disableBatteryNotifications() {
  2298         GeckoBatteryManager.disableNotifications();
  2301     @WrapElementForJNI(stubName = "GetCurrentBatteryInformationWrapper")
  2302     public static double[] getCurrentBatteryInformation() {
  2303         return GeckoBatteryManager.getCurrentInformation();
  2306     @WrapElementForJNI(stubName = "CheckURIVisited")
  2307     static void checkUriVisited(String uri) {
  2308         GlobalHistory.getInstance().checkUriVisited(uri);
  2311     @WrapElementForJNI(stubName = "MarkURIVisited")
  2312     static void markUriVisited(final String uri) {
  2313         ThreadUtils.postToBackgroundThread(new Runnable() {
  2314             @Override
  2315             public void run() {
  2316                 GlobalHistory.getInstance().add(uri);
  2318         });
  2321     @WrapElementForJNI(stubName = "SetURITitle")
  2322     static void setUriTitle(final String uri, final String title) {
  2323         ThreadUtils.postToBackgroundThread(new Runnable() {
  2324             @Override
  2325             public void run() {
  2326                 GlobalHistory.getInstance().update(uri, title);
  2328         });
  2331     @WrapElementForJNI
  2332     static void hideProgressDialog() {
  2333         // unused stub
  2336     /*
  2337      * WebSMS related methods.
  2338      */
  2339     @WrapElementForJNI(stubName = "SendMessageWrapper")
  2340     public static void sendMessage(String aNumber, String aMessage, int aRequestId) {
  2341         if (SmsManager.getInstance() == null) {
  2342             return;
  2345         SmsManager.getInstance().send(aNumber, aMessage, aRequestId);
  2348     @WrapElementForJNI(stubName = "GetMessageWrapper")
  2349     public static void getMessage(int aMessageId, int aRequestId) {
  2350         if (SmsManager.getInstance() == null) {
  2351             return;
  2354         SmsManager.getInstance().getMessage(aMessageId, aRequestId);
  2357     @WrapElementForJNI(stubName = "DeleteMessageWrapper")
  2358     public static void deleteMessage(int aMessageId, int aRequestId) {
  2359         if (SmsManager.getInstance() == null) {
  2360             return;
  2363         SmsManager.getInstance().deleteMessage(aMessageId, aRequestId);
  2366     @WrapElementForJNI(stubName = "CreateMessageListWrapper")
  2367     public static void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
  2368         if (SmsManager.getInstance() == null) {
  2369             return;
  2372         SmsManager.getInstance().createMessageList(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId);
  2375     @WrapElementForJNI(stubName = "GetNextMessageInListWrapper")
  2376     public static void getNextMessageInList(int aListId, int aRequestId) {
  2377         if (SmsManager.getInstance() == null) {
  2378             return;
  2381         SmsManager.getInstance().getNextMessageInList(aListId, aRequestId);
  2384     @WrapElementForJNI
  2385     public static void clearMessageList(int aListId) {
  2386         if (SmsManager.getInstance() == null) {
  2387             return;
  2390         SmsManager.getInstance().clearMessageList(aListId);
  2393     /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */
  2394     @WrapElementForJNI
  2395     @RobocopTarget
  2396     public static boolean isTablet() {
  2397         return HardwareUtils.isTablet();
  2400     public static void viewSizeChanged() {
  2401         LayerView v = getLayerView();
  2402         if (v != null && v.isIMEEnabled()) {
  2403             sendEventToGecko(GeckoEvent.createBroadcastEvent(
  2404                     "ScrollTo:FocusedInput", ""));
  2408     @WrapElementForJNI(stubName = "GetCurrentNetworkInformationWrapper")
  2409     public static double[] getCurrentNetworkInformation() {
  2410         return GeckoNetworkManager.getInstance().getCurrentInformation();
  2413     @WrapElementForJNI
  2414     public static void enableNetworkNotifications() {
  2415         GeckoNetworkManager.getInstance().enableNotifications();
  2418     @WrapElementForJNI
  2419     public static void disableNetworkNotifications() {
  2420         GeckoNetworkManager.getInstance().disableNotifications();
  2423     // values taken from android's Base64
  2424     public static final int BASE64_DEFAULT = 0;
  2425     public static final int BASE64_URL_SAFE = 8;
  2427     /**
  2428      * taken from http://www.source-code.biz/base64coder/java/Base64Coder.java.txt and modified (MIT License)
  2429      */
  2430     // Mapping table from 6-bit nibbles to Base64 characters.
  2431     private static final byte[] map1 = new byte[64];
  2432     private static final byte[] map1_urlsafe;
  2433     static {
  2434       int i=0;
  2435       for (byte c='A'; c<='Z'; c++) map1[i++] = c;
  2436       for (byte c='a'; c<='z'; c++) map1[i++] = c;
  2437       for (byte c='0'; c<='9'; c++) map1[i++] = c;
  2438       map1[i++] = '+'; map1[i++] = '/';
  2439       map1_urlsafe = map1.clone();
  2440       map1_urlsafe[62] = '-'; map1_urlsafe[63] = '_'; 
  2443     // Mapping table from Base64 characters to 6-bit nibbles.
  2444     private static final byte[] map2 = new byte[128];
  2445     static {
  2446         for (int i=0; i<map2.length; i++) map2[i] = -1;
  2447         for (int i=0; i<64; i++) map2[map1[i]] = (byte)i;
  2448         map2['-'] = (byte)62; map2['_'] = (byte)63;
  2451     final static byte EQUALS_ASCII = (byte) '=';
  2453     /**
  2454      * Encodes a byte array into Base64 format.
  2455      * No blanks or line breaks are inserted in the output.
  2456      * @param in    An array containing the data bytes to be encoded.
  2457      * @return      A character array containing the Base64 encoded data.
  2458      */
  2459     public static byte[] encodeBase64(byte[] in, int flags) {
  2460         if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.FROYO)
  2461             return Base64.encode(in, flags | Base64.NO_WRAP);
  2462         int oDataLen = (in.length*4+2)/3;       // output length without padding
  2463         int oLen = ((in.length+2)/3)*4;         // output length including padding
  2464         byte[] out = new byte[oLen];
  2465         int ip = 0;
  2466         int iEnd = in.length;
  2467         int op = 0;
  2468         byte[] toMap = ((flags & BASE64_URL_SAFE) == 0 ? map1 : map1_urlsafe);
  2469         while (ip < iEnd) {
  2470             int i0 = in[ip++] & 0xff;
  2471             int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
  2472             int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
  2473             int o0 = i0 >>> 2;
  2474             int o1 = ((i0 &   3) << 4) | (i1 >>> 4);
  2475             int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
  2476             int o3 = i2 & 0x3F;
  2477             out[op++] = toMap[o0];
  2478             out[op++] = toMap[o1];
  2479             out[op] = op < oDataLen ? toMap[o2] : EQUALS_ASCII; op++;
  2480             out[op] = op < oDataLen ? toMap[o3] : EQUALS_ASCII; op++;
  2482         return out; 
  2485     /**
  2486      * Decodes a byte array from Base64 format.
  2487      * No blanks or line breaks are allowed within the Base64 encoded input data.
  2488      * @param in    A character array containing the Base64 encoded data.
  2489      * @param iOff  Offset of the first character in <code>in</code> to be processed.
  2490      * @param iLen  Number of characters to process in <code>in</code>, starting at <code>iOff</code>.
  2491      * @return      An array containing the decoded data bytes.
  2492      * @throws      IllegalArgumentException If the input is not valid Base64 encoded data.
  2493      */
  2494     public static byte[] decodeBase64(byte[] in, int flags) {
  2495         if (Build.VERSION.SDK_INT >=Build.VERSION_CODES.FROYO)
  2496             return Base64.decode(in, flags);
  2497         int iOff = 0;
  2498         int iLen = in.length;
  2499         if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4.");
  2500         while (iLen > 0 && in[iOff+iLen-1] == '=') iLen--;
  2501         int oLen = (iLen*3) / 4;
  2502         byte[] out = new byte[oLen];
  2503         int ip = iOff;
  2504         int iEnd = iOff + iLen;
  2505         int op = 0;
  2506         while (ip < iEnd) {
  2507             int i0 = in[ip++];
  2508             int i1 = in[ip++];
  2509             int i2 = ip < iEnd ? in[ip++] : 'A';
  2510             int i3 = ip < iEnd ? in[ip++] : 'A';
  2511             if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
  2512                 throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
  2513             int b0 = map2[i0];
  2514             int b1 = map2[i1];
  2515             int b2 = map2[i2];
  2516             int b3 = map2[i3];
  2517             if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
  2518                 throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
  2519             int o0 = ( b0       <<2) | (b1>>>4);
  2520             int o1 = ((b1 & 0xf)<<4) | (b2>>>2);
  2521             int o2 = ((b2 &   3)<<6) |  b3;
  2522             out[op++] = (byte)o0;
  2523             if (op<oLen) out[op++] = (byte)o1;
  2524             if (op<oLen) out[op++] = (byte)o2; }
  2525         return out; 
  2528     public static byte[] decodeBase64(String s, int flags) {
  2529         return decodeBase64(s.getBytes(), flags);
  2532     @WrapElementForJNI(stubName = "GetScreenOrientationWrapper")
  2533     public static short getScreenOrientation() {
  2534         return GeckoScreenOrientation.getInstance().getScreenOrientation().value;
  2537     @WrapElementForJNI
  2538     public static void enableScreenOrientationNotifications() {
  2539         GeckoScreenOrientation.getInstance().enableNotifications();
  2542     @WrapElementForJNI
  2543     public static void disableScreenOrientationNotifications() {
  2544         GeckoScreenOrientation.getInstance().disableNotifications();
  2547     @WrapElementForJNI
  2548     public static void lockScreenOrientation(int aOrientation) {
  2549         GeckoScreenOrientation.getInstance().lock(aOrientation);
  2552     @WrapElementForJNI
  2553     public static void unlockScreenOrientation() {
  2554         GeckoScreenOrientation.getInstance().unlock();
  2557     @WrapElementForJNI
  2558     public static boolean pumpMessageLoop() {
  2559         Handler geckoHandler = ThreadUtils.sGeckoHandler;
  2560         Message msg = getNextMessageFromQueue(ThreadUtils.sGeckoQueue);
  2562         if (msg == null)
  2563             return false;
  2564         if (msg.obj == geckoHandler && msg.getTarget() == geckoHandler) {
  2565             // Our "queue is empty" message; see runGecko()
  2566             msg.recycle();
  2567             return false;
  2569         if (msg.getTarget() == null) 
  2570             Looper.myLooper().quit();
  2571         else
  2572             msg.getTarget().dispatchMessage(msg);
  2573         msg.recycle();
  2574         return true;
  2577     @WrapElementForJNI
  2578     public static void notifyWakeLockChanged(String topic, String state) {
  2579         if (getGeckoInterface() != null)
  2580             getGeckoInterface().notifyWakeLockChanged(topic, state);
  2583     @WrapElementForJNI
  2584     public static void registerSurfaceTextureFrameListener(Object surfaceTexture, final int id) {
  2585         ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
  2586             @Override
  2587             public void onFrameAvailable(SurfaceTexture surfaceTexture) {
  2588                 GeckoAppShell.onSurfaceTextureFrameAvailable(surfaceTexture, id);
  2590         });
  2593     @WrapElementForJNI(allowMultithread = true)
  2594     public static void unregisterSurfaceTextureFrameListener(Object surfaceTexture) {
  2595         ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(null);
  2598     @WrapElementForJNI
  2599     public static boolean unlockProfile() {
  2600         // Try to kill any zombie Fennec's that might be running
  2601         GeckoAppShell.killAnyZombies();
  2603         // Then force unlock this profile
  2604         if (getGeckoInterface() != null) {
  2605             GeckoProfile profile = getGeckoInterface().getProfile();
  2606             File lock = profile.getFile(".parentlock");
  2607             return lock.exists() && lock.delete();
  2609         return false;
  2612     @WrapElementForJNI(stubName = "GetProxyForURIWrapper")
  2613     public static String getProxyForURI(String spec, String scheme, String host, int port) {
  2614         final ProxySelector ps = new ProxySelector();
  2616         Proxy proxy = ps.select(scheme, host);
  2617         if (Proxy.NO_PROXY.equals(proxy)) {
  2618             return "DIRECT";
  2621         switch (proxy.type()) {
  2622             case HTTP:
  2623                 return "PROXY " + proxy.address().toString();
  2624             case SOCKS:
  2625                 return "SOCKS " + proxy.address().toString();
  2628         return "DIRECT";
  2631     /* Downloads the uri pointed to by a share intent, and alters the intent to point to the locally stored file.
  2632      */
  2633     public static void downloadImageForIntent(final Intent intent) {
  2634         final String src = intent.getStringExtra(Intent.EXTRA_TEXT);
  2635         final File dir = GeckoApp.getTempDirectory();
  2637         if (dir == null) {
  2638             showImageShareFailureToast();
  2639             return;
  2642         GeckoApp.deleteTempFiles();
  2644         String type = intent.getType();
  2645         OutputStream os = null;
  2646         try {
  2647             // Create a temporary file for the image
  2648             if (src.startsWith("data:")) {
  2649                 final int dataStart = src.indexOf(",");
  2651                 String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type);
  2653                 // If we weren't given an explicit mimetype, try to dig one out of the data uri.
  2654                 if (TextUtils.isEmpty(extension) && dataStart > 5) {
  2655                     type = src.substring(5, dataStart).replace(";base64", "");
  2656                     extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type);
  2659                 final File imageFile = File.createTempFile("image", "." + extension, dir);
  2660                 os = new FileOutputStream(imageFile);
  2662                 byte[] buf = Base64.decode(src.substring(dataStart + 1), Base64.DEFAULT);
  2663                 os.write(buf);
  2665                 // Only alter the intent when we're sure everything has worked
  2666                 intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
  2667             } else {
  2668                 InputStream is = null;
  2669                 try {
  2670                     final byte[] buf = new byte[2048];
  2671                     final URL url = new URL(src);
  2672                     final String filename = URLUtil.guessFileName(src, null, type);
  2673                     is = url.openStream();
  2675                     final File imageFile = new File(dir, filename);
  2676                     os = new FileOutputStream(imageFile);
  2678                     int length;
  2679                     while ((length = is.read(buf)) != -1) {
  2680                         os.write(buf, 0, length);
  2683                     // Only alter the intent when we're sure everything has worked
  2684                     intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
  2685                 } finally {
  2686                     safeStreamClose(is);
  2689         } catch(IOException ex) {
  2690             // If something went wrong, we'll just leave the intent un-changed
  2691         } finally {
  2692             safeStreamClose(os);
  2696     // Don't fail silently, tell the user that we weren't able to share the image
  2697     private static final void showImageShareFailureToast() {
  2698         Toast toast = Toast.makeText(getContext(),
  2699                                      getContext().getResources().getString(R.string.share_image_failed),
  2700                                      Toast.LENGTH_SHORT);
  2701         toast.show();

mercurial