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