mobile/android/base/GeckoAppShell.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial