|
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 } |