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