mobile/android/base/GeckoAppShell.java

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

mercurial